1. Introduction
1.绪论
In this tutorial, we introduce AuthenticationManagerResolver and then show how to use it for Basic and OAuth2 authentication flows.
在本教程中,我们将介绍AuthenticationManagerResolver,然后展示如何将其用于Basic和OAuth2认证流。
2. What Is the AuthenticationManager?
2.什么是AuthenticationManager?
Simply put, the AuthenticationManager is the main strategy interface for authentication.
简单地说,AuthenticationManager是认证的主要策略接口。
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.
如果输入认证的委托人是有效的并且经过验证,AuthenticationManager#authenticate会返回一个Authentication实例,其authenticated标志被设置为true。否则,如果委托人无效,它将抛出一个AuthenticationException。对于最后一种情况,如果它不能决定,则返回null。
ProviderManager is the default implementation of AuthenticationManager. It delegates the authentication process to a list of AuthenticationProvider instances.
ProviderManager是AuthenticationManager的默认实现。它将认证过程委托给一个AuthenticationProvider实例列表。
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.
如果我们创建一个SecurityFilterChain bean,我们可以设置全局或局部AuthenticationManager。对于本地的AuthenticationManager,我们可以创建一个AuthenticationManagerbean,通过HttpSecurity访问AuthenticationManagerBuilder。
AuthenticationManagerBuilder is a helper class that eases the set up of UserDetailService, AuthenticationProvider, and other dependencies to build an AuthenticationManager.
AuthenticationManagerBuilder是一个辅助类,它可以简化UserDetailService、AuthenticationProvider和其他依赖项的设置,从而构建一个AuthenticationManager。
For a global AuthenticationManager, we should define an AuthenticationManager as a bean.
对于全局的AuthenticationManager,我们应该将AuthenticationManager定义为一个bean。
3. Why the AuthenticationManagerResolver?
3.为什么使用AuthenticationManagerResolver?
AuthenticationManagerResolver lets Spring select an AuthenticationManager per context. It’s a new feature added to Spring Security in version 5.2.0:
AuthenticationManagerResolver让Spring为每个上下文选择一个AuthenticationManager。这是在5.2.0版本中添加到Spring Security中的新功能。
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.
AuthenticationManagerResolver#resolve可以根据通用上下文返回AuthenticationManager的实例。换句话说,如果我们想根据一个类来解析AuthenticationManager,我们可以设置一个类作为上下文。
Spring Security has integrated the AuthenticationManagerResolver in the authentication flow with HttpServletRequest and ServerWebExchange as the context.
Spring Security在认证流程中集成了AuthenticationManagerResolver,将HttpServletRequest和ServerWebExchange作为上下文。
4. Usage Scenario
4.使用情况
Let’s see how to use AuthenticationManagerResolver in practice.
让我们看看如何在实践中使用AuthenticationManagerResolver。
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.
例如,假设一个系统有两组用户:雇员和客户。这两个组有特定的认证逻辑,并有独立的数据存储。此外,这两组中的任何一组的用户都只允许调用他们相关的URL。
5. How Does AuthenticationManagerResolver Work?
5.AuthenticationManagerResolver如何工作?
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.
我们可以在任何需要动态选择AuthenticationManagerResolver的地方使用AuthenticationManager,但在本教程中,我们对在内置认证流中使用它感兴趣。
First, let’s set up an AuthenticationManagerResolver, then use it for Basic and OAuth2 authentications.
首先,让我们设置一个AuthenticationManagerResolver,然后将其用于Basic和OAuth2认证。
5.1. Setting Up AuthenticationManagerResolver
5.1.设置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。
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:
雇员的AuthenticationManager在逻辑上是一样的,只是我们用isEmployee代替isCustomer。
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,根据请求的URL进行解析。
AuthenticationManagerResolver<HttpServletRequest> resolver() {
return request -> {
if (request.getPathInfo().startsWith("/employee")) {
return employeesAuthenticationManager();
}
return customersAuthenticationManager();
};
}
5.2. For Basic Authentication
5.2.对于基本认证
We can use AuthenticationFilter to dynamically resolve the AuthenticationManager per request. AuthenticationFilter was added to Spring Security in version 5.2.
我们可以使用AuthenticationFilter来动态地解决每个请求的AuthenticationManager。AuthenticationFilter在5.2版本中被添加到Spring Security。
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.
如果我们把它添加到我们的安全过滤器链中,那么对于每个匹配的请求,它首先检查是否可以提取任何认证对象。如果是,那么它就向AuthenticationManagerResolver询问一个合适的AuthenticationManager,然后继续流程。
First, let’s add a method in our CustomWebSecurityConfigurer to create an AuthenticationFilter:
首先,让我们在我们的CustomWebSecurityConfigurer中添加一个方法来创建一个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.
将AuthenticationFilter#successHandler设置为no-op SuccessHandler的原因是为了防止认证成功后的默认重定向行为。
Then, we can add this filter to our security filter chain by creating a SecurityFilterChain bean in our CustomWebSecurityConfigurer:
然后,我们可以通过在我们的CustomWebSecurityConfigurer中创建一个SecurityFilterChainbean,将这个过滤器添加到我们的安全过滤器链中。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(authenticationFilter(), BasicAuthenticationFilter.class);
return http.build();
}
5.3. For OAuth2 Authentication
5.3.对于OAuth2认证
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.
BearerTokenAuthenticationFilter负责OAuth2认证。BearerTokenAuthenticationFilter#doFilterInternal方法检查请求中是否有BearerTokenAuthenticationToken,如果有,则解析适当的AuthenticationManager来验证该令牌。
OAuth2ResourceServerConfigurer is used to set up BearerTokenAuthenticationFilter.
OAuth2ResourceServerConfigurer用于设置BearerTokenAuthenticationFilter.。
So, we can set up AuthenticationManagerResolver for our resource server in our CustomWebSecurityConfigurer by creating a SecurityFilterChain bean:
因此,我们可以在我们的CustomWebSecurityConfigurer中通过创建SecurityFilterChainbean为我们的资源服务器设置AuthenticationManagerResolver。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer()
.authenticationManagerResolver(resolver());
return http.build();
}
6. Resolve AuthenticationManager in Reactive Applications
6.解决反应式应用程序中的AuthenticationManager问题
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:
对于一个反应式Web应用程序,我们仍然可以从根据上下文解析AuthenticationManager的概念中受益。但在这里我们有ReactiveAuthenticationManagerResolver来代替。
@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.
它返回ReactiveAuthenticationManager的Mono。ReactiveAuthenticationManager相当于AuthenticationManager的反应式,因此其authenticate方法返回Mono。
6.1. Setting Up ReactiveAuthenticationManagerResolver
6.1.设置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。
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:
而后,我们将为员工定义ReactiveAuthenticationManager。
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。
ReactiveAuthenticationManagerResolver<ServerWebExchange> resolver() {
return exchange -> {
if (match(exchange.getRequest(), "/employee")) {
return Mono.just(employeesAuthenticationManager());
}
return Mono.just(customersAuthenticationManager());
};
}
6.2. For Basic Authentication
6.2.对于基本认证
In a reactive web application, we can use AuthenticationWebFilter for authentication. It authenticates the request and fills the security context.
在一个反应式Web应用程序中,我们可以使用AuthenticationWebFilter进行认证。它对请求进行认证并填充安全上下文。
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.
AuthenticationWebFilter首先检查该请求是否匹配。之后,如果请求中有一个认证对象,它将从ReactiveAuthenticationManagerResolver获得适合该请求的ReactiveAuthenticationManager并继续认证流程。
Hence, we can set up our customized AuthenticationWebFilter in our security configuration:
因此,我们可以在安全配置中设置我们的定制AuthenticationWebFilter。
@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.
首先,我们禁用ServerHttpSecurity#httpBasic以阻止正常的认证流程,然后用AuthenticationWebFilter手动替换它,传入我们的自定义解析器。
6.3. For OAuth2 Authentication
6.3.对于OAuth2认证
We can configure the ReactiveAuthenticationManagerResolver with ServerHttpSecurity#oauth2ResourceServer. ServerHttpSecurity#build adds an instance of AuthenticationWebFilter with our resolver to the chain of security filters.
我们可以通过ServerHttpSecurity#oauth2ResourceServer来配置ReactiveAuthenticationManagerResolver。ServerHttpSecurity#build将带有我们的解析器的AuthenticationWebFilter的实例添加到安全过滤器链中。
So, let’s set our AuthenticationManagerResolver for OAuth2 authentication filter in our security configuration:
因此,让我们在安全配置中为OAuth2认证过滤器设置我们的AuthenticationManagerResolver。
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
// ...
.and()
.oauth2ResourceServer()
.authenticationManagerResolver(resolver())
.and()
// ...;
}
7. Conclusion
7.结语
In this article, we’ve used AuthenticationManagerResolver for Basic and OAuth2 authentications within a simple scenario.
在这篇文章中,我们使用AuthenticationManagerResolver在一个简单的场景中进行Basic和OAuth2认证。
And, we’ve also explored the usage of ReactiveAuthenticationManagerResolver in reactive Spring web applications for both Basic and OAuth2 authentications.
而且,我们还探索了ReactiveAuthenticationManagerResolver在反应式Spring Web应用中对Basic和OAuth2认证的使用。
As always, the source code is available over on GitHub. Our reactive example is also available over on GitHub.