后端安全框架:Spring Security + JWT 认证详解
后端安全框架:Spring Security + JWT 认证详解
技术基础:Spring Boot 4 + Spring Security 6 + JWT
一、基础配置
SecurityConfiguration 核心组件
| 组件 | 说明 |
|---|---|
| PasswordEncoder | BCrypt 密码加密 |
| FormAndJsonLoginFilter | 登录过滤器 |
| SecurityFilterChain | 安全过滤链 |
配置要点:
– CSRF 禁用(前后端分离场景)
– Session 无状态(JWT 方案)
– 白名单配置(登录、刷新、文档等接口)
JWT 配置参数
| 参数 | 说明 |
|---|---|
| access-secret | 签名密钥 |
| access-expire-seconds | 30分钟(access JWT 有效期) |
| access-refresh-seconds | 1天(refresh JWT 有效期) |
白名单接口:
/v1/auth/login # 登录
/v1/auth/register # 注册
/v1/auth/refresh # 刷新 Token
/v3/api-docs/** # OpenAPI 文档
/actuator/health # 健康检查
二、核心概念
Spring Security 认证流程
请求 → JwtAuthFilter → 验证 JWT → 从 Redis 获取 UserDetails → 设置 SecurityContext
两个关键点
- JWT 只做身份标识:JWT 里没有 SecurityContext 信息
- SecurityContext 只在 Redis 里:JwtAuthFilter 验证 JWT 后,从 Redis 获取 UserDetails 设置到 SecurityContext
三、Filter 执行顺序
JwtAuthFilter → FormAndJsonLoginFilter → UsernamePasswordAuthenticationFilter
四、核心组件
1. JwtAuthFilter
继承 OncePerRequestFilter,核心功能:
- 检查白名单(跳过不需要认证的接口)
- 从 Header 获取 Token(Bearer Token)
- 验证 JWT 签名
- 从 Redis 获取 UserDetails,设置 SecurityContext
自动延期机制:
– 当 access JWT 剩余时间 < 50% 时
– 在 Response Header 中返回新的 access JWT
– 注意:延期的只是 JWT 的生命周期,Redis TTL 不变
2. FormAndJsonLoginFilter
继承 UsernamePasswordAuthenticationFilter,处理 /v1/auth/login:
- 支持 JSON 请求体
- 支持表单提交
3. LoginSuccessHandler
实现 AuthenticationSuccessHandler,登录成功后:
- 生成 JWT Token(userId, username, updateSeq, tokenUUID)
- 存储到 Redis(TTL = 1天)
- 返回 JSON 响应(包含 access JWT 和 refresh JWT)
4. LogoutCustomHandler
实现 LogoutHandler,登出时:
- 删除 Redis Key(用户必须重新登录)
- 清理 SecurityContext
5. Refresh Token Handler
接收 refreshToken(JWT),验证后:
- 读取用户表,获取当前 updateSeq
- 对比 JWT 中的 updateSeq,不一致则返回 401
- 延期:延长 JWT 的生命周期(不换 tokenUUID)
- 返回新的 access JWT + refresh JWT
五、角色设计
| 角色 | 说明 |
|---|---|
| ROLE_SUPER_ADMIN | 超级管理员,拥有所有权限 |
| ROLE_ADMIN | 管理员,系统管理 |
| ROLE_VOLUNTEER | 志愿者 |
| ROLE_USER | 普通用户,基本功能 |
接口认证注解
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin")
public void admin() {}
@PreAuthorize("isAuthenticated()")
@GetMapping("/user")
public void user() {}
六、JWT 数据结构
| 字段 | 说明 |
|---|---|
| userId | 用户 ID |
| username | 用户名 |
| updateSeq | 秒级时间戳(来自 update_at 字段) |
| tokenUUID | 随机 UUID(同一用户多端登录) |
updateSeq 来源:用户的
update_at字段转为秒级时间戳
两种 JWT
| 类型 | 有效期 | 用途 |
|---|---|---|
| access JWT | 30分钟 | 每次 API 请求都带 |
| refresh JWT | 1天 | 刷新 token、重新打开浏览器 |
七、Redis Key 设计
- Key:
security:{username}:{updateSeq}:{tokenUUID} - 示例:
security:admin:1743840000123:550e8400-e29b-41d4-a716 - TTL:1天(access-refresh-seconds)
- 存储内容:Spring Security UserDetails 子类
为什么 Redis TTL = 1天?
– 减少 Redis key 数量
– refresh JWT 有效期内都能找到用户信息
– 超过 1 天需要重新登录
为什么 JWT 里没有 SecurityContext?
- JWT 是公开的(base64 可解码),不适合存敏感信息
- SecurityContext 需要实时从数据库/Redis 获取最新状态
- 通过 updateSeq 实现旧 token 自动失效
八、Token 延期机制
自动延期(JwtAuthFilter)
- 触发条件:access JWT 剩余时间 < 50%(< 15分钟)
- 行为:Response Header 返回新的 access JWT
- 注意:Redis TTL 不变
主动刷新(Refresh Handler)
- 触发条件:前端收到 401 或重新打开浏览器
- 行为:返回新的 access JWT + refresh JWT,Redis TTL 延期
为什么需要自动延期?
– 用户持续使用时无需频繁刷新
– 减少前后端交互
– 保证用户状态定期更新
九、Token 失效场景
| 场景 | 失效原因 | 结果 |
|---|---|---|
| 登出 | 删除 Redis Key | 必须重新登录 |
| 强制下线 | 清理 Redis Key | 必须重新登录 |
| 密码修改 | updateSeq 更新 | 旧 token 不匹配 |
| access JWT 过期 | 用 refresh JWT 刷新 | 获取新 token |
| refresh JWT 过期 | Redis Key 无效 | 需要重新登录 |
十、接口示例
10.1 登录 /v1/auth/login
请求:
{
"username": "admin",
"password": "xxx"
}
响应:
{
"code": 200,
"data": {
"accessToken": "xxx",
"refreshToken": "xxx",
"expiresIn": 1800
}
}
10.2 刷新 /v1/auth/refresh
请求:
{
"refreshToken": "xxx"
}
响应:
{
"code": 200,
"data": {
"accessToken": "xxx",
"refreshToken": "xxx",
"expiresIn": 1800
}
}
注意:每次刷新都生成新的 JWT,但 tokenUUID 不变
10.3 登出 /v1/auth/logout
响应:
{
"code": 200,
"message": "登出成功"
}
📝 技术分享,代码改变世界