Spring-Security управляет двумя AuthenticationFilters на основе заголовков - PullRequest
0 голосов
/ 11 марта 2019

В моем приложении весенней загрузки, которое является базовым API для отдыха, мне нужно управлять двумя видами аутентификации:

  • Один на основе токена, который мне нужно проверить и извлечь userId, когда токен присутствует в заголовке http X-API-TOKEN
  • Другой основан на BasicAuth, когда я получаю комбинацию заголовка "Authorization = Basic xxx" и заголовка X-API-USER-ID

Я добиваюсь реализации каждого варианта использования отдельно, но я не могу с этими двумя фильтрами настроить так. Я использую аннотацию Pre / PostAuthorize на моих контроллерах, потому что некоторые методы ограничены, а другие разрешены всем без токена или basicAuth + uid

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

@Configuration
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userConnector;

    @Autowired
    private TokenEncoder tokenEncoder;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests().antMatchers("/").permitAll().and()
                .authorizeRequests().antMatchers("/console/**").permitAll();

        httpSecurity.csrf().disable();
        httpSecurity.headers().frameOptions().disable();

        httpSecurity.addFilterAfter(basicAuthFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(tokenFilter(), BasicAuthenticationFilter.class)
                .exceptionHandling()
                .authenticationEntryPoint(myExceptionHandler())
                .accessDeniedHandler(myExceptionHandler());
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider
                = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        return authProvider;
    }

    @Bean
    public BasicAuthorizationFilter basicAuthFilter() {
        return new BasicAuthorizationFilter();
    }

    @Bean
    public MyTokenAuthorizationFilter tokenFilter() {
        return new MyTokenAuthorizationFilter(tokenEncoder);
    }

    @Bean
    public MyExceptionHandler myExceptionHandler() {
        return new MyExceptionHandler();
    }

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        return userConnector;
    }
}

TokenFilter:

public class MyTokenAuthorizationFilter extends OncePerRequestFilter {

    private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    private TokenEncoder tokenEncoder;

    private RememberMeServices rememberMeServices = new NullRememberMeServices();


    public MyTokenAuthorizationFilter(TokenEncoder tokenEncoder) {
        super();
        this.tokenEncoder = tokenEncoder;
    }

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        if(request.getHeader("X-API-TOKEN") == null) {
            filterChain.doFilter(request, response);
        } else {
            String uid = tokenEncoder.isValid(request.getHeader("X-API-TOKEN"));
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uid, "");
            token.setDetails(authenticationDetailsSource.buildDetails(request));
            Authentication authResult = authenticationManager.authenticate(token);
            SecurityContextHolder.getContext().setAuthentication(token);
            rememberMeServices.loginSuccess(request, response, authResult);
        }
        filterChain.doFilter(request, response);
    }


    @Override
    public void afterPropertiesSet() {
        Assert.notNull(authenticationManager,
                "An AuthenticationManager is required");
        Assert.notNull(this.authenticationEntryPoint,
                "An AuthenticationEntryPoint is required");
    }
}

И другой фильтр:

public class BasicAuthorizationFilter extends OncePerRequestFilter {

    private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    private RememberMeServices rememberMeServices = new NullRememberMeServices();

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        if(request.getHeader("X-API-USER-ID") == null ||
                request.getHeader("Authorization") == null) {
            filterChain.doFilter(request, response);
        }

        final String uid = request.getHeader("X-API-USER-ID");
        final String basicAuthorization = request.getHeader("Authorization");

        if (basicAuthorization.toLowerCase().startsWith("basic")) {
            // TODO : something with username/password

            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uid, "");
            token.setDetails(authenticationDetailsSource.buildDetails(request));
            Authentication authResult = authenticationManager.authenticate(token);
            SecurityContextHolder.getContext().setAuthentication(token);
            rememberMeServices.loginSuccess(request, response, authResult);
        }
        filterChain.doFilter(request, response);
    }


    @Override
    public void afterPropertiesSet() {
        Assert.notNull(authenticationManager,
                "An AuthenticationManager is required");
        Assert.notNull(this.authenticationEntryPoint,
                "An AuthenticationEntryPoint is required");
    }
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...