☰
Spring Security认证机制详解
2026/10/14 15:49:47 网站建设 项目流程

Spring Security认证机制详解

定位:第 02 篇,讲透认证三要素模型、UserDetailsService 体系、密码编码、完整认证流程与认证结果的落地
适用版本:Spring Security 6.x(Spring Boot 3.x)


目录

  • 一、认证模型
  • 二、用户信息来源
  • 三、密码编码
  • 四、认证流程
  • 五、认证成功后的落地
  • 六、总结
  • 七、常见高频面试题

一、认证模型

1.1 三个核心抽象

抽象角色
Authentication认证令牌:装凭证(用户名/密码/Token)、认证后的身份与权限
AuthenticationManager认证入口:接收 Authentication,分发给合适的 Provider
AuthenticationProvider真正执行校验:核对凭证,产出"已认证"的 Authentication

1.2 Authentication 的两态

认证前(未认证):持有用户名 + 密码(凭证),authenticated = false 认证后(已认证):持有主体(UserDetails)+ 权限,凭证清除,authenticated = true

同一个对象承载"待验证材料"与"验证结果"两种状态——认证成功后凭证(密码)被抹去,避免在上下文中长期留存敏感信息。

1.3 Provider 链

AuthenticationManager(默认ProviderManager)持有多个 Provider,依次尝试谁能处理该认证类型——这支撑了"表单登录、JWT、LDAP 等多种认证方式共存"。


二、用户信息来源

2.1 UserDetailsService

publicinterfaceUserDetailsService{UserDetailsloadUserByUsername(Stringusername);}

职责单一:按用户名查用户。典型实现查数据库:

@ServicepublicclassDbUserDetailsServiceimplementsUserDetailsService{publicUserDetailsloadUserByUsername(Stringusername){SysUseru=userMapper.findByUsername(username);// 查库if(u==null)thrownewUsernameNotFoundException(username);returnUser.builder().username(u.getUsername()).password(u.getPassword())// 库中已是哈希.authorities(u.getRoles())// 权限/角色.build();}}

2.2 UserDetails

用户的安全视图:用户名、密码、权限集合,以及账号状态标志(未过期、未锁定、凭证未过期、已启用)——任一标志为 false 会抛对应异常拒绝登录。

2.3 内置实现与选择

实现场景
内存用户演示/测试
JDBC标准用户表结构
自定义(查库)生产主流

三、密码编码

3.1 PasswordEncoder

Stringencode(CharSequencerawPassword);// 存储用booleanmatches(CharSequenceraw,Stringencoded);// 校验用

3.2 BCrypt 与 Delegating

编码器说明
BCryptPasswordEncoder默认推荐:加盐哈希,每次结果不同(盐随机),暴力破解成本高
DelegatingPasswordEncoderBoot 默认:按密文前缀({bcrypt}/{sha256})路由到对应算法,支持存量算法平滑升级

3.3 纪律

① 永不存明文密码 ② 不自行实现哈希(盐/迭代次数有讲究) ③ 旧弱算法(MD5/SHA1)只用于兼容校验,新密码一律强算法 ④ 登录失败信息不区分"用户不存在"与"密码错误"(防枚举)

四、认证流程

以表单登录为例的完整链路:

① UsernamePasswordAuthenticationFilter 从请求提取用户名/密码 ② 组装未认证的 UsernamePasswordAuthenticationToken ③ 交给 AuthenticationManager(ProviderManager) ④ DaoAuthenticationProvider 处理: UserDetailsService.loadUserByUsername 查用户 PasswordEncoder.matches 比对密码 校验账号状态(锁定/过期/禁用) ⑤ 成功:生成已认证 Authentication(凭证清除、含权限) → SecurityContextHolder 存入 → 后续过滤器(授权)使用 ⑥ 失败:抛 BadCredentialsException → ExceptionTranslationFilter → 401/登录页

要点:

  • DaoAuthenticationProvider是表单登录的默认 Provider,桥接 UserDetailsService 与 PasswordEncoder;
  • 认证成功后Authentication里的主体就是UserDetails——业务代码取当前用户的来源;
  • 认证是一次性动作,后续请求靠会话/Token 恢复(第五节)。

五、认证成功后的落地

5.1 上下文建立与持久化

认证成功 → SecurityContextHolder.setAuthentication(...)(当前线程) → SecurityContextRepository 保存到会话(表单登录场景) 请求结束 → 上下文清理,避免线程复用串用户

5.2 后续请求的恢复

下一个请求带会话(或 Token) → 安全上下文过滤器从仓库恢复 Authentication 到 Holder → 授权过滤器据此判定权限

会话形态见 04 篇;无状态 JWT 形态见 05 篇(每次请求解析 Token 重建,不存会话)。


六、总结

  1. 三要素模型:Authentication(两态令牌)、AuthenticationManager(分发)、AuthenticationProvider(校验);Provider 链支撑多认证方式共存。
  2. UserDetailsService:按用户名查用户的单一职责接口,生产实现查库返回 UserDetails(含账号状态标志)。
  3. 密码编码:BCrypt 加盐哈希是默认;DelegatingPasswordEncoder 支持算法平滑升级;不存明文、错误信息防枚举。
  4. 认证流程:过滤器提取凭证 → Token → ProviderManager → DaoAuthenticationProvider(查用户 + 比密码 + 验状态)→ 成功存上下文 / 失败 401。
  5. 落地:认证成功存 SecurityContextHolder 并持久化到会话(或无状态化),后续请求恢复上下文供授权使用。

七、常见高频面试题

1. Spring Security 的认证流程是怎样的?

要点:以表单登录为例——认证过滤器提取用户名密码,组装未认证的 Authentication(UsernamePasswordAuthenticationToken),交给 AuthenticationManager(ProviderManager)分发给 Provider;DaoAuthenticationProvider 通过 UserDetailsService 查用户、用 PasswordEncoder 比对密码、校验账号状态;成功则生成已认证的 Authentication(清除凭证、含权限)存入 SecurityContextHolder,失败抛异常经 ExceptionTranslationFilter 转 401/登录页。

2. Authentication 对象包含什么?为什么说有"两态"?

要点:Authentication 是认证令牌,承载凭证、主体(身份)与权限。两态:认证前——持有用户名密码等凭证,authenticated=false;认证成功后——持有主体(UserDetails)与权限,凭证被清除,authenticated=true。同一对象既当"待验证材料"又当"验证结果",成功后清除凭证避免敏感信息长期留存。

3. UserDetailsService 的作用?怎么自定义?

要点:按用户名加载用户的单一职责接口,唯一方法 loadUserByUsername 返回 UserDetails。自定义实现(生产主流):注入数据访问层,按用户名查库,用户不存在抛 UsernameNotFoundException,存在则用 User.builder 组装(用户名、哈希密码、权限、账号状态)。Spring 只用它取用户,密码比对由 DaoAuthenticationProvider + PasswordEncoder 完成。

4. UserDetails 和自定义用户实体是什么关系?

要点:UserDetails 是 Security 的用户安全视图——用户名、密码、权限集合、账号状态标志(未过期/未锁定/凭证未过期/启用);业务实体(如 SysUser)是领域模型,含业务字段。关系:UserDetailsService 负责把业务实体转换成 UserDetails(或让实体实现 UserDetails)。分离二者避免把领域模型直接暴露给安全框架。

5. 密码为什么要用 BCrypt 存储?DelegatingPasswordEncoder 解决什么?

要点:明文存储泄露即全毁,普通哈希可被彩虹表/暴力破解;BCrypt 是加盐慢哈希——每次加密结果不同(随机盐内置于密文)、计算成本高,抗暴力破解。DelegatingPasswordEncoder 按密文前缀({bcrypt}、{sha256})路由到对应算法,让系统能校验存量旧算法密码,同时新密码用更强算法——支持算法平滑升级而不强制全员改密。

6. DaoAuthenticationProvider 做了什么?

要点:表单/Basic 认证的核心 Provider,桥接三个部件:调 UserDetailsService.loadUserByUsername 获取用户;用 PasswordEncoder.matches 比对提交密码与库中哈希;校验账号状态(锁定/过期/禁用,抛对应异常)。全部通过则产出已认证 Authentication。它是默认注册的 Provider,自定义认证方式时新增自己的 Provider 与其并列。

7. 认证成功后用户信息存在哪?业务代码怎么取当前用户?

要点:存入 SecurityContextHolder(默认 ThreadLocal 的 SecurityContext)。业务代码取当前用户:SecurityContextHolder.getContext().getAuthentication().getPrincipal()(通常是 UserDetails);控制器方法可直接注入 Authentication/Principal 参数;模板层有 sec 标签。注意异步线程默认拿不到(上下文不跨线程),需传递或配置继承策略。

8. 登录失败时,为什么不能提示"用户不存在"?

要点:区分"用户不存在"与"密码错误"会帮助攻击者枚举有效用户名(账号探测)。安全做法:两者统一返回"用户名或密码错误",且响应时间尽量一致(对不存在的用户也做一次哈希比对,防时间侧信道)。同理登录要有失败次数与锁定,防暴力破解。

9. 多种认证方式(表单 + JWT + Basic)如何共存?

要点:ProviderManager 持有多个 AuthenticationProvider,按顺序尝试处理对应的 Authentication 类型;不同认证过滤器(表单、Basic、自定义 JWT 过滤器)各自提取凭证组装对应的 Token 交给 Manager。也可用多条 SecurityFilterChain 按路径分开配置。共存的关键是"过滤器负责提取,Provider 负责校验"的职责分离。

10. 认证和授权在流程上是什么关系?

要点:先后关系且数据传递。认证先执行——确定"你是谁",产出含权限的 Authentication 存入安全上下文;授权随后——从上下文读取身份与权限,按规则(角色/权限/表达式)判定"你能否访问"。没有认证就没有授权依据;认证失败(401)与授权失败(403)是不同阶段的不同异常。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询