开发日志:JWT 和 Token 在 后台 系统中的作用与实现
·
一、背景介绍
在 后台系统中,用户认证采用企业 SSO(单点登录) + JWT + 业务 Token 的多层架构。本文将详细讲解 JWT 和业务 Token 在在开发系统中的作用、关系以及具体实现方式。
二、整体架构图
text
┌─────────────────────────────────────────────────────────────────────────────┐ │ 用户登录流程 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ 用户 SSO中心 前端 后端 Redis │ │ │ │ │ │ │ │ │ │──登录──────▶│ │ │ │ │ │ │ │──验证──────▶│ │ │ │ │ │ │◀─返回JWT───│ │ │ │ │ │ │ │ │ │ │ │ │ │ │─GET /auth/xxx?tk=JWT──▶│ │ │ │ │ │ │ │ │ │ │ │ │ │─解析JWT────▶│ │ │ │ │ │ │ │ │ │ │ │ │ │─生成业务token─▶│ │ │ │ │ │ │ │ │ │ │ │ │ │─存储token→用户──▶│ │ │ │ │ │ │ │ │ │ │ │ │◀─返回业务token──│ │ │ │ │ │ │ │ │ │ │ │ │ │─存储到localStorage──│ │ │ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
三、JWT 和业务 Token 的区别
| 对比项 | JWT (id_token) | 业务 Token (APP_TOKEN) |
|---|---|---|
| 颁发者 | 企业 SSO 服务器 | 应用后端 |
| 格式 | JWT 标准格式(三段式) | 随机字符串 |
| 有效期 | SSO 控制(通常较短) | 可配置(如 6 小时) |
| 存储位置 | 前端临时使用,不持久化 | localStorage + Redis |
| 包含信息 | 用户身份信息(账号、邮箱等) | 用户信息的 Redis Key |
| 作用 | 证明"你是谁" | 证明"你已登录应用" |
| 验证方式 | 后端解析 JWT 获取用户信息 | 后端从 Redis 查 token |
四、JWT 的作用与处理
4.1 JWT 从哪里来?
用户点击登录后,前端跳转到企业 SSO 登录页:
javascript
// 伪代码示例
window.location.href = `https://sso.company.com/...&redirect_uri=${redirectUri}`
登录成功后,SSO 重定向回前端,URL 中携带 id_token:
text
http://localhost:xxxx/loginSSO#id_token=eyJhbGciOiJSUzI1NiIs...
4.2 前端如何提取 JWT?
javascript
// 登录回调页面
const params = window.location.href.split("#")[1];
const id_token = new URLSearchParams(params).get("id_token");
if (id_token) {
this.userLogin(); // 调用后端接口
}
4.3 后端如何解析 JWT?
java
// JWT 解析工具类
public JSONObject parseJwtBody(String token) {
// JWT 格式:header.payload.signature
String[] split = token.split("\\.");
Decoder decoder = Base64.getDecoder();
String body = new String(decoder.decode(split[1]));
return JSON.parseObject(body);
}
4.4 从 JWT 中提取用户信息
java
// 登录接口
JSONObject jwtJson = jwtUtil.parseJwtBody(jwtToken);
String preferredUsername = jwtJson.getString("preferred_username");
// 例如:zhangsan@company.com
// 提取用户账号
String account = preferredUsername.split("@")[0];
五、业务 Token 的生成与存储
5.1 生成业务 Token
java
// 生成随机字符串作为业务 token String token = StringUtil.createToken(account);
5.2 存储到 Redis
java
// Redis 存储工具类
public static void saveUserToken(String account, String token, String userInfoJson) {
Jedis jedis = jedisPool.getResource();
jedis.select(0);
// 业务 token 作为 key,用户信息 JSON 作为 value
// 有效期可配置
jedis.setex(token, TOKEN_EXPIRE_SECONDS, userInfoJson);
jedis.close();
}
Redis 存储结构:
| Key (业务 token) | Value (用户信息 JSON) |
|---|---|
abc123xyz... |
{"account_id":"zhangsan","fullName":"张三","roles":[1001,1004]} |
5.3 返回给前端
java
result.putData("token", token);
result.putData("user", userInfo);
六、前端如何存储和使用业务 Token
6.1 存储到 localStorage
javascript
// 登录成功回调
if (response.data.token) {
sessionStorage.setItem("APP_TOKEN", response.data.token);
localStorage.setItem("APP_TOKEN", response.data.token);
}
if (response.data.user) {
let user = response.data.user;
localStorage.setItem('APP_USER', JSON.stringify(user));
localStorage.setItem('APP_CURRENT_USER', user.account_id);
}
6.2 请求拦截器自动添加 Token
javascript
// Axios 请求拦截器
axios.interceptors.request.use(config => {
// 从 localStorage 获取 token
const token = localStorage.getItem('APP_TOKEN')
if (token) {
// 添加到请求头
config.headers.Authorization = token
}
return config
})
七、后端如何校验业务 Token
7.1 权限拦截器
java
// 权限拦截器
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 1. 获取请求头中的 token
String token = request.getHeader("Authorization");
// 2. 从 Redis 查询 token 是否存在
String userJson = redisUtil.get(token);
if (StringUtil.isEmpty(userJson)) {
// token 无效或已过期
response.setStatus(401);
return false;
}
// 3. 获取用户信息
UserAccount account = JSON.parseObject(userJson, UserAccount.class);
// 4. 检查是否有权限(如角色注解)
if (!hasPermission(account.getRoles(), requiredRoles)) {
response.setStatus(403);
return false;
}
return true;
}
7.2 角色权限校验
java
@RoleCheck({1001, 1004, 1009}) // 只有这些角色能访问
@PostMapping("/add")
public WebResult add(@RequestBody JobConfig job) {
// 业务逻辑
}
八、JWT vs 业务 Token 对比图
text
┌─────────────────────────────────────────────────────────────────┐
│ 登录时 │
├─────────────────────────────────────────────────────────────────┤
│ SSO中心 ──JWT──▶ 前端 ──JWT──▶ 后端 │
│ │ │
│ ▼ │
│ 解析 JWT │
│ │ │
│ ▼ │
│ 生成业务 Token │
│ │ │
│ ┌─────────────┴─────────────┐ │
│ ▼ ▼ │
│ 返回给前端 存入 Redis │
│ │ │ │
│ ▼ │ │
│ localStorage 存储 │ │
│ │ │ │
└────────────────────┼───────────────────────────┼────────────────┘
│ │
┌────────────────────┼───────────────────────────┼────────────────┐
│ ▼ │ │
│ 后续请求 │ │
│ │ │ │
│ 请求头带业务 Token │ │
│ │ │ │
│ ▼ │ │
│ 后端拦截器 │ │
│ │ │ │
│ └───────────从 Redis 查询────┘ │
│ │ │
│ ▼ │
│ 获取用户信息 │
│ │ │
│ ▼ │
│ 执行业务 │
└─────────────────────────────────────────────────────────────────┘
九、为什么需要两层 Token?
| 问题 | 解决方案 |
|---|---|
| JWT 有效期短,需要频繁刷新 | 业务 Token 有效期更长 |
| JWT 无法主动失效 | 业务 Token 存在 Redis,可主动删除 |
| 需要存储用户权限信息 | Redis 中存储完整的用户对象 |
| 分布式部署需要共享会话 | Redis 统一存储,多实例共享 |
十、关键代码文件汇总
| 文件 | 作用 |
|---|---|
LoginSSO.vue |
处理 SSO 登录回调,提取 JWT |
main.js |
拦截器,自动添加 token 到请求头 |
AuthController |
解析 JWT,生成业务 token |
RedisUtil.java |
Redis 存储业务 token |
AuthInterceptor.java |
拦截请求,校验 token |
TokenUtil.java |
生成随机业务 token |
十一、总结
| 概念 | 一句话总结 |
|---|---|
| JWT (id_token) | SSO 中心颁发的临时身份证,证明"你是谁" |
| 业务 Token | 应用自己颁发的门禁卡,证明"你可以进" |
| Redis | 存储业务 token 和用户信息的中间站 |
| localStorage | 前端存储业务 token 的地方 |
| 拦截器 | 后端校验业务 token 的关卡 |
核心流程:
-
用户登录企业 SSO → 获得 JWT
-
前端把 JWT 交给后端
-
后端解析 JWT,生成业务 token
-
业务 token 存 Redis,同时返回给前端
-
前端存 localStorage,后续请求自动带上
-
后端拦截器从 Redis 校验 token 有效性
一句话记住:JWT 是"临时门票",业务 Token 是"长期会员卡"! 🎯
更多推荐


所有评论(0)