上一篇我们已经把企业认证体系的全景图串起来了。
我们知道:
JWT
Redis
Spring Security
Gateway
在认证链路中的职责分别是什么。
但问题来了:
如果让你从零开始搭建一套认证系统,
你会怎么做?
今天我们从工程视角,
实现一套真实企业项目中常见的:
Spring Security + JWT + Redis 认证方案
一、需求分析
假设我们开发一个系统:
Android
iOS
Web
统一后端:
Spring Boot
要求:
- 用户登录
- Token认证
- 自动续签
- 强制下线
- 单设备登录
整体流程:
登录
↓
JWT
↓
访问接口
↓
JWT校验
↓
Redis校验
↓
业务接口
二、项目结构设计
企业项目一般会拆:
security
├── JwtUtil
├── JwtAuthenticationFilter
├── SecurityConfig
├── LoginService
├── RefreshTokenService
└── SecurityUtils
职责:
| 类 | 作用 |
|---|---|
| JwtUtil | JWT生成与解析 |
| JwtAuthenticationFilter | JWT认证过滤器 |
| SecurityConfig | Spring Security配置 |
| LoginService | 登录逻辑 |
| RefreshTokenService | 刷新Token |
| SecurityUtils | 获取当前用户 |
三、登录接口实现
登录接口:
@PostMapping("/login")
public LoginVO login(
@RequestBody LoginDTO dto) {
}
不要自己查数据库。
企业里 直接交给:
AuthenticationManager
处理。
Authentication authentication =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
dto.getUsername(),
dto.getPassword()
)
);
认证成功 拿到用户信息:
LoginUser loginUser =
(LoginUser) authentication.getPrincipal();
四、生成 JWT
创建工具类:
public class JwtUtil {
public static String createToken(
Long userId) {
}
}
生成:
String accessToken =
JwtUtil.createToken(userId);
JWT中一般放:
userId
username
role
expire
即可。
不要放:
密码
手机号
身份证
因为 JWT 只是编码。
不是加密。
五、生成 RefreshToken
登录成功后:
同时生成:
String refreshToken =
UUID.randomUUID().toString();
保存 Redis:
redisTemplate.opsForValue().set(
"refresh:" + userId,
refreshToken,
7,
TimeUnit.DAYS
);
返回客户端:
{
"accessToken":"xxx",
"refreshToken":"yyy"
}
六、JWT过滤器实现
企业里:
几乎都会写:
public class JwtAuthenticationFilter
extends OncePerRequestFilter
为什么?
因为:
OncePerRequestFilter
保证:
一次请求只执行一次
读取 Token:
String token =
request.getHeader(
"Authorization");
校验 JWT:
if(jwtUtil.validate(token)){
}
七、创建 Authentication
JWT验证成功后:
创建:
UsernamePasswordAuthenticationToken
例如:
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
loginUser,
null,
loginUser.getAuthorities()
);
放入:
SecurityContextHolder
SecurityContextHolder
.getContext()
.setAuthentication(auth);
此时 Controller里面: 就能获取当前用户。
八、获取当前用户
企业项目都会封装:
public class SecurityUtils
例如:
public static Long getUserId() {
Authentication auth =
SecurityContextHolder
.getContext()
.getAuthentication();
}
Controller:
直接:
Long userId =
SecurityUtils.getUserId();
即可。
九、Redis会话校验
很多教程:
到 JWT 就结束了。
但企业项目:
通常还要:
查 Redis
例如:
JWT:
解析出来:
userId = 1001
检查:
login:user:1001
是否存在。
不存在:
直接:
401
这样:
才能实现:
强制下线
账号冻结
风险控制
十、实现强制下线
管理员:
点击:
踢下线
删除:
redisTemplate.delete(
"login:user:1001"
);
此时:
JWT仍然没过期。
但:
Redis检查失败。
直接:
401 Unauthorized
实现:
立即下线
十一、实现单设备登录
用户:
手机A登录。
Redis:
login:user:1001
随后:
手机B登录。
覆盖:
login:user:1001
此时:
手机A:
JWT还在。
但:
Redis中的登录标识变了。
下一次请求:
401
实现:
单设备登录
十二、RefreshToken续签
AccessToken:
30分钟
RefreshToken:
7天
接口返回:
401
客户端:
自动调用:
POST /refresh
校验:
refreshToken
成功:
生成:
newAccessToken
返回。
客户端:
重放请求。
用户:
无感知
十三、企业项目中的真实流程
用户登录
│
▼
AuthenticationManager
│
▼
生成JWT
│
▼
生成RefreshToken
│
▼
Redis保存
│
▼
返回客户端
接口访问:
客户端
│
Bearer Token
▼
JwtFilter
│
JWT校验
▼
Redis校验
│
Authentication
▼
SecurityContextHolder
│
Controller
▼
Service
面试高频问题
Spring Security核心是什么?
Filter Chain
JwtFilter为什么继承OncePerRequestFilter?
保证一次请求只执行一次
Authentication是什么?
当前认证用户
SecurityContextHolder是什么?
当前线程用户上下文
为什么JWT还要Redis?
JWT负责认证
Redis负责会话控制
企业为什么不用纯JWT?
无法踢下线
无法主动失效
无法单设备登录
最终核心理解
到这里,
你已经把:
JWT
↓
Redis
↓
Spring Security
↓
认证体系
真正串起来了。
企业里的认证方案,
本质上是:
- Spring Security 提供认证框架
- JWT 提供身份认证
- Redis 提供会话生命周期管理
三者协同,共同完成:
企业级登录认证系统
下篇预告
下一篇我们继续:
《企业认证与安全体系(六):Gateway 为什么能统一鉴权?一篇讲透微服务认证体系》
深入讲透:
- 为什么微服务必须有网关
- Gateway 如何统一鉴权
- JWT 为什么适合网关
- 用户信息如何在服务间传递
- Gateway + JWT + Redis 企业架构
真正进入: