Home > OS >  How to set authentication failure handler on BearerTokenAuthenticationFilter?
How to set authentication failure handler on BearerTokenAuthenticationFilter?

Time:07-14

I am able to create the filter chain with http.oauth2ResourceServer().jwt() and I've also set spring.security.oauth2.resourceserver.jwt.issuer-uri. It is able to authenticate requests. However, I also need to do custom logging in the case of an authentication failure. The approach I'm taking is to use a custom authentication entry point to handle when no bearer token is present, combined with a custom BearerTokenAuthenticationFilter.authenticationFailureHandler to handle an invalid token. I'm open to other approaches to satisfy this goal.

I am able to configure a custom authentication entry point to handle the case where no token is present:

// in WebSecurityConfigurerAdapter::configure
http
    .exceptionHandling()
    .authenticationEntryPoint((request, response, exception) -> { /* ... */ });

However I haven't found a way to access the BearerTokenAuthenticationFilter. The best I've been able to come up with is to new up a second configured the way I want it, but this is not appealing to me because the server ends up doing extra work with every successfully authenticated request:

// in WebSecurityConfigurerAdapter::configure
var filter = new BearerTokenAuthenticationFilter(authenticationManagerBean());
filter.setAuthenticationFailureHandler(new JwtAuthenticationFailureHandler());
http.addFilterBefore(tokenAuth, BearerTokenAuthenticationFilter.class);
// my filter runs first

Surely there is some way to set this property in the filter that spring security creates? Ideally it would be exposed by OAuth2ResourceServerConfigurer, but that only offers accessDeniedHandler.

I've tried accessing either the filter itself or the DefaultSecurityFilterChain as a bean, but they don't exist as beans in the application context. I found this answer which suggests configuring a bean in spring-servlet.xml and running it through a post processor. The idea of a BeanPostProcessor seems promising to me however I wasn't able to get it to work, because after modifying spring-servlet.xml as suggested the bean still doesn't exist. I can't use getBean to find it and it's not seen by the BeanPostProcessor:

<http name="filterChain">

CodePudding user response:

The two cases you're looking for can be handled with a combination of AuthenticationEntryPoints applied at different levels, one for the BearerTokenAuthenticationFilter and a second for the filter chain. For example:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated()
        )
        .oauth2ResourceServer((oauth2) -> oauth2
            .jwt(Customizer.withDefaults())
            .authenticationEntryPoint((request, response, exception) -> {
                System.out.println("Authentication failed");
                BearerTokenAuthenticationEntryPoint delegate = new BearerTokenAuthenticationEntryPoint();
                delegate.commence(request, response, exception);
            })
        )
        .exceptionHandling((exceptions) -> exceptions
            .authenticationEntryPoint((request, response, exception) -> {
                System.out.println("Authentication is required");
                BearerTokenAuthenticationEntryPoint delegate = new BearerTokenAuthenticationEntryPoint();
                delegate.commence(request, response, exception);
            })
        );

    return http.build();
}

The reason it works this way is that the BearerTokenAuthenticationFilter is not invoked when no bearer token is present. In that case, the entire filter chain is tried and no valid authentication is found. This would be the normal "Authentication is required" scenario for Spring Security.

However, in the case of an present but invalid token, the BearerTokenAuthenticationFilter needs to validate the token to determine that it failed, and uses its own AuthenticationFailureHandler for this case. But the failure handler simply delegates to a specific AuthenticationEntryPoint by default, which is the one configured above for the "Authentication failed" case.

If you want to override the failure handler to do something else (though you may not have to after trying the above), you can do so with an ObjectPostProcessor. For example:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .oauth2ResourceServer((oauth2) -> oauth2
            .jwt(Customizer.withDefaults())
            .withObjectPostProcessor(new ObjectPostProcessor<BearerTokenAuthenticationFilter>() {
                @Override
                public <O extends BearerTokenAuthenticationFilter> O postProcess(O filter) {
                    filter.setAuthenticationFailureHandler((request, response, exception) -> {
                        System.out.println("Authentication failed (and is being handled in a custom way)");
                        BearerTokenAuthenticationEntryPoint delegate = new BearerTokenAuthenticationEntryPoint();
                        delegate.commence(request, response, exception);
                    });
                    return filter;
                }
            })
        );

    return http.build();
}
  • Related