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 AuthenticationEntryPoint
s 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();
}