Как добавить пользовательский фильтр OpenId в загрузочное приложение Spring? - PullRequest
0 голосов
/ 18 января 2019

Я пытаюсь реализовать внутреннюю сторону аутентификации OpenId Connect. Это API без сохранения состояния, поэтому я добавил фильтр, который обрабатывает токен Bearer.

Я создал фильтр OpenIdConnect, который обрабатывает аутентификацию, и добавил его в WebSecurityConfigurerAdapter.

public class OpenIdConnectFilter extends 
   AbstractAuthenticationProcessingFilter {
 @Value("${auth0.clientId}")
 private String clientId;

 @Value("${auth0.issuer}")
 private String issuer;

 @Value("${auth0.keyUrl}")
 private String jwkUrl;

private TokenExtractor tokenExtractor = new BearerTokenExtractor();

public OpenIdConnectFilter() {
    super("/connect/**");
    setAuthenticationManager(new NoopAuthenticationManager());
}

@Bean
public FilterRegistrationBean registration(OpenIdConnectFilter filter) {
  FilterRegistrationBean registration = new FilterRegistrationBean(filter);
  registration.setEnabled(false);
  return registration;
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {


    try {
      Authentication authentication = tokenExtractor.extract(request);

      String accessToken = (String) authentication.getPrincipal();
        String kid = JwtHelper.headers(accessToken)
            .get("kid");
        final Jwt tokenDecoded = JwtHelper.decodeAndVerify(accessToken, verifier(kid));
        final Map<String, Object> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
        verifyClaims(authInfo);
        Set<String> scopes = new HashSet<String>(Arrays.asList(((String) authInfo.get("scope")).split(" ")));
        int expires = (Integer) authInfo.get("exp");
        OpenIdToken openIdToken = new OpenIdToken(accessToken, scopes, Long.valueOf(expires), authInfo);
        final OpenIdUserDetails user = new OpenIdUserDetails((String) authInfo.get("sub"), "Test", openIdToken);

        return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    } catch (final Exception e) {
        throw new BadCredentialsException("Could not obtain user details from token", e);
    }

}

public void verifyClaims(Map claims) {
    int exp = (int) claims.get("exp");
    Date expireDate = new Date(exp * 1000L);
    Date now = new Date();
    if (expireDate.before(now) || !claims.get("iss").equals(issuer) || !claims.get("azp").equals(clientId)) {
        throw new RuntimeException("Invalid claims");
    }
}


private RsaVerifier verifier(String kid) throws Exception {
    JwkProvider provider = new UrlJwkProvider(new URL(jwkUrl));
    Jwk jwk = provider.get(kid);
    return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
}

Вот конфигурация безопасности:

@Configuration
@EnableWebSecurity
public class OpenIdConnectWebServerConfig extends 
WebSecurityConfigurerAdapter {

@Bean
public OpenIdConnectFilter myFilter() {
   final OpenIdConnectFilter filter = new OpenIdConnectFilter();
   return filter;
 }

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.cors();
  http.antMatcher("/connect/**").authorizeRequests() 
  .antMatchers(HttpMethod.GET, "/connect/public").permitAll()
  .antMatchers(HttpMethod.GET, "/connect/private").authenticated()
  .antMatchers(HttpMethod.GET, "/connect/private- 
       messages").hasAuthority("read:messages")
  .antMatchers(HttpMethod.GET, "/connect/private- 
      roles").hasAuthority("read:roles")
  .and()
    .addFilterBefore(myFilter(), 
       UsernamePasswordAuthenticationFilter.class);
}

Конечные точки отдыха выглядят следующим образом:

  @RequestMapping(value = "/connect/public", method = RequestMethod.GET, 
     produces = "application/json")
  @ResponseBody
   public String publicEndpoint() throws JSONException {
    return new JSONObject()
          .put("message", "All good. You DO NOT need to be authenticated to 
         call /api/public.")
          .toString();
  }

   @RequestMapping(value = "/connect/private", method = RequestMethod.GET, 
      produces = "application/json")
     @ResponseBody
     public String privateEndpoint() throws JSONException {
      return new JSONObject()
          .put("message", "All good. You can see this because you are 
     Authenticated.")
          .toString();

}

Если я полностью удаляю фильтр для конфигурации, а также определение @Bean, конфигурация работает, как ожидается: / connect / public доступна, тогда как / connect / private запрещена.

Если я сохраню определение @Bean и добавлю его в цепочку фильтров, ответ вернет статус Not Found для запросов как в / connect / public, так и / connect / private:

"timestamp": "18.01.2019 09:46:11",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/

При отладке я заметил, что фильтр обрабатывает токен и возвращает реализацию Аутентификации.

  1. Правильно ли добавлен фильтр в цепочку фильтров и в правильном ли положении?
  2. Почему фильтр также вызывается по пути / connect / public, если он должен быть открытым. Применяется ли он ко всем путям, соответствующим вызову super ("/ connect / **")?

  3. Почему он возвращает путь как "/", когда запрос сделан в / connect / private

Кажется, что-то не так с фильтром, потому что каждый раз, когда он применяется, ответ портится.

...