反馈

JWT 深度解析:结构、签名验证与常见误区

JWT(JSON Web Token)是一种自包含(self-contained)的身份令牌格式:把用户身份信息和签名打包成一个字符串,服务端不需要查数据库或 Session 存储就能验证它没被篡改。因为这个特性,JWT 成了前后端分离系统、微服务之间做身份验证的主流方案。


目录

  1. JWT 是什么
  2. 三段结构详解
  3. 签名算法:HS256 对称 vs RS256 非对称
  4. 验证流程
  5. JWT 和 Session 的区别
  6. 常见误区
  7. 代码示例

1. JWT 是什么

一个 JWT 长这样,三段用 . 拼接:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFsaWNlIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

拆开是 Header.Payload.Signature 三段,每段各自做 Base64URL 编码(注意不是加密,任何人都能解码出内容,只是签名保证没被改过)。

2. 三段结构详解

Header(头部):声明用的算法和类型。

{ "alg": "HS256", "typ": "JWT" }

Payload(载荷):实际数据,比如用户 ID、过期时间。有几个标准字段(都是可选的):

字段 含义
sub 主体,通常是用户 ID
iat 签发时间(Issued At)
exp 过期时间(Expiration)
iss 签发者(Issuer)
aud 接收方(Audience)

Signature(签名):对前两段的签名,防止内容被篡改:

signature = HMAC-SHA256(base64url(header) + "." + base64url(payload), secret)

3. 签名算法:HS256 对称 vs RS256 非对称

HS256 RS256
密钥类型 对称(一份密钥) 非对称(公钥+私钥)
签发方和验证方 必须共享同一个密钥 验证方只需要公钥
典型场景 单体应用、自己签发自己验证 微服务、第三方验证(如 OAuth)
密钥泄露风险 泄露即可伪造任意 token 私钥泄露才能伪造,公钥泄露无影响

多服务架构里,如果每个微服务都要验证 token,用 RS256 更合适:只把公钥分发出去,私钥留在签发服务里,泄露风险小很多。

4. 验证流程

  1. 拿到 token,按 . 拆成三段
  2. 用 header 里声明的算法,对 header.payload 重新计算一次签名
  3. 和 token 自带的签名段比较,不一致就拒绝
  4. 检查 exp 是否已过期
  5. 通过后,从 payload 里取出 sub 等字段,当作已验证的用户信息使用

关键点:第 2 步里"用什么算法"必须由服务端自己决定,不能读 token header 里声明的算法直接用——这是真实发生过的漏洞(alg: none 攻击),攻击者把算法声明改成"无签名",如果验证代码没做防御就会直接放行。

5. JWT 和 Session 的区别

Session JWT
状态存储 服务端(Redis/数据库) 客户端自己带着(无状态)
服务端要不要查存储 每次请求都要查 不用查,验证签名即可
多服务共享 需要共享 Session 存储 只要共享密钥/公钥
主动失效(登出) 直接删 Session 做不到真正的主动失效(见下)
存储开销 服务端有状态开销 无状态,但 token 本身更大

JWT 最大的短板就是没法主动失效:token 一旦签发,在过期之前始终有效,服务端无法像删 Session 一样让它立刻失效。常见的补救方案是维护一个"黑名单"或者缩短过期时间配合 Refresh Token,但这已经是在往有状态的方向妥协了。

6. 常见误区

误区一:JWT 是加密的,可以放敏感信息 错。Payload 只是 Base64URL 编码,不是加密,任何人拿到 token 都能直接解码看到内容。不要往里塞密码、身份证号等敏感数据。

误区二:不设过期时间更方便 不设 exp 意味着 token 一旦泄露就永久有效,是明确的安全隐患。生产环境应该设较短的过期时间,配合 Refresh Token 做续期。

误区三:JWT 能完全替代 Session,而且更安全 JWT 解决的是"无状态验证"问题,不是"更安全"——它没法主动失效就是明确的代价。需要频繁登出、封禁账号的场景,纯 JWT 方案会很麻烦。

7. 代码示例

// Node.js,用 jsonwebtoken 库
const jwt = require('jsonwebtoken');

const token = jwt.sign({ sub: '1234', name: 'Alice' }, 'my-secret', { expiresIn: '2h' });

try {
  const payload = jwt.verify(token, 'my-secret');
  console.log(payload);
} catch (e) {
  console.log('验证失败:', e.message);
}

在线 JWT 解码验证工具:JWT解码验证