SpringBoot实践:从验证码到业务接口的完整交互生命周期

一、 全链路交互流程 (The Full Lifecycle)

整个流程可以划分为四个阶段:人机验证 -> 身份认证 -> 令牌颁发 -> 业务鉴权

阶段 1:获取验证码 (初始化)

  1. 前端 :用户打开登录页,前端发起 GET /system/captcha/get 请求。
  2. 后端 (Controller)
    • 接口标记了 @PermitAll(安全放行)和 @TenantIgnore(租户放行)。
    • 调用 CaptchaService 生成背景图、滑块图、以及一个唯一标识 Token (UUID)
    • Redis 操作 :存储 Key=captcha:TokenValue=正确坐标(X=150)。此时状态隐含为 "未校验"
  3. 返回 :后端将 图片 Base64Token 返回给前端。

阶段 2:校验验证码 (动作验证)

  1. 前端 :用户拖动滑块,前端收集 Token用户轨迹坐标 ,发起 POST /system/captcha/check 请求。
  2. 后端 (Controller)
    • 接口同样标记了 @PermitAll
    • 逻辑计算 :从 Redis 取出正确坐标,计算 Math.abs(用户坐标 - 正确坐标)
    • 判定
      • 失败:返回错误,删除 Redis Key(强制刷新)。
      • 成功修改 Redis 中该 Key 的状态 (或生成加密凭证),标记为 "已通过 (Passed)"
  3. 返回:告诉前端"验证通过",前端准备发起登录。

阶段 3:登录与颁发令牌 (身份认证)

  1. 前端 :发起 POST /system/auth/login 请求,Payload 包含 { username, password, captchaVerification(Token) }
  2. 后端 (AuthService)
    • 步骤 A (凭证检查) :调用 CaptchaService,拿着 Token 去 Redis 查。不计算坐标,只检查该 Token 是否标记为"已通过"。如果不通过或不存在,阻断登录。
    • 步骤 B (密码验证):查询 MySQL,比对账号密码。
    • 步骤 C (令牌生成) :验证全部通过。生成 Access TokenRefresh Token
    • 数据落库
      • Access Token -> 存入 Redis (短效,用于鉴权) + MySQL (用于管理)。
      • Refresh Token -> 存入 MySQL (长效,用于续期)。
  3. 返回:将双 Token 返回给前端。

阶段 4:业务接口访问 (鉴权访问)

  1. 前端 :发起业务请求(如 /list-all-simple),Header 携带 Authorization: Bearer AccessToken

  2. 后端TokenAuthenticationFilter (核心过滤器)

    • 请求进入 TokenAuthenticationFilter
    • 查 Redis:验证 Token 是否存在且未过期。
    • 存上下文(关键动作) :如果 Redis 命中且有效,执行以下两步操作:
      1. 构建身份:将 Redis 数据转换为 LoginUser 对象。
      2. 填充环境:
        • SecurityContextHolder.setAuthentication(...):为了通过 Spring Security 的后续鉴权。
        • WebFrameworkUtils.setLoginUser(...):为了 Request 级别的日志记录。
  3. 后端AuthorizationFilter (权限守门员)

    • Spring Security 检查 SecurityContextHolder。发现里面有合法的用户身份。
    • 根据配置(如 .anyRequest().authenticated()),决定 放行。
  4. 后端 (Controller 层)

    • 执行业务逻辑,返回数据。

二、 核心疑问解答

1. 为什么要校验两次验证码?两次逻辑有什么不同?

这是为了平衡 "用户体验 / 服务器性能""业务安全性"

校验阶段 第一次校验 (/captcha/check) 第二次校验 (/login)
发生时机 用户松开鼠标/手指的瞬间 用户点击"登录"按钮后
校验内容 动作验证(数学计算) 计算 ` 用户坐标 - 真实坐标
核心目的 1. 即时反馈 :滑错了立马提示,不用等点击登录。 2. 挡箭牌:挡住 99% 的无效流量,防止恶意请求打到昂贵的"密码校验"逻辑上。 1. 业务闭环 :防止黑客跳过滑块步骤,直接调用登录接口。 2. 最终确认:确保这个登录请求是"合法人类"发起的。
资源消耗 极低 (纯内存计算) 极低 (Redis Key 查询)

总结: 第一次是为了体验和防刷 ,第二次是为了确保流程完整


2. TokenAuthenticationFilter 的验证逻辑是什么?

TokenAuthenticationFilter 是系统识别"你是谁"的唯一入口。它的验证逻辑简单而粗暴,核心就是 "查 Redis"

代码逻辑链:

  1. 提取 (Extract)

    • 从 HTTP Header 中获取 Authorization 字段。
    • 去掉 Bearer 前缀,得到纯净的 token 字符串。
    • (若为空,直接放行,交给后续 Security 拦截匿名访问)
  2. 查询 (Query)

    • 调用 OAuth2TokenApi -> OAuth2TokenService
    • 执行 redisTemplate.opsForValue().get(token)
  3. 判定 (Judge)

    • Redis 返回 null
      • 结论:Token 无效(可能是伪造的,也可能是过期被 Redis 自动删除了)。
      • 动作 :不报错,但SecurityContext 注入用户信息。请求变成"匿名用户"继续往下走(随即被 Security 拦截返回 401)。
    • Redis 返回 AccessTokenDO
      • 结论:Token 有效。
      • 动作 :解析出 userId, userType, scopes,构建 LoginUser 对象,写入 SecurityContextHolder

3. 为什么要设置 SecurityContext?

因为SpringSecurity框架不认accesstoken,SpringSecurity框架下的验证filterAuthorizationFilter 只看SecurityContext是否存有用户信息(代码中为Authentication对象,包含用户信息)。


4. 为什么既要设置 SecurityContext,又要设置 WebFrameworkUtils?

这是为了解决 组件职责不同 以及 生命周期不同步 的问题。

  • SecurityContextHolder
    • 作用给 Spring Security 看的 。后续的 AuthorizationFilter 只认这个上下文,有它才能鉴权通过。
    • 生命周期较短。Spring Security 为了防止线程池污染,通常会在过滤器链执行完毕前自动清空它。
  • WebFrameworkUtils (Request Attribute)
    • 作用给基础设施(如 AccessLogFilter)看的
    • 原因 :访问日志通常是在请求彻底结束(Response 返回)之后才记录的。此时 SecurityContext 可能已经被清空了。
    • 生命周期最长 。它挂载在 HttpServletRequest 对象上,只要请求没结束,数据就一直在。
相关推荐
zbguolei2 分钟前
MySQL根据身份证号码计算出生日期和年龄
数据库·mysql
我是苏苏4 分钟前
Web开发:C#通过ProcessStartInfo动态调用执行Python脚本
java·服务器·前端
JavaGuide8 分钟前
SpringBoot 官宣停止维护 3.2.x~3.4.x!
java·后端
瑶山31 分钟前
Spring Cloud微服务搭建一、Nacos配置和服务注册
spring·spring cloud·微服务·nacos
Victor35637 分钟前
Hibernate(39)Hibernate中如何使用拦截器?
后端
Victor35640 分钟前
Hibernate(40)Hibernate的命名策略是什么?
后端
tkevinjd1 小时前
动态代理
java
Knight_AL1 小时前
Spring 事务管理:为什么内部方法调用事务不生效以及如何解决
java·后端·spring
bcbnb1 小时前
iOS代码混淆技术深度实践:从基础到高级全面解析
后端
加洛斯1 小时前
SpringSecurity入门篇(2):替换登录页与config配置
前端·后端