前后端分离跨域(jwt 模式详细介绍)

前后端分离跨域(jwt 模式详细介绍)
jwt 模式详细介绍

JWT(JSON Web Token)是一种无状态的身份认证协议,核心是「服务器将用户信息加密成 Token 发给客户端,客户端后续请求携带 Token,服务器仅通过解密 / 验签验证身份,无需存储任何状态」。它完美适配 Tornado 这类高性能异步框架,也是前后端分离、微服务场景的主流方案。

下面从「核心原理、结构解析、完整流程、实战实现、优缺点」五个维度,用最易懂的方式讲透 JWT 模式,包含 Python/Tornado + Vue/TS 的完整代码。


一、JWT 核心原理(一句话讲清)

JWT 是「客户端存储加密令牌,服务器无状态验证」:

前后端分离跨域(jwt 模式详细介绍)

  1. 用户登录成功后,服务器不存储任何数据,而是将用户核心信息(如 user_id、角色)加密生成 JWT Token;
  2. 服务器把 Token 返回给客户端(前端存在 Cookie/LocalStorage,App 存在本地);
  3. 客户端后续请求携带 Token(请求头 / 参数);
  4. 服务器收到 Token 后,通过密钥解密并验证签名(确认未被篡改),直接从 Token 中解析用户信息,完成身份验证。

核心特点:服务器无状态(无需查库 / 缓存)、跨域友好易扩展


二、JWT 结构解析(三段式字符串)

JWT Token 是由 . 分隔的三段 Base64 编码字符串,格式如下:

plaintext

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMDAxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzE0NTU3NjAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. 头部(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-decode

1. 后端(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]

五、JWT 模式的优缺点

优点

  1. 无状态:服务器无需存储 Token,水平扩展(多节点部署)无压力,适配 Tornado 高性能场景;
  2. 跨域友好:Token 放在请求头,适配前后端分离、跨域、移动端 App 等场景;
  3. 性能高:验证 Token 仅需解密 / 验签,无需查库 / 缓存,响应速度快;
  4. 轻量化:Token 体积小,传输成本低。

缺点

  1. 无法主动销毁:Token 生成后除非过期,否则无法强制失效(如用户改密 / 登出);
  2. → 解决方案:结合 Redis 维护 Token 黑名单(登出时加入黑名单,验证时先查黑名单)。
  3. 载荷不可存敏感信息:Header/Payload 是 Base64 编码,可解码,仅能存非敏感信息;
  4. 密钥安全要求高:密钥泄露会导致攻击者伪造 Token,生产环境需定期更换、放在环境变量中;
  5. 过期时间固定:无法像 Session 那样 “滑动过期”(可通过刷新 Token 解决:生成 access_token(短期)+ refresh_token(长期))。

六、生产环境优化建议

  1. 密钥管理:JWT_SECRET_KEY 放在环境变量(如 .env),绝对不要硬编码;
  2. Token 传输:仅在 HTTPS 下传输,防止 Token 被劫持;
  3. Token 存储:前端优先存在 HttpOnly Cookie(防 XSS),而非 LocalStorage;
  4. 刷新 Token:实现 access_token(2 小时)+ refresh_token(7 天),access_token 过期后用 refresh_token 刷新;
  5. 黑名单机制:Redis 存储过期 / 登出的 Token,验证时先查黑名单;
  6. 算法选择:生产环境优先用 RS256(非对称加密,私钥生成 Token,公钥验证),而非 HS256(对称加密)。

总结(核心要点)

  1. 核心特性:JWT 是无状态令牌,由 Header+Payload+Signature 组成,服务器通过验签验证身份,无需存储状态;
  2. 核心流程:登录生成 Token → 客户端存储 → 后续请求携带 → 服务器验签解析;
  3. 适配场景:完美适配 Tornado 异步高性能框架、前后端分离、微服务;
  4. 关键注意:载荷不存敏感信息、密钥绝对保密、结合 Redis 解决 “无法主动销毁” 问题。

文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有

最新文章

热门文章

本栏目文章