1. 簡介
在本教程中,我們將介紹 AuthenticationManagerResolver,並演示如何使用它進行 Basic 和 OAuth2 身份驗證流程。
2. 什麼是 AuthenticationManager ?
簡單來説,AuthenticationManager 是認證的主要策略接口。
如果輸入認證的 principal 有效且已驗證,AuthenticationManager#authenticate 將返回一個 Authentication 實例,其中 authenticated 標誌設置為 true。 否則,如果 principal 無效,它將拋出 AuthenticationException。 對於最後一種情況,如果無法確定,它將返回 null。
ProviderManager 是 AuthenticationManager 的默認實現。 它將認證過程委託給一個 AuthenticationProvider 實例列表。
當創建 SecurityFilterChain bean 時,我們可以設置全局或本地 AuthenticationManager。 對於本地 AuthenticationManager,我們可以創建一個 AuthenticationManager bean,通過 HttpSecurity 訪問 AuthenticationManagerBuilder 。
AuthenticationManagerBuilder 是一個輔助類,可以簡化 UserDetailService、AuthenticationProvider 以及其他依賴項的設置,從而構建 AuthenticationManager。
對於全局 AuthenticationManager,我們應該將 AuthenticationManager 定義為一個 bean。
3. 為什麼使用 AuthenticationManagerResolver?
AuthenticationManagerResolver 允許 Spring 根據上下文選擇特定的 AuthenticationManager。這是一個在 Spring Security 5.2.0 版本中新增的功能:
public interface AuthenticationManagerResolver<C> {
AuthenticationManager resolve(C context);
}
AuthenticationManagerResolver#resolve 可以根據泛型上下文返回一個 AuthenticationManager 實例。換句話説,我們可以將一個類設置為上下文,以便根據它來解決 AuthenticationManager。
Spring Security 已將 AuthenticationManagerResolver 集成到認證流程中,使用 HttpServletRequest 和 ServerWebExchange 作為上下文。
4. 場景使用
讓我們看看如何在實踐中運用 AuthenticationManagerResolver。
例如,假設一個系統擁有兩個用户組:員工和客户。這兩個用户組具有特定的身份驗證邏輯,並且擁有獨立的數據庫。此外,這兩個用户組中的用户只能調用其相關的 URL。
5. 如何工作 AuthenticationManagerResolver?
我們可以使用 AuthenticationManagerResolver,只要我們需要動態地選擇 AuthenticationManager,但在此教程中,我們對使用它在內置身份驗證流程中感興趣。
首先,讓我們設置一個 AuthenticationManagerResolver,然後使用它進行基本和 OAuth2 身份驗證。
5.1. 設置 AuthenticationManagerResolver
讓我們從創建一個用於安全配置的類開始。
@Configuration
public class CustomWebSecurityConfigurer {
// ...
}
然後,讓我們添加一個返回 AuthenticationManager 的方法,用於客户:
AuthenticationManager customersAuthenticationManager() {
return authentication -> {
if (isCustomer(authentication)) {
return new UsernamePasswordAuthenticationToken(/*credentials*/);
}
throw new UsernameNotFoundException(/*principal name*/);
};
}
員工的 AuthenticationManager 在邏輯上與員工相同,我們只是用 AuthenticationManagerResolver 替代 AuthenticationManagerResolver:
public AuthenticationManager employeesAuthenticationManager() {
return authentication -> {
if (isEmployee(authentication)) {
return new UsernamePasswordAuthenticationToken(/*credentials*/);
}
throw new UsernameNotFoundException(/*principal name*/);
};
}
最後,讓我們添加一個 AuthenticationManagerResolver,它根據請求的 URL 進行解析:
AuthenticationManagerResolver<HttpServletRequest> resolver() {
return request -> {
if (request.getPathInfo().startsWith("/employee")) {
return employeesAuthenticationManager();
}
return customersAuthenticationManager();
};
}
5.2. 用於基本身份驗證
我們可以使用 AuthenticationFilter 動態地解決 AuthenticationManager。 AuthenticationFilter 在 Spring Security 5.2 中添加。
如果我們將其添加到安全過濾器鏈中,那麼對於匹配的請求,它首先檢查是否可以提取任何身份驗證對象,如果存在,則它會向 AuthenticationManagerResolver 請求合適的 AuthenticationManager 並繼續流程。
首先,讓我們在 CustomWebSecurityConfigurer 中創建一個方法來創建 AuthenticationFilter:
private AuthenticationFilter authenticationFilter() {
AuthenticationFilter filter = new AuthenticationFilter(
resolver(), authenticationConverter());
filter.setSuccessHandler((request, response, auth) -> {});
return filter;
}
原因是將 AuthenticationFilter 的 SuccessHandler 設置為無操作,以防止在成功身份驗證後重定向的默認行為。
然後,我們可以通過在 CustomWebSecurityConfigurer 中創建一個 SecurityFilterChain bean,將此過濾器添加到安全過濾器鏈中:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(authenticationFilter(), BasicAuthenticationFilter.class);
return http.build();
}
5.3. 用於 OAuth2 身份驗證
BearerTokenAuthenticationFilter 負責 OAuth2 身份驗證。 doFilterInternal 方法檢查請求中是否存在 BearerTokenAuthenticationToken,如果存在,則它會解析適當的 AuthenticationManager 來驗證令牌。
OAuth2ResourceServerConfigurer 用於設置 BearerTokenAuthenticationFilter。
因此,我們可以通過在 CustomWebSecurityConfigurer 中創建一個 SecurityFilterChain bean,為資源服務器設置 AuthenticationManagerResolver:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer()
.authenticationManagerResolver(resolver());
return http.build();
}
6. Resolve AuthenticationManager in Reactive Applications
對於一個反應式 Web 應用程序,我們仍然可以從上下文層面解決 AuthenticationManager 的概念。但是這裏我們有 ReactiveAuthenticationManagerResolver 代替:
@FunctionalInterface
public interface ReactiveAuthenticationManagerResolver<C> {
Mono<ReactiveAuthenticationManager> resolve(C context);
}
它返回一個 Mono 的 ReactiveAuthenticationManager。 ReactiveAuthenticationManager 是 AuthenticationManager 的反應式等價物,因此它的 authenticate 方法返回 Mono.
6.1. Setting Up ReactiveAuthenticationManagerResolver
讓我們首先創建一個用於安全配置的類:
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class CustomWebSecurityConfig {
// ...
}
接下來,讓我們在類中定義 ReactiveAuthenticationManager 用於客户:
ReactiveAuthenticationManager customersAuthenticationManager() {
return authentication -> customer(authentication)
.switchIfEmpty(Mono.error(new UsernameNotFoundException(/*principal name*/)))
.map(b -> new UsernamePasswordAuthenticationToken(/*credentials*/));
}
然後,我們定義用於員工的 ReactiveAuthenticationManager:
public ReactiveAuthenticationManager employeesAuthenticationManager() {
return authentication -> employee(authentication)
.switchIfEmpty(Mono.error(new UsernameNotFoundException(/*principal name*/)))
.map(b -> new UsernamePasswordAuthenticationToken(/*credentials*/));
}
最後,我們設置基於場景的 ReactiveAuthenticationManagerResolver:
ReactiveAuthenticationManagerResolver<ServerWebExchange> resolver() {
return exchange -> {
if (match(exchange.getRequest(), "/employee")) {
return Mono.just(employeesAuthenticationManager());
}
return Mono.just(customersAuthenticationManager());
};
}
6.2. For Basic Authentication
在反應式 Web 應用程序中,我們可以使用 AuthenticationWebFilter 進行身份驗證。它驗證請求並填充安全上下文。
AuthenticationWebFilter 首先檢查請求是否匹配。之後,如果請求中存在身份驗證對象,它會從 ReactiveAuthenticationManagerResolver 中獲取合適的 ReactiveAuthenticationManager 並繼續身份驗證流程。
因此,我們可以將自定義的 AuthenticationWebFilter 設置到我們的安全配置中:
@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();
}
首先,我們禁用 ServerHttpSecurity#httpBasic 以防止正常的身份驗證流程,然後手動替換它為 AuthenticationWebFilter,並傳入我們的自定義解析器。
6.3. For OAuth2 Authentication
我們可以使用 ServerHttpSecurity#oauth2ResourceServer 配置 ReactiveAuthenticationManagerResolver。ServerHttpSecurity#build 將一個 AuthenticationWebFilter 實例,該實例包含我們的解析器,添加到過濾器的鏈中。
因此,我們可以在我們的安全配置中定義用於 OAuth2 身份驗證的 AuthenticationManagerResolver:
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
// ...
.and()
.oauth2ResourceServer()
.authenticationManagerResolver(resolver())
.and()
// ...;
}
7. 結論
在本文中,我們使用了 AuthenticationManagerResolver 在一個簡單的場景中用於 Basic 和 OAuth2 認證。
此外,我們還探索了 ReactiveAuthenticationManagerResolver 在反應式 Spring Web 應用程序中用於 Basic 和 OAuth2 認證。