Home » Java » java – Spring security 5 api key filter-Exceptionshub

java – Spring security 5 api key filter-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

I’m trying to create a filter with spring security 5 and boot 2 that protects some but no all endpoints with an API Key and without sessions.
However after the filter authenticates it tires to redirect to “/” instead of the original url as SavedRequestAwareAuthenticationSuccessHandler has an empy request cache and falls back to the default “/”.

How can I make it to carry on to the targeted resource instead of ‘/’ and why it worked like this?

I’d also appreciate if someone could explain to me why AbstractPreAuthenticatedProcessingFilter is designed to carry on with the filter chain after a sucessfull authentication but AbstractAuthenticationProcessingFilter is not.

This is the security config class:

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${secret.admin.api.key}")
    String validApiKey;

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
        builder.authenticationProvider(new ApiKeyAuthenticationProvider(validApiKey));
    }

    @Override
    protected void configure(HttpSecurity security) throws Exception {
        security
            .csrf().disable()
            .sessionManagement()
                .sessionCreationPolicy(STATELESS)
                .and()
            .addFilterBefore(new ApiKeyAuthenticationFilter(authenticationManager()), AnonymousAuthenticationFilter.class)
            .requestMatchers()
                .antMatchers("/v1/admin/**")
                .antMatchers("/actuator/**")
                .and()
            .authorizeRequests().anyRequest().authenticated();
    }

}

My filter matches requests that have Authorization header with a custom value. Extracts the api key from the header and passes the token with the principal and credentials(api key) to the authentication manager.

public class ApiKeyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

//...

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException, IOException, ServletException {
        var apiKey = extractApiKey(request.getHeader(AUTHORIZATION));
        var token = new ApiKeyAuthenticationToken(apiKey);
        var authentication = getAuthenticationManager().authenticate(token);
        return authentication;
    }

//...
}

The following class validates the credentials and creates and authenticated token with a principal and without credentials:

public class ApiKeyAuthenticationProvider implements AuthenticationProvider {

// ...

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        var apiKey = (String) authentication.getCredentials();
        if (validApiKey.equals(apiKey)) {
            var auth = new ApiKeyAuthenticationToken();
            auth.setAuthenticated(true);
            return auth;
        } else {
            throw new BadCredentialsException("Bad ApiKey credentials");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return ApiKeyAuthenticationToken.class.isAssignableFrom(authentication);
    }

}

And the logs:

OrRequestMatcher: Trying to match using Ant [pattern='/v1/admin/**']
AntPathRequestMatcher: Checking match of request : '/v1/admin/namespace'; against '/v1/admin/**'
OrRequestMatcher: matched
FilterChainProxy$VirtualFilterChain: /v1/admin/namespace at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
FilterChainProxy$VirtualFilterChain: /v1/admin/namespace at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
FilterChainProxy$VirtualFilterChain: /v1/admin/namespace at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
FilterChainProxy$VirtualFilterChain: /v1/admin/namespace at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
[...]
FilterChainProxy$VirtualFilterChain: /v1/admin/namespace at position 5 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
FilterChainProxy$VirtualFilterChain: /v1/admin/namespace at position 6 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
FilterChainProxy$VirtualFilterChain: /v1/admin/namespace at position 7 of 11 in additional filter chain; firing Filter: 'ApiKeyAuthenticationFilter'
ProviderManager: Authentication attempt using ....security.ApiKeyAuthenticationProvider
AbstractAuthenticationTargetUrlRequestHandler: Using default Url: /
DefaultRedirectStrategy: Redirecting to '/'

I tried to make this work based on this.

How to&Answers:

I had to override the successfulAuthentication method in the filter and continue the chain to make it work.

@Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
        FilterChain chain, Authentication authResult) throws IOException, ServletException {

        log.info("Successful authentication");
        SecurityContextHolder.getContext().setAuthentication(authResult);
        chain.doFilter(request,response);
    }