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

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

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

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

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

阶段 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 Token 和 Refresh 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 对象上,只要请求没结束,数据就一直在。
相关推荐
用户83562907805118 分钟前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我1234521 分钟前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒23 分钟前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记29 分钟前
对接群晖NAS,上传和删除踩坑记录
前端·后端
Mk5987834 分钟前
P4:把 Agent 的记忆搬进 Redis —— AgentScope-Java 工程化实战
后端
雨中飘荡的记忆35 分钟前
Claude Code 入门必会:@、!、Slash Commands,一篇讲清楚
人工智能·后端
吹什么轩35 分钟前
MySQL:数据库基础
数据库·mysql
FluxDB42 分钟前
用 Rust + GPUI 重做数据库客户端
数据库·mysql
苜蓿muxu43 分钟前
Java 字符串拼接:+、StringBuilder、StringBuffer的区别
java
摇滚侠1 小时前
《Java并发编程之美》第 4 章 Java 并发包中原子操作类原理剖析 阅读笔记 7
java·笔记·python