AbstractAuthenticationProcessingFilter срабатывает независимо от того, в какую цепочку SecurityFilter он входит

#spring-boot #spring-mvc #spring-security

#spring-boot #spring-mvc #spring-безопасность

Вопрос:

Я экспериментирую с Spring security и столкнулся со странным поведением. Моя идея — создать фильтр безопасности, который проверяет подлинность запросов на основе токенов JWT (или JWS):

 public class JWTokenFilter extends AbstractAuthenticationProcessingFilter {

    public JWTokenFilter(AuthenticationManager authenticationManager) {
        super("/**"); //doesn't have any effect, every request still gets considered by this filter
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        String token = request.getHeader("Authorization");
        if (!StringUtils.hasText(token)) {
            throw new TokenException("Token is empty");
        }
        var authentication = determineAuthentication(token.replace("Bearer","").trim());
        //the AbstractAuthenticationProcessingFilter fills the Security context
        return this.getAuthenticationManager().authenticate(authentication);
    }

    @Override
    protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("Asked for " request.getRequestURI());
        return request.getHeader("Authorization") != null;
    }


    private TokenAuthentication<UserInfo> determineAuthentication(String token) {
        var split = token.split("\.");
        if (split.length < 2 || split.length > 3) {
            throw new TokenException("Token malformed");
        }
        if (split.length == 2){
            return new JWTAuthentication<>(token);
        }else {
            return new JWSAuthentication<>(token);
        }
    }

}
 

У меня есть 3 @RestController класса, для которых сопоставлены их пути:

  1. @RequestMapping("/admin")
  2. @RequestMapping("/all")
  3. @RequestMapping("/anon")

Наряду с этим у меня есть следующая конфигурация безопасности:

 @Configuration
@Order(98)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers()
                .antMatchers("/all/**","/anon/**")
                .and()
                .authorizeRequests().antMatchers("/all/**").permitAll()
                .and()
                .authorizeRequests().antMatchers("/anon/**").anonymous();
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().mvcMatchers("/webjars/**", "/css/**");
    }



    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Configuration
    @Order(99)
    public static class TokenSecurityConfig extends WebSecurityConfigurerAdapter{

        @Lazy
        @Autowired
        private JWTokenFilter tokenFilter;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests() //having /admin/** or /** makes no difference
                    .anyRequest().authenticated()
                    .and().addFilterBefore(tokenFilter,ExceptionTranslationFilter.class);//put this filter near the end of the chain
        }

        @Bean
        public JWTokenFilter tokenFilter(JWTokenAuthenticationProvider jwTokenAuthenticationProvider,JWSTokenAuthenticationProvider jwsTokenAuthenticationProvider){
            var list = new ArrayList<AuthenticationProvider>();
            list.add(jwsTokenAuthenticationProvider);
            list.add(jwTokenAuthenticationProvider);
            ProviderManager manager = new ProviderManager(list);
            return new JWTokenFilter(manager);
        }
    }
}
 

Из этой конфигурации здесь мы видим, что существует 2 SecurityFilterChan s (не считая /webjars единиц и /css ):

  1. Это соответствует всем запросам "/all/**" и "/anon/**" маршрутам REST
  2. Это соответствует любому запросу

Поскольку 1. chain имеет значение ниже @Order(98) , чем 2. @Order(99) , это означает, что 1. chain будет рассмотрен первым, что показано отладчиком, введите описание изображения здесь

и сопоставляется, если входящий запрос выглядит следующим образом:

 curl --request GET 
  --url http://localhost:8080/all/hello 
 

Теперь я испытываю то, что JWTokenFilter метод boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) всегда вызывается независимо от пути запроса!
И в выводе на консоль я могу найти Asked for /all/hello .

Редактировать: моя версия spring boot 2.3.6.RELEASE


Мой вопрос:

Почему JWTokenFIlter даже спрашивается, должен ли он проверять подлинность запросов с путями, которые не совпадают с SecurityFilterChain путями, частью которых он является?

Ответ №1:

Я полагаю, что у меня есть лучший ответ, но я хотел бы также ответить на ваш первоначальный вопрос. Я разделил это на две части.

Улучшенный ответ

Я понимаю, что это не отвечает на первоначальный вопрос, но я думаю, вам может быть лучше использовать встроенную поддержку аутентификации на основе JWT. Я бы проверил раздел сервера ресурсов OAuth 2.0 справочной документации.

Ответ на Первоначальный вопрос

Spring Boot автоматически зарегистрирует любой Filter открытый как a @Bean для каждого запроса непосредственно в контейнере сервлета.

У вас есть два варианта, которые я вижу. Во — первых , следует избегать разоблачения JwtTokenFilter как @Bean такового .

 @Configuration
@Order(99)
public static class TokenSecurityConfig extends WebSecurityConfigurerAdapter{
    @Autowired
    JWTokenAuthenticationProvider jwTokenAuthenticationProvider;

    @Autowired
    JWSTokenAuthenticationProvider jwsTokenAuthenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests() //having /admin/** or /** makes no difference
                .anyRequest().authenticated()
                .and().addFilterBefore(tokenFilter(),ExceptionTranslationFilter.class);//put this filter near the end of the chain
    }

    
    public JWTokenFilter tokenFilter(){
        var list = new ArrayList<AuthenticationProvider>();
        list.add(jwsTokenAuthenticationProvider);
        list.add(jwTokenAuthenticationProvider);
        ProviderManager manager = new ProviderManager(list);
        return new JWTokenFilter(manager);
    }
}
 

В качестве альтернативы вы можете продолжить показ JwtTokenFilter как a @Bean и создать a, FilterRegistrationBean который отключает регистрацию.

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