2、JWT工作原理:Token签发流程、客户端存储方式、服务端验证流程、无状态认证原理
JWT(JSON Web Token)是现代SaaS系统中应用最广泛的鉴权令牌标准。理解其工作原理,是构建安全、可扩展的鉴权链路的基础。本节将从Token的签发、存储、验证到无状态认证的核心思想,进行全链路拆解。
2.1 JWT的结构与组成
一个标准的JWT由三个部分通过“.”连接而成:Header(头部)、Payload(载荷)、Signature(签名)。其结构为:xxxxx.yyyyy.zzzzz。
| 组成部分 | 内容说明 | 是否可被客户端读取 | 是否可被客户端篡改 |
|---|---|---|---|
| Header | 声明令牌类型(typ: JWT)和签名算法(alg: HS256 / RS256) | 是(Base64解码) | 否(篡改后签名校验失败) |
| Payload | 存放声明(Claims),如用户ID(sub)、过期时间(exp)、发行者(iss)等 | 是(Base64解码) | 否(篡改后签名校验失败) |
| Signature | 对Header和Payload的签名,用于验证令牌的完整性和真实性 | 是(仅作为签名值) | 否(无密钥无法伪造) |
安全提示:Payload虽然经过Base64编码,但并未加密。切勿在Payload中存放密码、信用卡号等敏感信息。如需加密,应使用JWE(JSON Web Encryption)标准。
2.2 Token签发流程
Token签发发生在用户成功登录之后。服务端验证用户凭证(用户名/密码、OAuth授权码等)通过后,生成JWT并返回给客户端。
核心步骤:
- 用户认证:客户端提交凭证(如用户名+密码)到服务端。
- 凭证校验:服务端验证凭证有效性(如查询数据库、LDAP等)。
- 构建Payload:将用户标识、角色、权限范围、过期时间等写入Payload。
- 生成签名:使用服务端私钥(RS256)或共享密钥(HS256)对Header和Payload进行签名。
- 返回Token:服务端将完整的JWT字符串返回给客户端。
2.3 客户端存储方式
客户端收到JWT后,需要将其安全地存储起来,以便在后续请求中携带。不同的存储方式直接影响安全性。
| 存储方式 | 实现方法 | 安全性 | 适用场景 |
|---|---|---|---|
| localStorage / sessionStorage | 通过JavaScript直接存储 | 低(易受XSS攻击) | 仅用于开发环境或非敏感应用 |
| HttpOnly Cookie | 服务端设置Cookie,标记HttpOnly | 高(无法被JavaScript读取,防XSS) | 生产环境推荐,需配合CSRF防护 |
| 内存变量 | 存储在JS闭包或框架状态管理(如Vuex/Redux)中 | 中(刷新页面后丢失) | 配合刷新Token机制使用 |
安全警告:禁止将JWT存储在localStorage中用于生产环境。一旦页面存在XSS漏洞,攻击者可以轻松窃取Token,实现持久化登录劫持。推荐使用HttpOnly Cookie + CSRF Token的组合方案。
2.4 服务端验证流程
当客户端发起受保护资源的请求时,服务端需要对携带的JWT进行严格验证。验证通过后,才允许访问资源。
验证步骤:
- 提取Token:从请求头(Authorization: Bearer <token>)或Cookie中提取JWT。
- 解析结构:将JWT按“.”分割,分别获取Header、Payload、Signature。
- 校验签名:使用服务端密钥对Header和Payload重新计算签名,与传入的Signature比对。
- 校验声明:检查exp(过期时间)、nbf(生效时间)、iss(发行者)等是否有效。
- 提取用户信息:从Payload中获取用户ID、角色等,注入到请求上下文中。
2.5 无状态认证原理
无状态认证是JWT的核心优势之一。服务端不再需要维护Session会话信息,每个请求都携带了完整的用户认证信息。
| 对比维度 | 传统Session认证 | JWT无状态认证 |
|---|---|---|
| 服务端存储 | 需要存储Session ID与用户信息的映射 | 无需存储,Token自包含用户信息 |
| 扩展性 | 水平扩展需共享Session(如Redis) | 天然支持水平扩展,任何节点均可验证 |
| 性能 | 每次请求需查询Session存储 | 仅需计算签名,无需I/O查询 |
| 安全性 | Session ID泄露风险较低(随机字符串) | Token泄露后,在有效期内可被任意使用 |
| 主动失效 | 服务端可随时删除Session | 无法主动失效,需等待过期或维护黑名单 |
设计建议:在SaaS多租户场景中,无状态认证的优势尤为明显。每个微服务实例都可以独立验证Token,无需共享会话存储。但需注意,Token的过期时间不宜过长,并应配合刷新Token(Refresh Token)机制,在保证无状态的同时,兼顾安全性和用户体验。
2.6 无状态认证的挑战与应对
无状态认证并非银弹,在实际SaaS系统中,需要解决以下关键挑战:
- Token吊销问题:用户修改密码或管理员禁用账号后,已签发的Token仍然有效。解决方案:维护一个短TTL的Token黑名单(Redis),或使用短有效期Token + 刷新Token机制。
- Token体积膨胀:Payload中携带过多声明会导致Token体积变大,增加网络开销。解决方案:仅存放核心声明(sub, exp, roles),其他信息通过API查询。
- 密钥管理:使用对称密钥(HS256)时,所有服务必须共享同一密钥,存在泄露风险。解决方案:使用非对称算法(RS256/ES256),认证服务持有私钥,其他服务仅持有公钥。
实战避坑:不要在JWT的Payload中存放权限列表或租户ID列表。权限变更后,已签发的Token无法同步更新。正确的做法是在Payload中存放角色标识,服务端根据角色动态查询权限。