Ohhnews

分类导航

$ cd ..
Baeldung原文

Spring Security 7 中的多因素身份验证 (MFA) 实现指南

#spring security#多因素身份验证#网络安全#身份认证#java开发

[LOADING...]

1. 概述

身份验证是所有 Web 应用程序的第一道防线。传统上,应用程序依赖用户名和密码来验证用户身份。然而,专家们认为,对于现代系统而言,仅依赖单一身份验证因子已不再安全。

多因素身份验证 (MFA) 通过要求用户在访问系统之前使用多个独立的因子来验证其身份,从而解决了这一问题。

Spring Security 7 引入了对多因素身份验证的内置支持,允许开发人员使用现有的授权模型来强制执行多个身份验证步骤。在本文中,我们将探讨 MFA 在 Spring Security 7 中是如何工作的,以及如何在 Spring Boot 应用程序中实现它。

2. 理解 Spring Security 7 中的 MFA

Spring Security 7 引入了一种使用权限 (authorities) 来建模身份验证因子的新方法。 Spring Security 不再将 MFA 视为一种独立的身份验证机制,而是为每个成功的身份验证因子逐步授予相应的权限。

多因素身份验证 (MFA) 通过要求用户使用多个独立因子验证身份来增强安全性。这些因子通常分为三类:用户知道的信息(如密码或 PIN)、用户拥有的物品(如移动设备或电子邮件令牌)以及用户自身的特征(如指纹或其他生物识别数据)。通过组合这些因子,应用程序可以显著降低凭据泄露和未经授权访问的风险。

每当用户使用特定因子成功完成身份验证时,Spring Security 就会将相应的权限添加到身份验证对象中。此权限代表了在身份验证过程中已验证的因子。常见的示例包括:用于基于密码验证的 FACTOR_PASSWORD、用于基于证书验证的 FACTOR_X509 以及用于一次性令牌验证的 FACTOR_OTT。这些权限在内部由 FactorGrantedAuthority 类表示,并成为已认证用户安全上下文的一部分。

这种设计使得授权规则能够在授予受保护资源的访问权限之前,验证所需的身份验证因子是否已满足。

3. 项目设置

在实现多因素身份验证之前,我们将使用 Spring Initializr 设置一个简单的 Spring Boot 应用程序,并引入 Spring Security 7。首先,我们需要配置所需的依赖项。

我们引入了 Spring Boot 的 websecuritystarter-testwebmvc-test 以及 security-testing 起步依赖:

$ xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>4.0.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>4.0.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>4.0.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webmvc-test</artifactId>
    <version>4.0.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>7.0.0</version>
    <scope>test</scope>
</dependency>

此配置添加了应用程序所需的核心依赖项,包括用于构建 REST 端点的 Web 支持、用于实现身份验证和授权的 Spring Security 框架,以及用于编写安全测试的测试工具。

4. 全局启用多因素身份验证

Spring Security 7 引入了一个便捷的注解 @EnableMultiFactorAuthentication该注解允许开发人员定义所有受保护端点所需的身份验证因子。

以下配置通过密码和 X509 身份验证因子全局启用了 MFA:

$ java
@Configuration
@EnableWebSecurity
@EnableMultiFactorAuthentication(
  authorities = { FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.X509_AUTHORITY }
)
public class GlobalMfaSecurityConfig {
    @Bean
    @Order(3)
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/**").authorizeHttpRequests(auth ->auth.requestMatchers("/public")
          .permitAll().anyRequest().authenticated()).formLogin(withDefaults());
        return http.build();
    }
}

此配置启用了全局 MFA。设置完成后,用户必须完成两个身份验证因子,系统才会认为其已完成身份验证。在这种情况下,所需的因子是密码和 X.509 证书身份验证。全局应用 MFA 有助于避免配置不一致,并降低在单个授权配置中遗漏 MFA 规则的风险。

5. 将 MFA 应用于特定端点

在许多应用程序中,MFA 应仅针对敏感端点(如账户设置、金融操作或管理操作)进行要求。Spring Security 提供了 AuthorizationManagerFactory API 来以编程方式定义 MFA 规则。

以下配置仅要求对管理端点进行 MFA 验证:

$ java
@Configuration
@EnableWebSecurity
public class AdminMfaSecurityConfig {
    @Bean
    @Order(1)
    SecurityFilterChain adminSecurityFilterChain(HttpSecurity http) throws Exception {
        AuthorizationManagerFactory<Object> mfa = AuthorizationManagerFactories.multiFactor()
          .requireFactors(
            FactorGrantedAuthority.PASSWORD_AUTHORITY,
            FactorGrantedAuthority.X509_AUTHORITY)
          .build();
        http.securityMatcher("/admin/**").authorizeHttpRequests(auth ->auth.requestMatchers("/admin/**")
          .access(mfa.hasRole("ADMIN")).anyRequest().authenticated()).formLogin(withDefaults());
        return http.build();
    }
}

在此配置中,MFA 仅应用于匹配 /admin/** 的请求。当用户尝试访问这些路由时,Spring Security 会检查所需的身份验证因子是否存在。此外,用户还必须具备 ADMIN 角色。这种方法提供了细粒度的安全控制。

6. 实现基于时间的身份验证规则

某些应用要求用户在执行敏感操作前重新进行身份验证。例如,银行应用可能要求在更新付款信息前重新登录。Spring Security 7 支持基于时间的 MFA 规则。

以下配置要求用户在过去五分钟内完成密码因子验证:

$ java
@Configuration
@EnableWebSecurity
public class TimeBasedMfaSecurityConfig {
    @Bean
    @Order(2)
    SecurityFilterChain profileSecurityFilterChain(HttpSecurity http) throws Exception {
        AuthorizationManagerFactory<Object> recentLogin = AuthorizationManagerFactories.
          multiFactor().requireFactor(
            factor -> factor.passwordAuthority().validDuration(Duration.ofMinutes(5)))
          .build();
        http.securityMatcher("/profile", "/profile/**").authorizeHttpRequests(
          auth -> auth.requestMatchers("/profile", "/profile/**")
            .access(recentLogin.authenticated())
            .anyRequest().
            authenticated()).formLogin(withDefaults());
        return http.build();
    }
}

如果密码因子验证的时间超过五分钟,Spring Security 将要求用户再次进行身份验证。这确保了用户在执行关键操作时,必须经过近期且已验证的身份验证步骤。

7. 实现基于用户的 MFA 规则

有时 MFA 规则仅适用于特定用户。例如,管理员可能被要求使用 MFA,而普通用户只需单因子登录。Spring Security 允许实现自定义授权管理器来支持此类场景。

以下示例仅对管理员用户强制执行 MFA:

$ java
@Component
public class AdminMfaAuthorizationManager implements AuthorizationManager<Object> {
    AuthorizationManager<Object> mfa =
      AllAuthoritiesAuthorizationManager.hasAllAuthorities(FactorGrantedAuthority.OTT_AUTHORITY,
        FactorGrantedAuthority.PASSWORD_AUTHORITY);
    @Override
    public AuthorizationResult authorize(Supplier<? extends Authentication> authentication, Object context) {
        Authentication auth = authentication.get();
        if (auth != null && "admin".equals(auth.getName())) {
            return mfa.authorize(authentication, context);
        }
        return new AuthorizationDecision(true);
    }
}

该逻辑检查已认证用户的身份,并有条件地应用 MFA 规则。如果是管理员,系统会验证其是否完成了两个必需的身份验证因子。对于其他用户,系统则直接允许请求。

8. 编写 MFA 单元测试

测试身份验证流程对于确保安全规则按预期运行至关重要。Spring Security 提供了专门的测试工具,可以轻松模拟已认证用户并验证授权行为。

8.1 控制器示例

我们定义一个简单的控制器来暴露受 MFA 保护的端点:

$ java
@RestController
public class DemoController {
    @GetMapping("/public")
    public String publicEndpoint() { return "public endpoint"; }
    @GetMapping("/profile")
    public String profileEndpoint() { return "profile endpoint"; }
    @GetMapping("/admin/dashboard")
    public String adminDashboard() { return "admin dashboard"; }
}

8.2 MFA 安全测试

此测试验证系统在全局强制执行 MFA 时的行为:

$ java
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
class GlobalMfaSecurityTest {
    @Autowired
    MockMvc mockMvc;
    @Test
    void givenUserWithoutMfa_whenAccessProfile_thenForbidden() throws Exception {
        mockMvc.perform(get("/profile").with(user("user").roles("USER")))
          .andExpect(status().is3xxRedirection())
          .andExpect(header().string("Location", containsString("/login")));
    }
}

由于模拟的身份验证请求仅包含基本角色,未包含必要的因子权限,因此请求被重定向至登录页面。

8.3 管理员端点 MFA 测试

此测试验证系统是否为管理员端点强制执行 MFA:

$ java
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
class AdminMfaSecurityTest {
    @Autowired
    MockMvc mockMvc;
    @Test
    void givenAdminWithoutMfa_whenAccessAdminEndpoint_thenForbidden() throws Exception {
        mockMvc.perform(get("/admin/dashboard").with(user("admin").roles("ADMIN")))
          .andExpect(status().is3xxRedirection())
          .andExpect(header().string("Location", containsString("/login")));
    }
}

即使拥有 ADMIN 角色,如果缺少 MFA 权限,请求也会被重定向。

8.4 基于时间的 MFA 安全测试

此测试验证 /profile 端点是否要求近期身份验证:

$ java
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
class TimeBasedMfaSecurityTest {
    @Autowired
    MockMvc mockMvc;
    @Test
    void givenUserWithoutRecentAuthentication_whenAccessProfile_thenForbidden() throws Exception {
        mockMvc.perform(get("/profile").with(user("user").roles("USER")))
          .andExpect(status().is3xxRedirection())
          .andExpect(header().string("Location", containsString("/login")));
    }
}

9. 结论

多因素身份验证 (MFA) 是现代应用程序的关键安全措施。Spring Security 7 提供了内置的 MFA 支持,允许开发人员使用现有的授权模型轻松实现多步骤验证。通过使用 FactorGrantedAuthority@EnableMultiFactorAuthenticationAuthorizationManagerFactory,开发人员可以构建灵活且安全的身份验证流程。随着安全威胁的演变,实施 MFA 已成为保护用户数据和关键系统的必要步骤。

本文中的示例代码可在 GitHub 上找到。