Describe the bug
By default, the AuthenticationManagerBuilderis autoconfigured with an AuthenticationProvider, if registered, or with a DaoAuthenticationProvider, if an UserDetailsService is registered. Both configurer back off if the AuthenticationManagerBuilder is already configured, i.e. if at least one AuthenticationProvider has been specified.
In particular, if a user registers their own AuthenticationProvider bean, it is added to the manager's providers list by InitializeAuthenticationProviderManagerConfigurer, and InitializeUserDetailsManagerConfigurer does nothing since it consider the manager already configured.
However, InitializeAuthenticationProviderManagerConfigurer only add the provider if there's just a single registered one. If multiple beans are registered, the configurer does nothing, but because the manager's providers list is still empty InitializeUserDetailsManagerConfigurer thinks that the manager was not yet configured, and creates the DaoAuthenticationProvider.
This is a problem if I want both a DaoAuthenticationProvider to handle UsernamePasswordAuthenticationToken and a second provider to handle SSO tokens. If I only register the second provider, SSO works but the DaoAuthenticationProvider is not present in the ProviderManager's providers list, so I cannot log in with username/password. If I also register a DaoAuthenticationProvider, the default one also will be created, possibly with a different configuration (e.g. a different PasswordEncoder).
The workaround I found is to register my SSO provider:
@Bean
public AuthenticationProvider ssoProvider(AuthenticationManagerBuilder auth) {
var provider = new MySsoProvider();
auth.authenticationProvider(provider);
return provider;
}
and a dummy one:
@Bean
public AuthenticationProvider dummyAuthProvider() {
return new AuthenticationProvider() {
public boolean supports(Class<?> authentication) {
return false;
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return null;
}
};
}
so I can use my SSO provider and the Dao provider configured by InitializeUserDetailsManagerConfigurer, but this is clearly not the intended way to do it.
To Reproduce
Register a UserDetailsService and multiple AuthenticationProviders in a @Configuration.
Expected behavior
InitializeUserDetailsManagerConfigurer should behave the same if either one or more than one AuthenticationProvider beans are registered.
Sample
spring-security-dao-configurer
Comment From: gbaso
Any progress on this?
Comment From: gbaso
Issue is still present in latest version 5.6.1
Comment From: eleftherias
Thanks for reaching out @gbaso.
The class that initializes the authentication based on the AuthenticationProvider beans is InitializeAuthenticationProviderBeanManagerConfigurer.
From the Javadoc:
Lazily initializes the global authentication with an AuthenticationProvider if it is not yet configured and there is only a single Bean of that type.
To register multiple AuthenticationProviders in a the same AuthenticationManager you can use HttpSecurity#authenticationProvider.
For example:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.httpBasic(basic -> {})
.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
.authenticationProvider(new FirstProvider())
.authenticationProvider(new SecondProvider())
.build();
}
Feel free to give this a try and let us know if you have any trouble.
Comment From: gbaso
Thanks @eleftherias, your suggested approach works fine.
However, it still feels weird to me that DaoAuthenticationProvider is autoconfigured when multiple beans of type AuthenticationProvider are registered.
The autoconfiguration for DaoAuthenticationProvider (correctly) backs off when a user register their own AuthenticationProvider, should it not do the same when there are multiple beans registered?
Or, iIf registering multiple AuthenticationProviders is not supported, maybe it could be better to throw an exception and advise the user on the correct approach.
What do you think?
Comment From: UtkarshBhavsar
I still have been facing the same issue with 5.7.3 version. I am implementing multiple securityfilterchain with different authentication provider. Do we have any update on this?
Comment From: RazvanSebastian
Also on new 6.x versions, the behavbiour is the same.
I did some debug and I found that how HttpSecurity bean is firstly initialized on HttpSecurityConfiguration .
The proccess is starting here where the authenticationBuilder.parentAuthenticationManager(authenticationManager()); is called and then method getAuthenticationManager from AuthenticationConfiguration is called. Baiscally this one is initialzing the AuthenticationManagerBuilder.
If you take a look closer here, there is a lit of 3 GlobalAuthenticationConfigurerAdapterobjects which are defined as beans and are used to initialize our AuthenticationManagerBuilder.
These 3 objects of type EnableGlobalAuthenticationAutowiredConfigurer, InitializeUserDetailsBeanManagerConfigurer, InitializeAuthenticationProviderBeanManagerConfigurerare used and
InitializeAuthenticationProviderBeanManagerConfigurer: this one is dealing with the initialization with AuthenticationProvider beans, BUT only if there is ONLY ONE bean (link here).
Since we have the situation where we have more than one AuthenticationProvider bean or none beans of this type, then InitializeUserDetailsBeanManagerConfigurer will be used and only that DaoAuthenticationProvider will be used (link here)
It seems that even if you register Java objects with authenticationProvider method, we are only setting the AuthenticationProviderBuilderand not AuthenticationManagerso for example
http..authenticationProvider(someProvider).addFilterBefore(authenticationFilter(authenticationManager)
we are receiving on filter the AuthenticationManagerwhere we have only DaoAuthenticationProvider
The solution for me is the following:
- Create a bean of type AuthenticationManager
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, AuthenticationProvider authenticationProvider1, AuthenticationProvider authenticationProvider2) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.authenticationProvider(authenticationProvider1);
authenticationManagerBuilder.authenticationProvider(authenticationProvider2);
return authenticationManagerBuilder.build();
}
- This bean will be used to set directly on HttpSecurity builder the AuthenticationManager.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authenticationManager) {
// Other building methods
http.authenticationManager(authenticationManager);
}