Spring Security 的认证流程是怎样的?AuthenticationManager、ProviderManager、AuthenticationProvider 是什么关系?
- 标签:Spring Security、认证授权、后端
🏛 业务架构师
Spring Security 认证流程与三者关系,我按"一次登录"走一遍:
流程:请求进入认证过滤器(如 UsernamePasswordAuthenticationFilter 拦截 /login 的 POST)→ 组装未认证的 UsernamePasswordAuthenticationToken(用户名+密码)→ 交给 AuthenticationManager 认证。
三者关系(组织架构比喻):
- AuthenticationManager:认证的"门面"接口——标准实现是 ProviderManager;
- ProviderManager:认证调度中心——持有一组 AuthenticationProvider,逐个询问"你支持这种认证吗"(supports 方法)?支持就委托执行;认证成功返回已认证的 Authentication,全都不支持则抛 ProviderNotFoundException;还可挂父级 ProviderManager(认证链分层,多模块系统用);
- AuthenticationProvider:具体认证逻辑的实现者——DaoAuthenticationProvider(用户名密码:从 UserDetailsService 加载用户,PasswordEncoder 比对密码)、OAuth2LoginAuthenticationProvider、RememberMeAuthenticationProvider……每种登录方式一个 Provider。
收尾:认证成功 → Authentication(含 principal+权限列表)存入 SecurityContextHolder(默认 ThreadLocal)→ 后续授权环节直接读取;失败走 AuthenticationEntryPoint/FailureHandler 返回 401 或登录页。
业务架构价值:这个设计是策略模式+责任链的标准范本——新增一种认证方式(短信登录、企业微信扫码)= 加一个 Provider,主流程零改动。企业身份体系会持续演进(SSO 接入、多租户、生物认证),认证架构的"可插拔性"决定身份体系演进的成本——这是我评估一个安全架构好坏的首要视角。
🏗 系统架构师
三者的关系是"编排者-提供者"的策略组合:
- AuthenticationManager = 认证的门面接口(
authenticate(Authentication)):入参携带凭据的 Authentication(如 UsernamePasswordAuthenticationToken),返回认证成功的 Authentication(带权限),失败抛异常。业务代码只依赖它,不关心具体认证方式; - ProviderManager = 它的默认实现:持有多个
AuthenticationProvider列表(+ 可选 parent 委托链),遍历询问每个 provider:"你支持这种 Authentication 吗?"(supports(Class))——支持则尝试认证;全都不支持/失败则抛 ProviderNotFoundException/合并异常——策略路由器; - AuthenticationProvider = 具体认证策略:每个 provider 实现一种认证方式——
DaoAuthenticationProvider(用户名密码 + UserDetailsService 查库 + PasswordEncoder 验证)、RememberMeAuthenticationProvider、JWT/OAuth2 自定义等。一个 provider 只擅长一类凭据,组合多个覆盖多入口。
流程示例(表单登录):UsernamePasswordAuthenticationFilter 捕获表单 → 构造未认证 Token → ProviderManager 路由 → DaoAuthenticationProvider:loadUserByUsername 查库 → passwordEncoder.matches 验密 → 成功构建已认证 Authentication(principal=UserDetails、authorities=权限集)→ 存入 SecurityContextHolder。
架构师视角的设计精髓:
- 开闭原则:新增认证方式 = 注册新 provider,门面与业务不动;
- 组合替代继承:parent 链实现认证逻辑的层级复用;
- 生产实践:多 provider 时注意异常语义统一(哪个 provider 的失败都不泄露"用户存在与否"——UsernameNotFoundException 被 hide 折成 BadCredentials 是默认防枚举设计,别手贱关掉)。
💻 全栈开发专家
三层关系一句话:SecurityFilter 收到认证请求 → 交给 AuthenticationManager(接口)→ 其唯一常用实现 ProviderManager 持有多个 AuthenticationProvider 逐个尝试,谁支持谁认证。
职责拆解:
- AuthenticationManager:认证入口接口,
authenticate(Authentication)——框架调用方只认它; - ProviderManager:标准实现,内部是 AuthenticationProvider 列表,遍历找
supports(认证类型)的 provider 委托执行;都失败抛 ProviderNotFoundException/具体异常。可挂 parent AuthenticationManager(认证委托链,多模块共享场景);它还会把成功结果通知AuthenticationEventPublisher(失败计数/锁定的挂点); - AuthenticationProvider:具体认证逻辑单元——
DaoAuthenticationProvider(用户名密码+UserDetailsService)、JwtAuthenticationProvider(资源服务器验 JWT)、自定义短信/OAuth 后登录 provider。职责单一可插拔,这是 Spring Security 认证可扩展性的核心设计。
完整认证流程(用户名密码场景):
1. UsernamePasswordAuthenticationFilter 拦 /login,包装成
UsernamePasswordAuthenticationToken(未认证)
2. → AuthenticationManager(ProviderManager)
3. → DaoAuthenticationProvider.supports()=true
4. → UserDetailsService.loadUserByUsername() 查用户
→ PasswordEncoder.matches(rawPwd, encodedPwd) 验密码
5. 成功:返回已认证 Authentication(principal=UserDetails, authorities)
6. Filter 存入 SecurityContextHolder;失败:AuthenticationException
→ ExceptionTranslationFilter 翻译成 401/跳登录页自定义示例(手机号+验证码):写 AuthenticationToken(未认证载体)+ AuthenticationProvider(supports 你的 token,verify 验证码+查用户)+ 一个 Filter 构造 token 交给 manager——三个组件注册进 HttpSecurity,就能优雅接入整个体系,比在 Controller 里手写验证码登录规范得多。