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 对象上,只要请求没结束,数据就一直在。
相关推荐
Han_coding12082 小时前
从原理到实战:基于游标分页解决深分页问题(附源码方案)
java·服务器·数据库·spring boot·spring cloud·oracle
二等饼干~za8986682 小时前
碰一碰发视频系统源码开发搭建--技术分享
java·运维·服务器·重构·django·php·音视频
ss2732 小时前
线程池优雅关闭:线程池生命周期管理:四种关闭策略的实战对比
java·jvm·算法
不能只会打代码2 小时前
蓝桥杯--生命之树(Java)
java·算法·蓝桥杯·动态规划·贪心
多则惑少则明2 小时前
AI大模型实用(九)Java快速实现智能体整理(使用LangChain4j-agentic + Tool)
java·人工智能·springai·langchain4j
与遨游于天地2 小时前
深入了解 Java `synchronized`:从对象头到锁升级、线程竞争感知
java·开发语言·c#
天天摸鱼的java工程师2 小时前
Kafka 消息积压处理实战:百万级队列清空的优化技巧
java·后端
今晚务必早点睡2 小时前
Redis——快速入门第一课:了解redis
数据库·redis·缓存
今晚务必早点睡2 小时前
Redis——快速入门第三课:真实项目里的缓存完整流程
数据库·redis·缓存