★ Spring Security 认证流程与过滤器链是如何工作的?
结论先行:Spring Security 基于 Servlet 过滤器链实现,核心是 FilterChainProxy 按顺序执行一组 SecurityFilter。认证类过滤器(如 UsernamePasswordAuthenticationFilter)负责“取凭证→问 AuthenticationManager→写 SecurityContext”;授权由 AuthorizationFilter 在链路末尾裁决。回答框架:一条链、两类过滤器(认证/授权)、一个上下文。
一、过滤器链架构
| 部件 | 作用 |
|---|---|
| DelegatingFilterProxy | 把 Servlet 过滤器委托给 Spring 容器中的 Bean |
| FilterChainProxy | 持有并调度所有 SecurityFilterChain |
| SecurityFilterChain | 一组有序过滤器 + 请求匹配规则 |
| SecurityContextHolder | 保存当前认证信息(默认 ThreadLocal) |
Security 6.x 起以 Bean 方式声明链,配置风格改为 lambda:
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/public/**").permitAll()
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.csrf(csrf -> csrf.disable());
return http.build();
}
二、表单认证主流程
- 请求进入 UsernamePasswordAuthenticationFilter(默认拦截 /login POST);
- 从请求中取 username/password,构造未认证的 UsernamePasswordAuthenticationToken;
- 交给 AuthenticationManager(默认 ProviderManager)→ 遍历 AuthenticationProvider;
- DaoAuthenticationProvider 调 UserDetailsService.loadUserByUsername 拿用户,再用 PasswordEncoder 比对密码;
- 成功 → 生成已认证 Token 写入 SecurityContextHolder,走成功处理器(写 Session、跳转);
- 失败 → 清上下文,走 AuthenticationFailureHandler。
@Bean
UserDetailsService users() {
UserDetails u = User.withUsername("admin")
.password("{bcrypt}$2a$10$...") // 生产用 BCrypt
.roles("ADMIN").build();
return new InMemoryUserDetailsManager(u);
}
三、授权与细节
- 授权过滤器:Security 6 用 AuthorizationFilter(配合 requestMatchers 规则),老版本 FilterSecurityInterceptor 已废弃;
- 匿名访问也会生成 AnonymousAuthenticationToken,因此“未登录”不等于“无 Authentication”;
- 无状态场景:加
sessionManagement(s -> s.sessionCreationPolicy(STATELESS))并禁用 CSRF,令牌交给自定义 JWT 过滤器解析。
常见追问 / 记忆点
- 追问:多个 SecurityFilterChain 如何匹配?答:按声明的顺序逐个 match,第一条命中的链生效,可用 securityMatcher 限定路径。
- 追问:为什么自定义过滤器要放在指定位置?答:认证过滤器需在授权过滤器之前执行,用 addFilterBefore/After 精确插入。
- 记忆点:一条 FilterChainProxy;认证=取凭证→AuthenticationManager→写上下文;授权在最后裁决。