Spring Security 认证流程是一个高度可扩展的链条式处理过程,下面是完整流程及各种自定义扩展点的详细说明:


Spring Security 认证完整流程

图1

  1. 请求拦截

    • FilterChainProxy 委托给 SecurityFilterChain
    • 认证过滤器(如 UsernamePasswordAuthenticationFilter)拦截请求
  2. 创建认证对象

    • 从请求中提取凭证(用户名/密码、Token等)
    • 创建未认证的 Authentication 对象(如 UsernamePasswordAuthenticationToken
  3. 认证管理器处理

    • AuthenticationManager(通常是 ProviderManager)接收认证请求
    • 委托给支持的 AuthenticationProvider
  4. 认证提供者处理

    • AuthenticationProvider.supports() 检查是否能处理该认证类型
    • 调用 authenticate() 执行核心认证逻辑
  5. 用户信息加载

    • 通过 UserDetailsService.loadUserByUsername() 加载用户信息
    • 返回 UserDetails 对象(包含用户名、密码、权限等)
  6. 凭证验证

    • 使用 PasswordEncoder.matches() 比对请求密码和存储密码
    • 检查账户状态(启用/锁定/过期等)
  7. 构建认证对象

    • 创建完全认证的 Authentication 对象
    • 包含用户信息、权限列表等
  8. 存储认证上下文

    • 将认证对象存入 SecurityContextHolder
    • 通过 SecurityContextRepository 持久化(如Session存储)

自定义扩展点详解

1. 自定义认证过滤器

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain chain) {
        String token = extractToken(request);
        if (token != null) {
            // 创建未认证Token
            Authentication authRequest = new JwtAuthenticationToken(token);
            // 触发认证流程
            Authentication authResult = authenticationManager.authenticate(authRequest);
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }
        chain.doFilter(request, response);
    }
}

使用场景:添加JWT、API Key等非标准认证方式


2. 自定义 AuthenticationProvider

public class FingerprintAuthenticationProvider implements AuthenticationProvider {
    
    @Override
    public Authentication authenticate(Authentication auth) {
        FingerprintAuthenticationToken token = (FingerprintAuthenticationToken) auth;
        UserDetails user = fingerprintService.verify(token.getFingerprintData());
        return new FingerprintAuthenticationToken(user, null, user.getAuthorities());
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return FingerprintAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

使用场景:生物识别、硬件设备认证等特殊认证方式


3. 自定义 UserDetailsService

@Service
public class CustomUserDetailsService implements UserDetailsService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userRepository.findByUsernameOrEmailOrPhone(username);
        return new CustomUserDetails(
            user.getLoginId(), 
            user.getEncryptedPassword(),
            getAuthorities(user.getRoles())
        );
    }
}

使用场景:多字段登录(用户名/手机/邮箱)、多数据源加载


4. 自定义 UserDetails

public class CustomUserDetails extends User {
    private Long departmentId;
    private String timezone;
    
    public CustomUserDetails(String username, String password, 
                            Collection<? extends GrantedAuthority> authorities,
                            Long departmentId) {
        super(username, password, authorities);
        this.departmentId = departmentId;
    }
    
    // 附加业务字段
}

使用场景:扩展用户属性(部门、时区等业务字段)


5. 自定义 PasswordEncoder

public class LegacyPasswordEncoder implements PasswordEncoder {
    
    @Override
    public String encode(CharSequence rawPassword) {
        return MyLegacySystem.encrypt(rawPassword.toString());
    }
    
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        String encrypted = MyLegacySystem.encrypt(rawPassword.toString());
        return encrypted.equals(encodedPassword);
    }
}

使用场景:兼容旧系统加密算法、多加密策略并存


6. 自定义 Authentication 对象

public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
    private final OAuth2User principal;
    private String accessToken;
    
    public OAuth2AuthenticationToken(OAuth2User principal, 
                                    Collection<? extends GrantedAuthority> authorities,
                                    String accessToken) {
        super(authorities);
        this.principal = principal;
        this.accessToken = accessToken;
        super.setAuthenticated(true);
    }
    
    @Override
    public Object getCredentials() {
        return accessToken;
    }
    
    @Override
    public Object getPrincipal() {
        return principal;
    }
}

使用场景:携带额外认证信息(OAuth令牌、会话ID等)


7. 自定义认证事件处理

@Component
public class CustomAuthEventListener {
    
    @EventListener
    public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) {
        Authentication auth = event.getAuthentication();
        auditService.logLoginSuccess(auth.getName());
    }
    
    @EventListener
    public void handleAuthenticationFailure(AbstractAuthenticationFailureEvent event) {
        String username = (String) event.getAuthentication().getPrincipal();
        auditService.logLoginFailure(username, event.getException());
    }
}

使用场景:审计日志、登录通知、锁定策略


8. 多认证方式共存配置

@Configuration
@EnableWebSecurity
public class MultiAuthConfig extends WebSecurityConfigurerAdapter {
    
    @Bean
    public AuthenticationProvider daoAuthProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }
    
    @Bean
    public AuthenticationProvider otpAuthProvider() {
        return new OtpAuthenticationProvider();
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(daoAuthProvider())
            .authenticationProvider(otpAuthProvider());
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class)
            .formLogin()
            .and()
            .addFilterAfter(new OtpVerificationFilter(), BasicAuthenticationFilter.class);
    }
}

支持场景

  • 用户名密码 + 短信验证码
  • JWT + 表单登录
  • 主认证 + 二次认证(2FA)

高级自定义场景

场景1:动态权限加载

public class DynamicPermissionEvaluator implements PermissionEvaluator {
    
    @Override
    public boolean hasPermission(Authentication auth, Object target, Object permission) {
        String username = auth.getName();
        // 实时查询数据库获取最新权限
        List<String> permissions = permissionService.getUserPermissions(username);
        return permissions.contains(permission.toString());
    }
}

场景2:认证失败自定义响应

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
    @Override
    public void commence(HttpServletRequest request, 
                        HttpServletResponse response,
                        AuthenticationException authException) {
        
        response.setContentType("application/json");
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.getWriter().write("""
            {
              "error": "AUTH_REQUIRED",
              "message": "请提供有效的认证凭证",
              "login_url": "/api/v1/auth/login"
            }
            """);
    }
}

场景3:分布式会话管理

@Bean
public SecurityContextRepository securityContextRepository() {
    return new DelegatingSecurityContextRepository(
        new RequestAttributeSecurityContextRepository(), // 请求级缓存
        new RedisSecurityContextRepository(redisTemplate) // Redis持久化
    );
}

最佳实践建议

  1. 优先使用组合而非继承

    • 扩展 DaoAuthenticationProvider 而非实现底层接口
    • 包装现有组件而非重写完整流程
  2. 保持认证与业务分离

    • 认证组件不应包含业务逻辑
    • 通过事件监听器处理业务副作用
  3. 分层自定义策略

    HTTP层 → 认证过滤器 (处理协议)
    |
    V
    认证管理层 → AuthenticationProvider (核心认证逻辑)
    |
    V
    数据访问层 → UserDetailsService (加载数据)
  4. 测试策略

    • 使用 @WithMockUser 测试权限
    • 使用 SecurityTestExecutionListener 集成测试
    • 模拟 AuthenticationManager 单元测试

通过深度理解这些扩展点,可以灵活实现从简单的表单登录到复杂的多因素认证系统,满足各种安全需求。

添加新评论