知識庫 / Spring / Spring Security RSS 訂閱

Spring Security 身份驗證管理器解析器指南

Spring Security
HongKong
11
01:06 PM · Dec 06 ,2025

1. 引言

本教程將介紹 AuthenticationManagerResolver,並演示如何使用它進行 Basic 和 OAuth2 認證流程。

2. 什麼是 AuthenticationManager

簡單來説,AuthenticationManager 是認證的主要策略接口。

如果輸入認證的 principal 有效且已驗證,則 AuthenticationManager#authenticate 方法返回一個包含 authenticated 標誌設置為 trueAuthentication 實例。 否則,如果 principal 無效,則會拋出 AuthenticationException 異常。 對於最後一種情況,如果無法確定,則返回 null

ProviderManagerAuthenticationManager 的默認實現。 它將認證過程委託給一個 AuthenticationProvider 實例列表。

我們可以創建 SecurityFilterChain bean 時設置全局或本地 AuthenticationManager。 對於本地 AuthenticationManager,我們可以通過 HttpSecurity 訪問 AuthenticationManagerBuilder,並創建一個 AuthenticationManager bean。

AuthenticationManagerBuilder 是一個輔助類,可以簡化 UserDetailServiceAuthenticationProvider 以及其他依賴項的設置,從而構建 AuthenticationManager

對於全局 AuthenticationManager,我們應該將其定義為一個 bean。

3. 為什麼使用 AuthenticationManagerResolver

AuthenticationManagerResolver 允許 Spring 根據上下文選擇特定的 AuthenticationManager。這是一個在 Spring Security 5.2.0 版本中新增的功能,解決了 Spring Security 中上下文感知 AuthenticationManager 的問題。(該功能旨在解決 Spring Security 中上下文感知 AuthenticationManager 的問題。)

public interface AuthenticationManagerResolver<C> {
    AuthenticationManager resolve(C context);
}

AuthenticationManagerResolver#resolve 可以根據泛型上下文返回一個 AuthenticationManager 實例。換句話説,我們可以將一個類設置為上下文,以便根據它來解析 AuthenticationManager

Spring Security 將 AuthenticationManagerResolver 集成到身份驗證流程中,並使用 HttpServletRequestServerWebExchange 作為上下文。

4. 使用場景

讓我們看看如何在實踐中運用 AuthenticationManagerResolver

例如,假設一個系統中有兩個用户組:員工和客户。這兩個用户組具有特定的身份驗證邏輯,並且擁有獨立的數據庫。此外,這兩個用户組中的用户只能調用其相關的 URL。

5. AuthenticationManagerResolver 的工作原理是什麼?

我們可以使用 AuthenticationManagerResolver 在我們需要動態選擇 AuthenticationManager 的任何地方。但是,在本教程中,我們主要關注如何在內置身份驗證流程中使用它。

首先,讓我們設置一個 AuthenticationManagerResolver,然後使用它進行 Basic 和 OAuth2 身份驗證。

5.1. 配置 AuthenticationManagerResolver</em/>

讓我們首先創建一個用於安全配置的類。

@Configuration
public class CustomWebSecurityConfigurer {
    // ...
}

然後,我們添加一個方法,該方法返回客户的 AuthenticationManager

AuthenticationManager customersAuthenticationManager() {
    return authentication -> {
        if (isCustomer(authentication)) {
            return new UsernamePasswordAuthenticationToken(/*credentials*/);
        }
        throw new UsernameNotFoundException(/*principal name*/);
    };
}

員工認證管理器在邏輯上相同,我們僅將 isCustomer 替換為 isEmployee

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. 基本身份驗證

我們可以使用 <em AuthenticationFilter</em>> 動態地為每個請求解決 <em AuthenticationManager</em>>。<em AuthenticationFilter>` 在 Spring Security 5.2 版本中添加。

如果我們將它添加到我們的安全過濾器鏈中,則對於每個匹配的請求,它首先會檢查是否可以提取任何身份驗證對象。如果是,它將向 <em AuthenticationManagerResolver</em>> 請求一個合適的 <em AuthenticationManager</em>>,並繼續流程。

首先,讓我們在我們的 <em CustomWebSecurityConfigurer</em>> 中添加一個方法來創建 <em AuthenticationFilter</em>>

private AuthenticationFilter authenticationFilter() {
    AuthenticationFilter filter = new AuthenticationFilter(
      resolver(), authenticationConverter());
    filter.setSuccessHandler((request, response, auth) -> {});
    return filter;
}

設置 AuthenticationFilter#successHandler 為空實現 SuccessHandler 的原因是為了防止在成功認證後默認的重定向行為。

然後,我們可以通過在 CustomWebSecurityConfigurer 中創建 SecurityFilterChain bean 將此過濾器添加到我們的安全過濾器鏈中:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.addFilterBefore(authenticationFilter(), BasicAuthenticationFilter.class);
    return http.build();
}

5.3. 使用 OAuth2 認證

<em>BearerTokenAuthenticationFilter</em> 負責 OAuth2 認證。BearerTokenAuthenticationFilter#doFilterInternal 方法檢查請求中是否存在 <em>BearerTokenAuthenticationToken</em>,如果存在,則會解析相應的 <em>AuthenticationManager</em> 來驗證該令牌。

<em>OAuth2ResourceServerConfigurer</em> 用於配置BearerTokenAuthenticationFilter。`

因此,我們可以通過在 <em>CustomWebSecurityConfigurer</em> 中創建SecurityFilterChain bean,為我們的資源服務器配置AuthenticationManagerResolver。`

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .oauth2ResourceServer()
      .authenticationManagerResolver(resolver());
    return http.build();
}

6. 解決反應式應用中的 AuthenticationManager

對於反應式 Web 應用程序,我們仍然可以根據上下文解決 AuthenticationManager 的問題。但這裏我們使用 ReactiveAuthenticationManagerResolver 代替:

@FunctionalInterface
public interface ReactiveAuthenticationManagerResolver<C> {
    Mono<ReactiveAuthenticationManager> resolve(C context);
}

它返回一個 <em >Mono</em ><em >ReactiveAuthenticationManager</em ><em >ReactiveAuthenticationManager</em ><em >AuthenticationManager</em > 的反應式等價物,因此它的 <em >authenticate</em > 方法返回 <em >Mono</em >

6.1. 設置 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. 基本身份驗證

在反應式 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. OAuth2 身份驗證

我們可以使用 ReactiveAuthenticationManagerResolverServerHttpSecurity#oauth2ResourceServer 結合進行配置。 ServerHttpSecurity#build 將一個包含我們解析器的 AuthenticationWebFilter 實例添加到安全過濾器鏈中。

因此,讓我們為 OAuth2 身份驗證過濾器設置我們的 AuthenticationManagerResolver

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    return http
      // ...
      .and()
      .oauth2ResourceServer()
      .authenticationManagerResolver(resolver())
      .and()
      // ...;
}

7. 結論

在本文中,我們使用了 AuthenticationManagerResolver 在一個簡單場景中進行 Basic 和 OAuth2 認證。

並且,我們還探索了在反應式 Spring Web 應用程序中,使用 ReactiveAuthenticationManagerResolver 進行 Basic 和 OAuth2 認證的應用。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.