jwt 模式详细介绍
JWT(JSON Web Token)是一种无状态的身份认证协议,核心是「服务器将用户信息加密成 Token 发给客户端,客户端后续请求携带 Token,服务器仅通过解密 / 验签验证身份,无需存储任何状态」。它完美适配 Tornado 这类高性能异步框架,也是前后端分离、微服务场景的主流方案。
下面从「核心原理、结构解析、完整流程、实战实现、优缺点」五个维度,用最易懂的方式讲透 JWT 模式,包含 Python/Tornado + Vue/TS 的完整代码。
一、JWT 核心原理(一句话讲清)
JWT 是「客户端存储加密令牌,服务器无状态验证」:

- 用户登录成功后,服务器不存储任何数据,而是将用户核心信息(如 user_id、角色)加密生成 JWT Token;
- 服务器把 Token 返回给客户端(前端存在 Cookie/LocalStorage,App 存在本地);
- 客户端后续请求携带 Token(请求头 / 参数);
- 服务器收到 Token 后,通过密钥解密并验证签名(确认未被篡改),直接从 Token 中解析用户信息,完成身份验证。
核心特点:服务器无状态(无需查库 / 缓存)、跨域友好、易扩展。
二、JWT 结构解析(三段式字符串)
JWT Token 是由 . 分隔的三段 Base64 编码字符串,格式如下:
plaintext
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMDAxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzE0NTU3NjAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c1. 头部(Header)
- 内容:指定加密算法(如 HS256)和 Token 类型(JWT);
- 示例(JSON):
- json
- { "alg": "HS256", // 签名算法:HMAC SHA256 "typ": "JWT" // Token 类型 }
- 处理:Base64 编码(可解码,非加密),构成 Token 第一段。
2. 载荷(Payload)
- 内容:存储非敏感的用户核心信息(如 user_id、用户名)+ 过期时间(exp);
- 内置标准字段(可选):
- 表格
- 字段含义示例exp过期时间(时间戳)1714557600(2026-05-01)iss签发者"https://your-api.com"sub主题(用户 ID)1001
- 自定义字段:如 username、roles 等;
- 示例(JSON):
- json
- { "user_id": 1001, "username": "admin", "exp": 1714557600 }
- 处理:Base64 编码(可解码,绝对不能存敏感信息如密码),构成 Token 第二段。
3. 签名(Signature)
- 作用:验证 Token 未被篡改,是 JWT 安全的核心;
- 生成规则:
- plaintext
- HMACSHA256( base64UrlEncode(Header) + "." + base64UrlEncode(Payload), 服务器密钥(绝对保密) )
- 处理:生成的哈希值构成 Token 第三段。
关键安全点
- Header 和 Payload 是 Base64 编码(可解码),仅签名是加密的;
- 服务器密钥绝对不能泄露(否则攻击者可伪造 Token);
- 载荷中绝对不能存敏感信息(如密码、token 密钥)。
三、JWT 完整工作流程(Tornado + Vue/TS)
预览
查看代码
生成失败,请重试
sequenceDiagram participant 客户端(Vue+TS) participant 服务器(Tornado) participant 数据库 # 1. 登录请求 客户端(Vue+TS)->>服务器(Tornado): POST /api/login (用户名+密码) 服务器(Tornado)->>数据库: 验证用户名密码 数据库-->>服务器(Tornado): 验证通过(返回用户信息) # 2. 生成 JWT Token 服务器(Tornado)->>服务器(Tornado): 用密钥生成 JWT(包含user_id/exp) 服务器(Tornado)-->>客户端(Vue+TS): 返回 JWT Token # 3. 客户端存储 Token 客户端(Vue+TS)->>客户端(Vue+TS): 存储 Token(Cookie/LocalStorage) # 4. 后续请求 客户端(Vue+TS)->>服务器(Tornado): GET /api/profile (请求头携带 Token) 服务器(Tornado)->>服务器(Tornado): 验证 Token 签名+过期时间 服务器(Tornado)-->>客户端(Vue+TS): 返回用户信息(无需查库)四、实战实现(Tornado + Vue/TS)
前置依赖
bash
运行
# 后端(Tornado)pip install tornado pyjwt cryptography# 前端(Vue+TS)npm install jwt-decode @types/jwt-decode1. 后端(Tornado)实现 JWT
python
运行
import jwtimport timeimport tornado.webimport tornado.ioloopfrom typing import Dict, Any# JWT 配置(生产环境需放在环境变量中)JWT_SECRET_KEY = "your_strong_secret_key_123456" # 绝对保密!JWT_ALGORITHM = "HS256"JWT_EXPIRE_SECONDS = 7200 # 2小时过期class JWTUtil: """JWT 工具类:生成/验证 Token""" @staticmethod def generate_token(user_info: Dict[str, Any]) -> str: """生成 JWT Token""" # 载荷:仅存非敏感核心信息 payload = { "user_id": user_info["id"], "username": user_info["username"], "roles": user_info["roles"], "exp": int(time.time()) + JWT_EXPIRE_SECONDS # 过期时间(时间戳) } # 生成 Token token = jwt.encode(payload, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM) return token @staticmethod def verify_token(token: str) -> Dict[str, Any] | None: """验证 Token,返回解析后的载荷(失败返回 None)""" try: # 验证签名 + 过期时间(exp 字段自动校验) payload = jwt.decode( token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM], options={"verify_exp": True} # 强制校验过期时间 ) return payload except jwt.ExpiredSignatureError: # Token 过期 return None except jwt.InvalidSignatureError: # 签名错误(被篡改) return None except Exception: # 其他错误(格式错误等) return None# 登录 Handlerclass LoginHandler(tornado.web.RequestHandler): async def post(self): # 1. 获取前端参数 username = self.get_body_argument("username", "") password = self.get_body_argument("password", "") # 2. 模拟验证用户(实际需查数据库) if username == "admin" and password == "123456": user_info = { "id": 1001, "username": "admin", "roles": ["admin"] } # 3. 生成 JWT Token token = JWTUtil.generate_token(user_info) # 4. 返回 Token(前端可存在 Cookie/LocalStorage) self.write({ "code": 0, "msg": "登录成功", "data": {"token": token} }) else: self.set_status(401) self.write({"code": -1, "msg": "用户名或密码错误"})# 验证 JWT 的装饰器(通用中间件)def jwt_auth_required(func): """装饰器:验证 JWT Token""" async def wrapper(self, *args, **kwargs): # 1. 从请求头获取 Token(格式:Bearer ) auth_header = self.request.headers.get("Authorization", "") if not auth_header or not auth_header.startswith("Bearer "): self.set_status(401) self.write({"code": 401, "msg": "未携带 Token"}) return # 2. 提取 Token 并验证 token = auth_header.split(" ")[1] payload = JWTUtil.verify_token(token) if not payload: self.set_status(401) self.write({"code": 401, "msg": "Token 过期或无效"}) return # 3. 将用户信息挂载到 handler 上,供后续使用 self.current_user = payload # 4. 执行原函数 await func(self, *args, **kwargs) return wrapper# 受保护的接口(需要登录)class ProfileHandler(tornado.web.RequestHandler): @jwt_auth_required async def get(self): # 从 Token 载荷中直接获取用户信息(无需查库) user_info = { "user_id": self.current_user["user_id"], "username": self.current_user["username"], "roles": self.current_user["roles"] } self.write({"code": 0, "data": user_info})# 跨域配置(适配 Vue 前端)class BaseHandler(tornado.web.RequestHandler): def set_default_headers(self): self.set_header("Access-Control-Allow-Origin", "http://localhost:8080") self.set_header("Access-Control-Allow-Credentials", "true") self.set_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") self.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization") def options(self): self.set_status(204) self.finish()# 继承 BaseHandler 实现跨域class LoginHandler(BaseHandler, LoginHandler): passclass ProfileHandler(BaseHandler, ProfileHandler): pass# 启动服务def make_app(): return tornado.web.Application([ (r"/api/login", LoginHandler), (r"/api/profile", ProfileHandler), ], debug=True)if __name__ == "__main__": app = make_app() app.listen(8888) print("Tornado JWT Server running on http://127.0.0.1:8888") tornado.ioloop.IOLoop.current().start() 2. 前端(Vue + TS)实现 JWT
步骤 1:封装 JWT 工具类(src/utils/jwt.ts)
typescript
运行
import jwtDecode from 'jwt-decode';// Token 存储键名const TOKEN_KEY = 'JWT_TOKEN';// 解析后的 Token 载荷类型export interface JwtPayload { user_id: number; username: string; roles: string[]; exp: number; // 过期时间戳(秒)}/** * 存储 Token * @param token JWT Token */export const setToken = (token: string) => { localStorage.setItem(TOKEN_KEY, token); // 或存在 Cookie(更安全):import Cookies from 'js-cookie'; Cookies.set(TOKEN_KEY, token, { httpOnly: false, expires: 1/12 });};/** * 获取 Token */export const getToken = (): string | null => { return localStorage.getItem(TOKEN_KEY);};/** * 删除 Token */export const removeToken = () => { localStorage.removeItem(TOKEN_KEY);};/** * 解析 Token 载荷(仅前端展示,不做验证) */export const decodeToken = (): JwtPayload | null => { const token = getToken(); if (!token) return null; try { return jwtDecode(token); } catch (e) { return null; }};/** * 前端检查 Token 是否过期(仅提示,最终以后端验证为准) */export const isTokenExpired = (): boolean => { const payload = decodeToken(); if (!payload) return true; // 转换为毫秒比较 const expireTime = payload.exp * 1000; return Date.now() > expireTime;}; 步骤 2:封装请求(src/utils/request.ts)
typescript
运行
import axios from 'axios';import { getToken, removeToken } from './jwt';import { ElMessage } from 'element-plus';const request = axios.create({ baseURL: 'http://localhost:8888/api', timeout: 5000});// 请求拦截器:携带 Tokenrequest.interceptors.request.use( (config) => { const token = getToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error));// 响应拦截器:处理 Token 过期request.interceptors.response.use( (response) => response.data, (error) => { if (error.response?.status === 401) { // Token 过期/无效:清空存储,跳转登录页 removeToken(); ElMessage.error('登录状态已过期,请重新登录'); if _(window.location.pathname !== '/login') { _window.location.href = '/login'; } } return Promise.reject(error); });export { request };步骤 3:登录 / 获取用户信息(组件示例)
vue
<!-- src/views/Login.vue -->[xss_clean]import { ref } from 'vue';import { useRouter } from 'vue-router';import { request } from '@/utils/request';import { setToken } from '@/utils/jwt';const router = useRouter();const form = ref({ username: '', password: ''});const handleLogin = async () => { try { const res = await request.post('/login', form.value); if (res.code === 0) { // 存储 Token setToken(res.data.token); ElMessage.success('登录成功'); router.push('/profile'); } } catch (error) { ElMessage.error('登录失败'); }};[xss_clean] vue
<!-- src/views/Profile.vue -->[xss_clean]import { ref, onMounted } from 'vue';import { request } from '@/utils/request';import { decodeToken } from '@/utils/jwt';const userInfo = ref(null);onMounted(async () => { try { const res = await request.get('/profile'); if (res.code === 0) { userInfo.value = res.data; } } catch (error) { console.error('获取用户信息失败', error); }});[xss_clean] 欢迎 {{ userInfo?.username }}
用户ID:{{ userInfo?.user_id }}
角色:{{ userInfo?.roles.join(',') }}
五、JWT 模式的优缺点
优点
- 无状态:服务器无需存储 Token,水平扩展(多节点部署)无压力,适配 Tornado 高性能场景;
- 跨域友好:Token 放在请求头,适配前后端分离、跨域、移动端 App 等场景;
- 性能高:验证 Token 仅需解密 / 验签,无需查库 / 缓存,响应速度快;
- 轻量化:Token 体积小,传输成本低。
缺点
- 无法主动销毁:Token 生成后除非过期,否则无法强制失效(如用户改密 / 登出);
- → 解决方案:结合 Redis 维护 Token 黑名单(登出时加入黑名单,验证时先查黑名单)。
- 载荷不可存敏感信息:Header/Payload 是 Base64 编码,可解码,仅能存非敏感信息;
- 密钥安全要求高:密钥泄露会导致攻击者伪造 Token,生产环境需定期更换、放在环境变量中;
- 过期时间固定:无法像 Session 那样 “滑动过期”(可通过刷新 Token 解决:生成 access_token(短期)+ refresh_token(长期))。
六、生产环境优化建议
- 密钥管理:JWT_SECRET_KEY 放在环境变量(如 .env),绝对不要硬编码;
- Token 传输:仅在 HTTPS 下传输,防止 Token 被劫持;
- Token 存储:前端优先存在 HttpOnly Cookie(防 XSS),而非 LocalStorage;
- 刷新 Token:实现 access_token(2 小时)+ refresh_token(7 天),access_token 过期后用 refresh_token 刷新;
- 黑名单机制:Redis 存储过期 / 登出的 Token,验证时先查黑名单;
- 算法选择:生产环境优先用 RS256(非对称加密,私钥生成 Token,公钥验证),而非 HS256(对称加密)。
总结(核心要点)
- 核心特性:JWT 是无状态令牌,由 Header+Payload+Signature 组成,服务器通过验签验证身份,无需存储状态;
- 核心流程:登录生成 Token → 客户端存储 → 后续请求携带 → 服务器验签解析;
- 适配场景:完美适配 Tornado 异步高性能框架、前后端分离、微服务;
- 关键注意:载荷不存敏感信息、密钥绝对保密、结合 Redis 解决 “无法主动销毁” 问题。
文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有