If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:
>> LEARN SPRING SECURITYMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
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 create a SecurityFilterChain bean. For a local AuthenticationManager, we could create an AuthenticationManager bean, accessing AuthenticationManagerBuilder through HttpSecurity.
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 ServerWebExchange 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.
@Configuration
public class CustomWebSecurityConfigurer {
// ...
}
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 creating a SecurityFilterChain bean in our CustomWebSecurityConfigurer:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(authenticationFilter(), BasicAuthenticationFilter.class);
return http.build();
}
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 creating a SecurityFilterChain bean:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer()
.authenticationManagerResolver(resolver());
return http.build();
}
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<ServerWebExchange> resolver() {
return exchange -> {
if (match(exchange.getRequest(), "/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
.csrf(csrfSpec -> csrfSpec.disable())
.authorizeExchange(auth -> auth.pathMatchers(HttpMethod.GET,"/**")
.authenticated())
.httpBasic(httpBasicSpec -> httpBasicSpec.disable())
.addFilterAfter(authenticationWebFilter(), 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.
