Describe the bug
Following the deprecation of WebSecurityConfigurerAdapter, there is a new recommended way of configuring a filter chain. The event publishing are slightly different between the two configuration flavors.
In both cases, the global authentication manager has uses an AuthenticationEventPublisher defined as a been, e.g. DefaultAuthenticationEventPublisher provided by Boot. However, behavior is different for HttpSecurity's local auth manager. With WebSecurityConfigurerAdapter, the local auth manager uses the same event publisher. With @Bean SecurityFilterChain ...(), the local auth manager uses a NullEventPublisher instead.
To Reproduce
See the sample for more details:
With Adapter:
- Configure spring security through WebSecurityConfigurerAdapter, and include a custom authentication provider (or use oauth2Login, saml2Login, etc)
- Log in using that custom provider (or perform OAuth2 login, etc)
- Observe that
AuthenticationSuccessEventare published
@Configuration
public class Adapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authenticationProvider(new FooUserAuthenticationProvider())
.authorizeHttpRequests(req -> req.anyRequest().authenticated())
.httpBasic(withDefaults());
}
}
With Bean:
- Configure spring security through WebSecurityConfigurerAdapter, and include a custom authentication provider (or use oauth2Login, saml2Login, etc)
- Log in using that custom provider (or perform OAuth2 login, etc)
- Observe that
AuthenticationSuccessEventare not published
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authenticationProvider(new FooUserAuthenticationProvider())
.authorizeHttpRequests(req -> req.anyRequest().authenticated())
.httpBasic(withDefaults())
.build();
}
Expected behavior
Consistent behavior between WebSecurityConfigurerAdapter and bean-provided security filter chains.
Workaround
I inject the event publisher in the auth manager builder, as workaround to get consistent behavior. This avoids having to use the config DSL just to access the auth manager:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationEventPublisher publisher)
throws Exception {
http.getSharedObject(AuthenticationManagerBuilder.class).authenticationEventPublisher(publisher);
return http
// ...
.build();
}
Sample
Minimal repro repository, with instructions in the README and unit tests showing the differences.
Comment From: sjohnr
Hi @Kehrlann, thanks for the report and very helpful minimal sample!
I wonder if we have multiple related things going on here. (I say "I wonder" because I haven't dug too deeply yet!)
With @Bean SecurityFilterChain ...(), the local auth manager uses a NullEventPublisher instead.
Do you know if the "local auth manager" is the same as the one referred to in this comment, which is the AuthenticationManager built by the AuthenticationManagerBuilder in the shared objects cache?
Comment From: Kehrlann
@sjohnr Yes I believe that is correct. If I understand correctly, in the @Bean case, it is the AuthenticationManager that comes from the HttpSecurity implementation through the use of an AuthenticationManagerBuilder in the shared objects (source code)[1] , hence the workaround using the shared object cache.
In the case of the WebSecurityConfigurerAdapter, it's built by the adapter instead (source code) and, and that manager gets an event publisher attached.
[1] I picked up "local auth manager" in the Spring Security without the WebSecurityConfigurerAdapter article.
Comment From: sjohnr
Ok, thanks for clarifying what you meant by "local" @Kehrlann, and I agree that usage makes sense. Actually, I believe the http.getSharedObject(AuthenticationManager.class) and http.getSharedObject(AuthenticationManagerBuilder.class) aren't the same (e.g. that builder isn't used to produce that instance of AuthenticationManager in shared objects). I'll have to do some more research.
Comment From: Kehrlann
@sjohnr In HttpSecurity, unless you specify an authentication manager directly (by using HttpSecurity#authenticationManager(...)), the manager-builder is used to produce the auth manager, see HttpSecurity#beforeConfigure(...) (source)
Comment From: sjohnr
Ah, I see! When I created a custom DSL, that auth manager builder only seemed to have the anonymous provider in it 🤷. Either way, I will have to dig into this more. Thanks!
Comment From: sjohnr
It looks like this is a duplicate of gh-10552, so I'm going to move the conversation over there. I'll link to the sample and the original comment of this issue because the context provided by @Kehrlann is extremely helpful. Thanks @Kehrlann!