JWT 认证与安全:Token 签发、验证与最佳实践

FreeGuideOnline 最新 2026-06-13

JWT 认证与安全:Token签发、验证与最佳实践

JSON Web Token(JWT)已成为现代 Web 应用中实现无状态身份验证的主流方案。本文将带你从零开始掌握 JWT 的结构、签发、验证过程,并深入剖析常见的安全风险与防御策略,帮助你构建安全可靠的认证体系。

什么是 JWT

JWT 是一种紧凑且自包含的令牌格式,用于在各方之间以 JSON 对象形式安全传输信息。它的典型应用场景是授权,用户登录后,服务器生成一个 JWT 返回给客户端,后续请求只需携带该令牌,服务端即可验证身份并获取权限。

JWT 由三部分组成,以点号(.)分隔:

xxxxx.yyyyy.zzzzz

分别是:

  • Header(头部):声明类型与签名算法
  • Payload(载荷):存放声明(Claims),即需要传递的数据
  • Signature(签名):用于验证令牌未被篡改

它们的生成过程如下:

Base64UrlEncode(header) + "." + Base64UrlEncode(payload) + "." + Signature

JWT 的结构解析

头部通常是一个 JSON 对象,包含两个字段:alg(签名算法,如 HS256)和 typ(类型,通常为 JWT)。

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

该 JSON 经过 Base64Url 编码后形成 JWT 的第一部分。

Payload

载荷包含声明(Claims),有三种类型:

  • Registered claims:预定义的字段,如 iss(签发者)、sub(主题)、aud(受众)、exp(过期时间)、iat(签发时间)等。
  • Public claims:自定义的公开声明,建议在 IANA JSON Web Token Registry 注册或使用防冲突的命名空间。
  • Private claims:服务端与客户端约定的私有声明,用于传递业务数据。

示例:

{
  "sub": "1234567890",
  "name": "Alice",
  "admin": true,
  "iat": 1715000000,
  "exp": 1715003600
}

重要警告: 载荷仅被 Base64Url 编码,任何人都可以解码阅读。切勿在载荷中存放密码、信用卡号等敏感信息!

Signature

签名部分用于验证令牌的完整性和来源。生成公式为:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

如果使用非对称算法(如 RS256),则使用私钥签名,公钥验证。签名的存在使得任何对头部或载荷的修改都会导致签名不匹配,从而被检测到。


JWT 的签发(Token Issuance)

签发的核心步骤可概括为:

  1. 构造头部和载荷对象
  2. 使用 Base64Url 编码两者
  3. 用指定的算法和密钥生成签名
  4. 拼接形成完整 JWT 字符串

使用 Node.js + jsonwebtoken 库签发

安装依赖:

npm install jsonwebtoken

签发示例:

const jwt = require('jsonwebtoken');

const payload = {
  userId: 'user_001',
  role: 'editor'
};

const secret = 'your-256-bit-secret';

const options = {
  algorithm: 'HS256',   // 签名算法,默认HS256
  expiresIn: '2h',      // 过期时长,如 '2h', '1d', 60*60 等
  issuer: 'my-app',
  audience: 'my-app-client'
};

const token = jwt.sign(payload, secret, options);
console.log('JWT Token:', token);

jwt.sign() 会自动完成 Base64Url 编码、签名及拼接。其中 secret 是用于 HMAC 算法的共享密钥,必须保密且强度足够(至少 256 位随机值)。

签发时应设置的声明

  • exp:过期时间,必须设置,且不宜过长(通常 15 分钟至 2 小时)
  • iat:签发时间,用于判断令牌的签发时刻
  • nbf:生效时间,在此之前令牌无效
  • iss:签发者标识,便于多方验证来源
  • aud:受众,指定令牌的目标接收方

示例负载:

{
  "sub": "user@example.com",
  "role": "member",
  "iat": 1715000000,
  "exp": 1715007200,
  "iss": "auth-service",
  "aud": "api-gateway"
}

JWT 的验证(Token Verification)

服务端收到请求后,需对 JWT 进行严格验证,流程包括:

  1. 解析令牌,提取头部和载荷
  2. 验证签名,确保令牌未被伪造或篡改
  3. 检查标准声明:exp 是否过期、nbf 是否已生效、iss 是否可信、aud 是否匹配等
  4. 验证通过后,从载荷中取出所需信息进行授权

使用 jsonwebtoken 验证

const jwt = require('jsonwebtoken');

function verifyToken(token) {
  try {
    const decoded = jwt.verify(token, 'your-256-bit-secret', {
      algorithms: ['HS256'],        // 限制允许的算法,防止算法混淆攻击
      issuer: 'my-app',
      audience: 'my-app-client',
      maxAge: '2h'                  // 额外约束最大有效期
    });
    console.log('验证通过,载荷:', decoded);
    return decoded;
  } catch (err) {
    console.error('验证失败:', err.message);
    // 可根据 err.name 处理不同错误:TokenExpiredError, JsonWebTokenError, NotBeforeError
    return null;
  }
}

验证要点

  • 算法白名单:始终在 verify 中显式指定允许的算法数组,防止攻击者将算法篡改为 none 或削弱签名。
  • 时钟偏移:处理 expnbf 时,可设置一个合理的时钟偏移 clockTolerance(如 30 秒),避免多服务器间微小时间差导致误判。
  • 密钥管理:对于非对称算法,验证方应持有公钥,签名方持有私钥,严禁私钥泄露。

JWT 安全风险与最佳实践

1. 绝不在载荷中存储敏感数据

JWT 的载荷仅被编码而非加密。任何获取到令牌的人都可以通过 Base64UrlDecode 查看内容。敏感数据(如密码、身份证号)应完全移除,仅存放用户标识等非敏感信息。

2. 使用 HTTPS

所有传输 JWT 的通道必须使用 HTTPS,防止中间人攻击截获令牌。即使令牌本身有过期时间,攻击者也可能在短时间内滥用。

3. 合理设置过期时间与令牌刷新

  • 短期访问令牌(Access Token):有效期建议 15~60 分钟,减少被盗用后的风险窗口。
  • 长期刷新令牌(Refresh Token):用于获取新的 Access Token,需存储在安全位置(如 HttpOnly、Secure 的 Cookie 中),并具备撤销机制。
  • 实现令牌轮换(Rotation),每次刷新后旧 Refresh Token 失效,提升安全性。

4. 防止 CSRF 与 XSS

  • 若将 JWT 存储在 Cookie 中,务必设置 HttpOnlySecureSameSite=Strict 属性,防止 JS 读取和跨站请求携带。
  • 若存储在 localStoragesessionStorage,需严防 XSS 攻击,确保应用对所有用户输入进行转义和过滤。
  • 优先推荐:Access Token 存放在内存中(或仅通过 JS 闭包持有),Refresh Token 采用 HttpOnly 安全 Cookie。

5. 选择合适的签名算法

  • 对称算法(HS256, HS384, HS512):使用共享密钥,适合单一服务或内部微服务,密钥管理相对简单但分发较难。
  • 非对称算法(RS256, ES256):使用公钥/私钥对,私钥仅签发方持有,公钥分发给验证方,安全性更高且便于横向扩展。
  • 避免使用 none 算法,在代码中始终禁用。

6. 防范算法混淆攻击

某些库在验证时可能根据令牌头部自动选择算法,攻击者可将算法修改为 none 或构造一个 HS256 签名的令牌并以 RS256 公钥进行验证(因为公钥可获取)。防御措施:验证时强制指定允许的算法列表,如 algorithms: ['RS256']

7. 最小化载荷大小

JWT 附加在每个 HTTP 请求中,体积过大会影响网络性能。载荷中仅包含必要声明,避免存储大对象或冗余信息。

8. 令牌撤销与黑名单机制

JWT 是无状态的,签发后无法主动撤销(除非依赖过期时间)。对于需要实时失效的场景,可引入:

  • 黑名单:将需吊销的令牌 jti(JWT ID)存入 Redis,验证时检查。
  • 版本号:在用户数据中记录一个令牌版本,当用户修改密码或登出时递增版本号,验证时对比版本。

9. 安全存储密钥

  • 密钥不得硬编码在源代码中,应通过环境变量、密钥管理服务(如 AWS Secrets Manager、HashiCorp Vault)加载。
  • 定期轮换密钥,旧令牌在过期前仍可使用新密钥验证,需支持多密钥并存验证。

10. 记录与监控

对认证失败事件进行日志记录,监控异常频率,及时识别令牌滥用或暴力尝试。


总结

JWT 为分布式系统提供了优雅的无状态认证方案,但安全性高度依赖实现细节。牢记以下核心原则:

  • 载荷不存敏感数据,传输必须 HTTPS
  • 签名算法强限制,密钥保护要严密
  • 短期令牌 + 安全刷新,配合 HttpOnly Cookie
  • 验证时严格检查所有声明,拒绝任何可疑令牌

在实际项目中,推荐直接使用经过广泛验证的 JWT 库(如 jsonwebtokenjjwtPyJWT 等),并遵循官方安全指南。只有将签发、验证、存储、失效等环节都纳入安全设计,才能真正发挥 JWT 的优势而不留隐患。