Skip to content

Spring Security 源码级原理解析

入口不是 HttpSecurity

运行时入口是 Servlet Filter,不是配置类。真正处理请求的是 FilterChainProxy

java
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();

DelegatingFilterProxy -> FilterChainProxy

Servlet 容器注册的是 DelegatingFilterProxy,它只负责从 Spring 容器拿到名为 springSecurityFilterChain 的 Bean 并调用。

源码层面要点:

  • 容器只认标准 Filter
  • Spring Security 借代理桥接到 IoC 容器
  • 真正的安全逻辑全部挂在 FilterChainProxy

FilterChainProxy 做的事情

FilterChainProxy 会维护多条 SecurityFilterChain

一次请求进来后:

  1. RequestMatcher 找第一条匹配链。
  2. 拿到该链上的过滤器集合。
  3. 依次执行过滤器。

这也是 Spring Security 6 推荐多条 SecurityFilterChain Bean 的原因,匹配与执行天然分离。

SecurityContextHolder

默认策略是 MODE_THREADLOCAL。认证成功后的 Authentication 会被放进当前线程的 SecurityContext

java
Authentication result = authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(username, password));
SecurityContextHolder.getContext().setAuthentication(result);

运行时含义:

  • 控制器里直接注入 Authentication,本质上就是从上下文取值。
  • 同一线程中的后续授权逻辑共享这份身份信息。
  • 线程池复用时必须清理上下文。

ProviderManager

AuthenticationManager 默认实现通常是 ProviderManager。它像责任链:

  • 逐个问 AuthenticationProvider 是否 supports
  • 找到支持者后执行 authenticate
  • 成功返回完整的认证对象
java
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
    return configuration.getAuthenticationManager();
}

DaoAuthenticationProvider

这是用户名密码场景的核心 Provider:

  1. 调用 UserDetailsService.loadUserByUsername
  2. 取回 UserDetails
  3. PasswordEncoder 校验密码
  4. 组装已认证 Authentication
java
@Service
class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) {
        return User.withUsername(username)
                .password("{noop}123456")
                .authorities("ROLE_USER")
                .build();
    }
}

授权入口

Spring Security 6 的请求授权核心落在 AuthorizationFilterAuthorizationManager

java
http.authorizeHttpRequests(auth -> auth
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated());

机制上不是简单字符串判断,而是:

  • 读取当前 Authentication
  • 结合当前请求对象
  • 交给 AuthorizationManager 计算结果

一条请求的内部状态变化

text
未认证请求
  -> 过滤器读取凭证
  -> AuthenticationManager 认证
  -> SecurityContextHolder 写入身份
  -> AuthorizationFilter 鉴权
  -> Controller
  -> 清理上下文