Spring Security 7 中的多因素身份驗證

工程 | Josh Cummings | 2025年10月21日 | ...

2013 年,有人提議 將多因素身份驗證新增到 Spring Security。那一年,“自拍”被新增到英語詞典中,“What Does the Fox Say?”風靡 YouTube。

毋庸置疑,Spring Security 7 中最大的功能之一是醞釀已久,也是我們通往 GA 之路的下一站。

什麼是多因素身份驗證 (MFA)?

多因素身份驗證是一種身份驗證策略,透過多種驗證方式(或因素)來確定您在網站上的身份。常見的因素分為以下幾類:

  • 您所知道的;例如密碼或安全問題的答案
  • 您所擁有的;例如手機上的應用程式
  • 您所的;例如指紋或其他生物識別資訊

例如,當你輸入使用者名稱和密碼,然後被要求輸入傳送到你電子郵件的驗證碼時,這就是多因素認證。使用者名稱和密碼是你知道的東西,而你的電子郵件是你擁有的東西。

多因素認證提高了應用程式對使用者身份聲稱的信任度。

Spring Security 支援的 MFA 功能

截至本文撰寫之時,Spring Security 支援幾個重要的 MFA 用例

  • 全域性要求使用者按指定順序提供多個因素
  • 有條件地要求多個因素,可透過 Web 端點或方法簽名配置
  • 基於時間的認證,以便你可以在給定時間後要求使用者對某些端點重新認證
  • 基於使用者的認證,以便你可以處理並非所有使用者都已使用 MFA 的選擇性加入場景
  • 與自定義 AuthenticationProvider 的整合

在這篇部落格文章中,我們將逐一探討這些內容。不過,首先,讓我們瞭解 MFA 在 Spring Security 中是如何建模的

Spring Security 如何透過漸進式授權建模 MFA

將 MFA 引入 Spring Security 的關鍵在於不起眼的 GrantedAuthority

最初,用授權來描述認證策略可能看起來很奇怪,直到你考慮到授權規則可以而且通常確實會考慮到認證的獲取方式。

例如,如果使用者在過去 5 分鐘內進行了認證,或者驗證了其電子郵件地址,或者透過特定的 OAuth 2.0 頒發者獲得了授權,網站可能只會授權對其部分內容的請求。

為了方便這一點,Spring Security 7 為每次成功的認證頒發一個因素 GrantedAuthority

透過這個簡單的更改,Spring Security 中的 MFA 變成了許可權的漸進式授予;每次成功認證都會授予一個許可權。你可以編寫授權規則,規定哪些認證因素對你來說在哪裡很重要。

然後,當缺少某個因素許可權時,Spring Security 會將終端使用者重定向到可以獲取該許可權的端點。例如,要求 FACTOR_PASSWORD 的規則將導致 Spring Security 重定向到 /login 頁面以獲取使用者的使用者名稱和密碼。

想象一下這樣的授權規則

@PreAuthorize("hasAllAuthorities('FACTOR_PASSWORD', 'FACTOR_X509', 'ROLE_ADMIN')")

此規則規定終端使用者必須是管理員,但還必須提供其使用者名稱、密碼(頒發 FACTOR_PASSWORD 許可權)和 X.509 證書(頒發 FACTOR_X509 許可權)來識別自己。

使用許可權為任何給定端點或方法呼叫要求多種型別的認證提供了一種簡單有效的速記方式。

全域性啟用 MFA

現在,雖然上述 @PreAuthorize 規則會起作用,但為每個授權規則指定每個因素可能會很繁瑣。授權規則的重複減少意味著敏感程式碼中的編碼錯誤也減少。

為此,你可以在 Spring Security 中透過在 @EnableMultiFactorAuthentication 註解中列出所需的許可權來啟用 MFA

@EnableMultiFactorAuthentication(FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.X509_AUTHORITY)

這將在所有授權規則中新增一個隱式檢查,要求使用者在顯示任何需要認證的頁面之前提供其使用者名稱和密碼(他們知道的東西)以及 X.509 證書(他們擁有的東西)。

剩下的唯一事情是為每個認證機制宣告適當的配置

@EnableMultiFactorAuthentication(authorities = {
	FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.X509_AUTHORITY
})
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig {

    @Bean 
    Customizer<HttpSecurity> formLogin() {
        return (http) -> http.formLogin(Customizer.withDefaults());
    }
    
    @Bean 
    Customizer<HttpSecurity> x509Login() {
        return (http) -> http.x509(Customizer.withDefaults());
    }
    
    @Bean 
    UserDetailsService users() {
        return new InMemoryUserDetailsManager(myTestUser);
    }

}

這將把之前的 @PreAuthorize 規則改回

@PreAuthorize("hasAuthority('ROLE_ADMIN')")

這樣,如果終端使用者在其瀏覽器中安裝了 X.509 證書,他們也將被重定向到 /login 以提供使用者名稱/密碼因素。

當 MFA 未啟用時,啟用這兩個機制意味著終端使用者可以使用 X.509 或使用者名稱和密碼進行認證。

[提示] 你是否注意到 Spring Security 對自定義 HttpSecurity 的新支援?

使用 AuthorizationManagerFactory 進行細粒度 MFA 控制

在許多情況下,你可能不想為每個端點和方法呼叫要求多個因素。在這種情況下,你可以使用 Spring Security 的 AuthorizationManagerFactory 以程式設計方式構建適當的多因素規則。

為此,首先建立一個 AuthorizationManagerFactory 例項,宣告你的多個因素

AuthorizationManagerFactory<Object> mfa = AuthorizationManagerFactories.multiFactor()
        .requireFactors(FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.X509_AUTHORITY)
        .build();

這與 @EnableMultiFactorAuthentication 建立並作為 bean 釋出的是同一個元件。

在我們的例子中,我們將在描述授權規則時使用它

@Bean
Customizer<HttpSecurity> authz() {
    AuthorizationManagerFactory<Object> mfa = AuthorizationManagerFactories.multiFactor()
        .requireFactors(
            FactorGrantedAuthority.PASSWORD_AUTHORITY,
            FactorGrantedAuthority.X509_AUTHORITY).build();
    return (http) -> http.authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/admin/**”).access(mfa.hasRole(“ADMIN”))
        .anyRequest().authenticated());
}

此應用程式中針對 /admin/** 的授權規則將要求使用者名稱和密碼、X.509 證書以及使用者具有 ROLE_ADMIN。應用程式的其餘部分將只要求一個因素。

基於時間的授權規則

每次完成認證後,Spring Security 都會頒發具有名稱和時間戳的相應 FactorGrantedAuthority。這意味著我還可以編寫基於時間的授權規則,就像你在網站上看到的那些,如果使用者在過去五分鐘內沒有登入,他們會要求你重新登入才能訪問網站的特定部分。

我們可以再次使用 AuthorizationManagerFactory,這次指定給定因素所需的時間

@Bean
Customizer<HttpSecurity> authz() {
    AuthorizationManagerFactory<Object> recentLogin = AuthorizationManagerFactories.multiFactor()
        .requireFactor((f) -> f.passwordAuthority().validDuration(Duration.ofMinutes(5)))
        .requireFactor((f) -> f.x509Authority())
        .build();
    return (http) -> http.authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/profile/**").access(recentLogin.authenticated())
        .anyRequest().authenticated());
}

這樣,使用者可以登入,正常瀏覽網站,當他們訪問 /profile/me 頁面時,會被要求重新認證。

基於使用者的授權規則

多因素業務規則也可能只考慮某些使用者,例如那些選擇為其賬戶使用 MFA 的使用者。

考慮一個使用 Spring Security 的一次性令牌登入的應用程式,除了使用使用者名稱/密碼登入外,還會向用戶的電子郵件地址傳送令牌(他們擁有的東西)。

這次,我們將建立一個自定義的 AuthorizationManager,它以程式設計方式檢視當前的 Authentication。例如,假設我們希望要求 admin 使用者使用兩個因素;這可能看起來像以下內容

@Component
class AdminMfaAuthorizationManager implements AuthorizationManager<Object> {
    private final AuthorizationManager<Object> mfa = AllAuthoritiesAuthorizationManager
            .hasAllAuthorities(FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY);

    @Override
    public AuthorizationResult authorize(
        Supplier<? extends @Nullable Authentication> authentication, Object context) {
        if ("admin".equals(authentication.get().getName())) {
            return this.mfa.authorize(authentication, context);
        } else {
            return new AuthorizationDecision(true);
        }
    }
}

然後,我們現在可以使用 Spring Security 的 DefaultAuthorizationManagerFactory 的預設實現,並指示它將此授權管理器附加到所有授權規則中

@Bean
AuthorizationManagerFactory<Object> authorizationManagers(AdminMfaAuthorizationManager admins) {
    DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
    defaults.setAdditionalAuthorization(admins);
    return defaults;
}

現在,所有 Web 和方法安全規則也將隱式檢查此授權管理器。

自行頒發許可權

你的自定義認證機制也可以無縫參與。所需要的只是你的 AuthenticationProvider 頒發一個 FactorGrantedAuthority,其名稱可供規則用於識別它。

考慮一個像這樣的生物識別認證提供者

class MyBiometricAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) {
        // ..,.
        UserDetails user = this.users.findUserByUsername(username);
        Collection<GrantedAuthority> authorities = new HashSet<>(user.getAuthorities());
        return new UsernamePasswordAuthenticationToken(username, null, authorities);
    }
}

除了你授予的任何使用者級別許可權外,你還可以授予自己的基礎設施許可權

class MyBiometricAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) {
        // ..,.
        UserDetails user = this.users.findUserByUsername(username);
        Collection<GrantedAuthority> authorities = new HashSet<>(user.getAuthorities());
        authorities.add(FactorGrantedAuthority.withFactor("THUMBPRINT").build());
        return new UsernamePasswordAuthenticationToken(username, null, authorities);
    }
}

現在,你可以新增自己的授權規則,要求使用者提供指紋才能訪問給定資源。

無密碼化

MFA 在允許應用程式無密碼化方面發揮著重要作用。例如,你現在可以通過幾個簡單的配置要求通行金鑰和生物識別掃描。

首先添加註解

@EnableMultiFactorAuthentication(authorities = {
    FactorGrantedAuthority.WEBAUTHN_AUTHORITY,
    "FACTOR_THUMBPRINT"
})

[注意] 請注意,此示例使用了一個自定義認證提供者,用於驗證使用者的生物識別資料。

然後,新增認證機制

@Bean
Customizer<HttpSecurity> webAuthn() {
    return (http) -> http
        .webAuthn((webAuthn) -> webAuthn
            .rpName("Spring Security Relying Party")
            .rpId("example.com")
            .allowedOrigins("https://example.com"));
}

@Bean
Customizer<HttpSecurity> biometrics() {
    return (http) -> http.authenticationProvider(new MyBiometricsAuthenticationProvider());
}

就是這樣!

總結

讓我們總結一下。多因素認證是 Spring Security 7 中的一個強大新功能,它允許你根據指定所需因素的授權規則要求一個以上因素。你可以使用 @EnableMultiFactorAuthentication 來指定全域性應用的因素規則,或使用 AuthorizationManagerFactory 來指定在特定條件下應用的規則。你的自定義認證機制也可以透過在其 Authentication 的授予許可權列表中新增 FactorGrantedAuthority 例項來協同工作。

要了解更多資訊,請檢視 spring-security-samples 中的示例程式碼參考文件

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有