2. JWT 工作原理:签发流程、验证流程、Token 的存储方式
JWT(JSON Web Token)是目前分布式系统中最常用的无状态认证方案。理解其签发与验证流程,以及 Token 的客户端存储策略,是构建安全异步任务系统的前提。本章从原理到实践,拆解 JWT 的核心机制。
2.1 JWT 的结构与组成
一个 JWT 由三个 Base64URL 编码的部分组成,以点号(.)分隔:
header.payload.signature
| 部分 | 内容 | 示例 |
|---|---|---|
| Header | 令牌类型(typ)与签名算法(alg) | {"alg":"HS256","typ":"JWT"} |
| Payload | 声明(Claims):用户身份、权限、过期时间等 | {"sub":"user_123","role":"admin","exp":1718000000} |
| Signature | 对前两部分使用密钥生成的签名,防篡改 | HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload), secret) |
关键点:Payload 仅做 Base64URL 编码,不是加密。敏感信息(如密码、身份证号)严禁放入 JWT。签名只保证数据未被篡改,不保证内容不可见。
2.2 JWT 签发流程
签发流程发生在用户登录成功时,服务端生成 Token 并返回给客户端。典型步骤如下:
- 用户提交凭证:客户端发送用户名/密码到认证接口(如
/api/login)。 - 服务端验证身份:查询数据库,比对密码哈希。验证失败则返回 401。
- 构建 Payload:将用户 ID、角色、权限范围、签发时间(iat)、过期时间(exp)等放入 Claims。
- 生成签名:使用服务端密钥(Secret Key)对 Header + Payload 进行签名。
- 返回 Token:将完整的 JWT 字符串返回给客户端。
安全实践:密钥必须存储在服务端环境变量或密钥管理服务中,绝不可硬编码在代码或暴露给前端。推荐使用 RS256(非对称算法),私钥签名,公钥验证,避免密钥泄露风险。
2.3 JWT 验证流程
当客户端发起需要认证的请求时,服务端对 Token 进行验证。验证过程不依赖数据库会话,这是 JWT 无状态特性的核心。
- 提取 Token:从请求头
Authorization: Bearer <token>中获取 JWT。 - 解析 Header:解码 Base64URL,获取签名算法(alg)。
- 验证签名:使用服务端密钥(或公钥)重新计算签名,与 Token 中的签名比对。不一致则拒绝。
- 检查有效期:验证
exp(过期时间)是否在当前时间之后。过期则返回 401。 - 提取 Claims:验证通过后,从 Payload 中读取用户身份与权限,供后续业务逻辑使用。
常见漏洞:攻击者可能将
alg 改为 none 绕过签名验证。服务端必须强制校验算法白名单,拒绝 none 算法。
2.4 Token 的存储方式:LocalStorage vs Cookie
客户端收到 JWT 后,需要选择存储位置。两种主流方案各有优劣,直接影响安全性。
| 对比维度 | LocalStorage | Cookie(HttpOnly + Secure) |
|---|---|---|
| 访问方式 | JavaScript 可直接读取(localStorage.getItem) |
仅由浏览器自动发送,JS 无法读取(HttpOnly) |
| XSS 风险 | 高:攻击者通过 XSS 脚本可窃取 Token | 低:HttpOnly 属性阻止 JS 访问 |
| CSRF 风险 | 低:Token 不在请求中自动携带 | 高:Cookie 自动附加,需额外 CSRF Token 防护 |
| 跨域支持 | 需手动设置 Authorization 头,灵活 |
需配置 SameSite 与 CORS,较复杂 |
| 持久性 | 持久存储,需手动清除 | 可设置过期时间,支持 Session Cookie |
| 推荐场景 | SPA 应用 + 强 XSS 防护 + 短生命周期 Token | 传统 Web 应用 + CSRF 防护 + 敏感操作 |
最佳实践:对于异步任务系统,推荐使用 Cookie + HttpOnly + Secure + SameSite=Strict 存储 Refresh Token,而 Access Token 可放在内存变量中(非持久化),降低 XSS 与 CSRF 双重风险。
2.5 存储方式决策树
在实际项目中,可根据以下条件快速选择存储策略:
- 是否完全控制前端代码? 是 → 考虑 Cookie;否 → 考虑 LocalStorage。
- 是否担心 XSS 攻击? 是 → 优先 Cookie(HttpOnly)。
- 是否使用第三方 API 或跨域? 是 → LocalStorage 更灵活。
- 是否需要无状态认证? 是 → 两种均可,但 Cookie 需配合 CSRF 防护。
安全底线:无论选择哪种存储方式,永远不要在 URL 参数、日志、错误信息中泄露 JWT。Token 的生命周期应尽量短(建议 Access Token 15 分钟,Refresh Token 7 天),并配合黑名单机制处理登出场景。