spring Security 认证流程闭环与调用链路详解

下 面 我 们 以 小 明 的 authenticateUser 方 法 为 入 口 , 详 细 拆 解 认 证 执 行 的 ** 完 整 流 程 闭 环 ** , 并 深 入 分 析 Authentication authentication = authenticationManager.authenticate(...) 的 ** 调 用 链 路 ** , 同 时 说 明 ** 自 定 义 类 ** 在 其 中 的 作 用 。 代 码 块 中 汉 字 正 常 书 写 , 代 码 块 外 汉 字 间 保 持 2 个 空 格 。

一 、 完 整 认 证 流 程 闭 环

认 证 流 程 从 用 户 提 交 登 录 请 求 开 始 , 到 返 回 JWT 响 应 结 束 , 形 成 如 下 闭 环 :

用户提交登录请求

接收LoginRequest参数

创建UsernamePasswordAuthenticationToken

调用AuthenticationManager.authenticate

DaoAuthenticationProvider处理认证

UserDetailsService查询用户

PasswordEncoder验证密码

构建认证后Authentication对象

设置SecurityContextHolder

JwtService生成JWT令牌

封装JwtResponse响应

返回响应给用户

流 程 闭 环 详 细 步 骤 ( 结 合 代 码 )

1. 接 收 登 录 请 求 ( 自 定 义 DTO 入 口 )

用 户 通 过 前 端 提 交 用 户 名 和 密 码 , 请 求 体 被 @RequestBody LoginRequest loginRequest 接 收 。

** 自 定 义 类 LoginRequest 作 用 ** : 封 装 登 录 请 求 参 数 ( 用 户 名 、 密 码 ) , 代 码 如 下 :

复制代码
public class LoginRequest {  
    private String username;  // 用  户  名  
    private String password;  // 密  码  
    // Getters and Setters  
}  
2. 创 建 认 证 请 求 对 象 ( Spring Security 自 带 类 )

通 过 new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()) 创 建 认 证 请 求 对 象 。

** Spring Security 自 带 类 UsernamePasswordAuthenticationToken 作 用 ** :

  • 来 源 : org.springframework.security.authentication.UsernamePasswordAuthenticationToken
  • 作 用 : 封 装 用 户 名 密 码 认 证 请 求 , 初 始 状 态 下 authorities ( 权 限 ) 为 空 , 相 当 于 " 未 盖 章 的 通 行 证 " 。
3. 调 用 AuthenticationManager.authenticate() ( 核 心 调 用 链 路 , 下 文 详 解 )

将 上 述 UsernamePasswordAuthenticationToken 传 入 authenticationManager.authenticate() , 触 发 认 证 流 程 。

4. 认 证 成 功 后 设 置 安 全 上 下 文

认 证 成 功 返 回 填 充 完 权 限 的 Authentication 对 象 , 通 过 SecurityContextHolder.getContext().setAuthentication(authentication) 绑 定 到 当 前 线 程 。

5. 生 成 JWT 令 牌 ( 自 定 义 JwtService

调 用 jwtService.generateToken(authentication) 生 成 JWT , 用 于 后 续 无 状 态 认 证 。

** 自 定 义 类 JwtService 作 用 ** : 封 装 JWT 生 成 / 验 证 逻 辑 , 核 心 方 法 generateToken(Authentication authentication)authentication 中 提 取 用 户 名 、 权 限 等 信 息 , 按 规 则 生 成 签 名 令 牌 。

6. 封 装 响 应 数 据 ( 自 定 义 DTO )

authentication.getPrincipal() 获 取 UserDetailsImpl ( 自 定 义 用 户 详 情 类 ) , 提 取 用 户 ID 、 用 户 名 、 邮 箱 、 角 色 等 信 息 , 封 装 为 JwtResponse 返 回 。

** 自 定 义 类 作 用 ** :

  • UserDetailsImpl : 实 现 Spring Security 的 UserDetails 接 口 , 封 装 用 户 基 础 信 息 ( ID 、 用 户 名 、 邮 箱 ) 和 权 限 ( 角 色 / 权 限 字 符 串 ) , 代 码 示 例 :

    public class UserDetailsImpl implements UserDetails {
    private Long id;
    private String username;
    private String email;
    private String password;
    private Collection<? extends GrantedAuthority> authorities;
    // 实现UserDetails接口方法(getAuthorities()等)
    // Getters and Setters
    }

  • JwtResponse : 封 装 JWT 响 应 数 据 ( token 、 用 户 ID 、 用 户 名 、 邮 箱 、 角 色 列 表 ) , 代 码 示 例 :

    public class JwtResponse {
    private String token;
    private Long id;
    private String username;
    private String email;
    private List roles;
    // Constructor, Getters and Setters
    }

7. 返 回 响 应 与 异 常 处 理

认 证 成 功 返 回 ResponseEntity.ok(response) ; 失 败 ( 如 密 码 错 误 ) 捕 获 BadCredentialsException , 返 回 401 错 误 。

二 、 AuthenticationManager.authenticate(...) 调 用 链 路 详 解

这 段 代 码 是 认 证 流 程 的 ** 核 心 枢 纽 ** , 其 内 部 调 用 链 路 如 下 :

调 用 链 路 示 意 图

复制代码
authenticationManager.authenticate(token)  
  ↓  
AuthenticationManager  (  接  口  ,  默  认  实  现  ProviderManager  )  
  ↓  遍  历  注  册  的  AuthenticationProvider  ,  找  到  支  持  UsernamePasswordAuthenticationToken  的  Provider  
DaoAuthenticationProvider  (  Spring Security  自  带  ,  处  理  用  户  名  密  码  认  证  )  
  ↓  调  用  UserDetailsService.loadUserByUsername(username)  (  自  定  义  实  现  )  
UserServiceImpl  (  自  定  义  ,  实  现  UserDetailsService  )  
  ↓  从  数  据  库  查  询  用  户  信  息  ,  封  装  为  UserDetailsImpl  返  回  
DaoAuthenticationProvider  
  ↓  用  PasswordEncoder.matches(rawPassword, encodedPassword)  验  证  密  码  
PasswordEncoder  (  自  定  义  BCryptPasswordEncoder  )  
  ↓  验  证  通  过  ,  构  建  已  认  证  Authentication  对  象  
new UsernamePasswordAuthenticationToken(principal, credentials, authorities)  
  ↓  返  回  给  authenticationManager.authenticate()  
Authentication  (  认  证  成  果  )  

链 路 关 键 环 节 注 释

1. AuthenticationManager : 认 证 总 调 度 员
  • ** 来 源 ** : Spring Security 核 心 接 口 org.springframework.security.authentication.AuthenticationManager , 默 认 实 现 为 ProviderManager
  • ** 作 用 ** : 接 收 Authentication 认 证 请 求 , 协 调 多 个 AuthenticationProvider ( 认 证 提 供 者 ) 完 成 认 证 。
  • ** 入 参 ** : 未 认 证 的 UsernamePasswordAuthenticationToken ( 含 用 户 名 、 明 文 密 码 ) 。
  • ** 出 参 ** : 认 证 成 功 后 的 Authentication 对 象 ( 含 用 户 详 情 、 权 限 集 合 ) 。
2. DaoAuthenticationProvider : 认 证 执 行 者 ( Spring Security 自 带 )
  • ** 来 源 ** : org.springframework.security.authentication.dao.DaoAuthenticationProvider
  • ** 作 用 ** : 处 理 用 户 名 密 码 认 证 的 具 体 逻 辑 , 依 赖 UserDetailsServicePasswordEncoder
  • ** 核 心 步 骤 ** :
    a. 调 用 UserDetailsService.loadUserByUsername(username) 查 询 用 户 ;
    b. 用 PasswordEncoder 比 对 明 文 密 码 与 数 据 库 中 的 加 密 密 码 ;
    c. 验 证 通 过 后 , 构 建 已 认 证 的 UsernamePasswordAuthenticationToken ( 填 充 authorities 权 限 ) 。
3. UserDetailsService : 用 户 信 息 查 询 器 ( 自 定 义 实 现 )
  • ** 来 源 ** : Spring Security 核 心 接 口 org.springframework.security.core.userdetails.UserDetailsService

  • ** 自 定 义 实 现 类 UserServiceImpl 作 用 ** : 从 数 据 库 查 询 用 户 信 息 , 封 装 为 UserDetails 对 象 ( 此 处 为 UserDetailsImpl ) 返 回 。

  • ** 核 心 方 法 ** :

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // 1. 从数据库查询用户(自定义User实体类)
    User user = userRepository.findByUsername(username)
    .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
    // 2. 封装为UserDetailsImpl(含权限)
    return UserDetailsImpl.build(user);
    }

4. PasswordEncoder : 密 码 验 证 器 ( 自 定 义 配 置 )
  • ** 来 源 ** : Spring Security 核 心 接 口 org.springframework.security.crypto.password.PasswordEncoder , 此 处 用 自 定 义 BCryptPasswordEncoder

  • ** 作 用 ** : 验 证 明 文 密 码 与 数 据 库 中 BCrypt 加 密 密 码 是 否 匹 配 。

  • ** 调 用 代 码 ** :

    if (!passwordEncoder.matches(loginRequest.getPassword(), userDetails.getPassword())) {
    throw new BadCredentialsException("用户名或密码错误");
    }

5. 构 建 认 证 后 Authentication 对 象

认 证 通 过 后 , DaoAuthenticationProvider 创 建 ** 已 认 证 ** 的 UsernamePasswordAuthenticationToken

复制代码
// 参数:principal=用户详情(UserDetailsImpl),credentials=null(密码已清空),authorities=用户权限  
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());  

三 、 自 定 义 类 在 流 程 中 的 核 心 作 用 总 结

** 自 定 义 类 ** ** 作 用 描 述 ** ** 关 键 方 法 / 属 性 **
LoginRequest 封 装 登 录 请 求 参 数 ( 用 户 名 、 密 码 ) getUsername()getPassword()
UserDetailsImpl 实 现 UserDetails 接 口 , 封 装 用 户 详 情 ( ID 、 用 户 名 、 邮 箱 、 权 限 ) getAuthorities() ( 返 回 权 限 集 合 ) 、 getUsername()
UserServiceImpl 实 现 UserDetailsService , 从 数 据 库 查 询 用 户 并 封 装 为 UserDetailsImpl loadUserByUsername(String username)
JwtService 生 成 / 验 证 JWT 令 牌 , 封 装 JWT 算 法 逻 辑 generateToken(Authentication authentication)
JwtResponse 封 装 JWT 响 应 数 据 ( token 、 用 户 信 息 、 角 色 ) tokenidroles

💡 小 明 的 流 程 体 会

认 证 流 程 闭 环 像 一 场 " 身 份 验 证 演 习 " :

  1. 用 户 递 交 " 身 份 申 请 " ( LoginRequest ) ;
  2. 框 架 发 放 " 临 时 通 行 证 " ( UsernamePasswordAuthenticationToken ) ;
  3. " 认 证 官 " ( AuthenticationManager ) 调 度 " 审 查 员 " ( DaoAuthenticationProvider ) ;
  4. " 审 查 员 " 查 阅 " 档 案 库 " ( UserServiceImpl 查 数 据 库 ) , 用 " 密 码 对 比 仪 " ( PasswordEncoder ) 验 证 ;
  5. 通 过 后 发 放 " 正 式 身 份 证 " ( 认 证 后 Authentication ) , 存 入 " 工 作 台 " ( SecurityContextHolder ) ;
  6. 最 后 制 作 " 长 期 通 行 证 " ( JWT ) 返 还 用 户 。

整 个 过 程 中 , 自 定 义 类 像 " 业 务 胶 水 " , 将 Spring Security 的 通 用 认 证 能 力 与 项 目 的 数 据 库 、 业 务 逻 辑 绑 定 , 既 复 用 框 架 核 心 能 力 , 又 满 足 个 性 化 需 求 。

相关推荐
葫芦和十三2 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp3 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑3 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯4 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan6 小时前
多Agent之间的区别
后端
青石路8 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充8 小时前
1.面向对象设计思想
后端
IT_陈寒9 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro9 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗9 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端