1. Introduction
In this tutorial, we introduce AuthenticationManagerResolver and then show how to use it for Basic and OAuth2 authentication flows.
2. What is the AuthenticationManager?
Simply put, the AuthenticationManager is the main strategy interface for authentication.
If the principal of the input authentication is valid and verified, AuthenticationManager#authenticate returns an Authentication instance with the authenticated flag set to true. Otherwise, if the principal is not valid, it will throw an AuthenticationException. For the last case, it returns null if it can't decide.
ProviderManager is the default implementation of AuthenticationManager. It delegates the authentication process to a list of AuthenticationProvider instances.
We can set up global or local AuthenticationManager if we extend WebSecurityConfigurerAdapter. For a local AuthenticationManager, we could override configure(AuthenticationManagerBuilder).
AuthenticationManagerBuilder is a helper class that eases the set up of UserDetailService, AuthenticationProvider, and other dependencies to build an AuthenticationManager.
For a global AuthenticationManager, we should define an AuthenticationManager as a bean.
3. Why the AuthenticationManagerResolver?
AuthenticationManagerResolver lets Spring select an AuthenticationManager per context. It's a new feature added to Spring Security in version 5.2.0:
public interface AuthenticationManagerResolver<C> { AuthenticationManager resolve(C context); }
AuthenticationManagerResolver#resolve can return an instance of AuthenticationManager based on a generic context. In other words, we can set a class as the context if we want to resolve the AuthenticationManager according to it.
Spring Security has integrated the AuthenticationManagerResolver in the authentication flow with HttpServletRequest and ServerHttpRequest as the context.
4. Usage Scenario
Let's see how to use AuthenticationManagerResolver in practice.
For example, assume a system that has two groups of users: employees and customers. These two groups have specific authentication logic and have separate datastores. Moreover, users in either of these groups are only allowed to call their related URLs.
5. How Does AuthenticationManagerResolver Work?
We can use AuthenticationManagerResolver wherever we need to choose an AuthenticationManager dynamically, but in this tutorial, we're interested in using it in built-in authentication flows.
First, let's set up an AuthenticationManagerResolver, then use it for Basic and OAuth2 authentications.
5.1. Setting Up AuthenticationManagerResolver
Let's start by creating a class for security configuration. We should extend WebSecurityConfigurerAdapter:
@Configuration public class CustomWebSecurityConfigurer extends WebSecurityConfigurerAdapter { // ... }
Then, let's add a method that returns the AuthenticationManager for customers:
AuthenticationManager customersAuthenticationManager() { return authentication -> { if (isCustomer(authentication)) { return new UsernamePasswordAuthenticationToken(/*credentials*/); } throw new UsernameNotFoundException(/*principal name*/); }; }
The AuthenticationManager for employees is logically the same, only we replace isCustomer with isEmployee:
public AuthenticationManager employeesAuthenticationManager() { return authentication -> { if (isEmployee(authentication)) { return new UsernamePasswordAuthenticationToken(/*credentials*/); } throw new UsernameNotFoundException(/*principal name*/); }; }
Finally, let's add an AuthenticationManagerResolver that resolves according to the URL of request:
AuthenticationManagerResolver<HttpServletRequest> resolver() { return request -> { if (request.getPathInfo().startsWith("/employee")) { return employeesAuthenticationManager(); } return customersAuthenticationManager(); }; }
5.2. For Basic Authentication
We can use AuthenticationFilter to dynamically resolve the AuthenticationManager per request. AuthenticationFilter was added to Spring Security in version 5.2.
If we add it to our security filter chain, then for every matched request, it first checks if it can extract any authentication object or not. If yes, then it asks the AuthenticationManagerResolver for a suitable AuthenticationManager and continues the flow.
First, let's add a method in our CustomWebSecurityConfigurer to create an AuthenticationFilter:
private AuthenticationFilter authenticationFilter() { AuthenticationFilter filter = new AuthenticationFilter( resolver(), authenticationConverter()); filter.setSuccessHandler((request, response, auth) -> {}); return filter; }
The reason for setting the AuthenticationFilter#successHandler with a no-op SuccessHandler is to prevent the default behavior of redirection after successful authentication.
Then, we can add this filter to our security filter chain by overriding WebSecurityConfigurerAdapter#configure(HttpSecurity) in our CustomWebSecurityConfigurer:
@Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore( authenticationFilter(), BasicAuthenticationFilter.class); }
5.3. For OAuth2 Authentication
BearerTokenAuthenticationFilter is responsible for OAuth2 authentication. The BearerTokenAuthenticationFilter#doFilterInternal method checks for a BearerTokenAuthenticationToken in the request, and if it's available, then it resolves the appropriate AuthenticationManager to authenticate the token.
OAuth2ResourceServerConfigurer is used to set up BearerTokenAuthenticationFilter.
So, we can set up AuthenticationManagerResolver for our resource server in our CustomWebSecurityConfigurer by overriding WebSecurityConfigurerAdapter#configure(HttpSecurity):
@Override protected void configure(HttpSecurity http) throws Exception { http .oauth2ResourceServer() .authenticationManagerResolver(resolver()); }
6. Resolve AuthenticationManager in Reactive Applications
For a reactive web application, we still can benefit from the concept of resolving AuthenticationManager according to the context. But here we have ReactiveAuthenticationManagerResolver instead:
@FunctionalInterface public interface ReactiveAuthenticationManagerResolver<C> { Mono<ReactiveAuthenticationManager> resolve(C context); }
It returns a Mono of ReactiveAuthenticationManager. ReactiveAuthenticationManager is the reactive equivalent to AuthenticationManager, hence its authenticate method returns Mono.
6.1. Setting Up ReactiveAuthenticationManagerResolver
Let's start by creating a class for security configuration:
@EnableWebFluxSecurity @EnableReactiveMethodSecurity public class CustomWebSecurityConfig { // ... }
Next, let's define ReactiveAuthenticationManager for customers in this class:
ReactiveAuthenticationManager customersAuthenticationManager() { return authentication -> customer(authentication) .switchIfEmpty(Mono.error(new UsernameNotFoundException(/*principal name*/))) .map(b -> new UsernamePasswordAuthenticationToken(/*credentials*/)); }
And after that, we'll define ReactiveAuthenticationManager for employees:
public ReactiveAuthenticationManager employeesAuthenticationManager() { return authentication -> employee(authentication) .switchIfEmpty(Mono.error(new UsernameNotFoundException(/*principal name*/))) .map(b -> new UsernamePasswordAuthenticationToken(/*credentials*/)); }
Lastly, we set up a ReactiveAuthenticationManagerResolver based on our scenario:
ReactiveAuthenticationManagerResolver<ServerHttpRequest> resolver() { return request -> { if (match(request, "/employee")) { return Mono.just(employeesAuthenticationManager()); } return Mono.just(customersAuthenticationManager()); }; }
6.2. For Basic Authentication
In a reactive web application, we can use AuthenticationWebFilter for authentication. It authenticates the request and fills the security context.
AuthenticationWebFilter first checks if the request matches. After that, if there's an authentication object in the request, it gets the suitable ReactiveAuthenticationManager for the request from ReactiveAuthenticationManagerResolver and continues the authentication flow.
Hence, we can set up our customized AuthenticationWebFilter in our security configuration:
@Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http .authorizeExchange() .pathMatchers("/**") .authenticated() .and() .httpBasic() .disable() .addFilterAfter( new AuthenticationWebFilter(resolver()), SecurityWebFiltersOrder.REACTOR_CONTEXT ) .build(); }
First, we disable ServerHttpSecurity#httpBasic to prevent the normal authentication flow, then manually replace it with an AuthenticationWebFilter, passing in our custom resolver.
6.3. For OAuth2 Authentication
We can configure the ReactiveAuthenticationManagerResolver with ServerHttpSecurity#oauth2ResourceServer. ServerHttpSecurity#build adds an instance of AuthenticationWebFilter with our resolver to the chain of security filters.
So, let's set our AuthenticationManagerResolver for OAuth2 authentication filter in our security configuration:
@Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http // ... .and() .oauth2ResourceServer() .authenticationManagerResolver(resolver()) .and() // ...; }
7. Conclusion
In this article, we've used AuthenticationManagerResolver for Basic and OAuth2 authentications within a simple scenario.
And, we've also explored the usage of ReactiveAuthenticationManagerResolver in reactive Spring web applications for both Basic and OAuth2 authentications.
As always, the source code is available over on GitHub. Our reactive example is also available over on GitHub.