现代Web应用离不开用户认证。无论是登录社交账号、调用API接口,还是使用移动应用,背后都有认证机制在工作。在众多认证方案中,JWT(JSON Web Token)已经成为最流行的选择之一。它简洁、自包含、易于扩展,被广泛应用于单点登录、API认证、微服务架构等场景。

什么是JWT

JWT的全称是JSON Web Token,读音与英文单词"jot"相同。它是一种开放标准(RFC 7519),定义了一种紧凑且URL安全的方式,用于在各方之间以JSON对象的形式安全地传输信息。

JWT的核心思想很简单:服务器认证用户后,生成一个包含用户身份信息的令牌(Token)返回给客户端。客户端在后续请求中携带这个令牌,服务器通过验证令牌的签名来确认用户身份,而不需要查询数据库或Session存储。

2015年5月,IETF正式发布RFC 7519,将JWT标准化。如今,JWT已成为OAuth 2.0和OpenID Connect协议的重要组成部分,几乎所有现代身份认证系统都在使用它。

JWT的结构:三个部分组成一个完整的令牌

一个实际的JWT看起来像这样一长串字符:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuS4iua1t-W4puS8miIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

这串字符由三部分组成,用点号(.)分隔:Header(头部)Payload(负载)Signature(签名)

JWT Structure

图片来源: jwt.io

Header:描述令牌的元数据

Header是一个JSON对象,描述令牌的类型和签名算法。例如:

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg(algorithm):指定签名算法,HS256表示使用HMAC SHA-256算法
  • typ(type):指定令牌类型,通常为JWT

这个JSON对象会使用Base64URL算法编码,形成JWT的第一部分。Base64URL是Base64的一个变体,专门设计用于URL安全传输——它将+替换为-,将/替换为_,并去掉末尾的=填充符号。

Payload:携带实际的数据

Payload是JWT的核心,包含需要传递的数据。这些数据以"声明"(Claim)的形式存在,每个声明都是一个键值对。

RFC 7519定义了七个注册声明(Registered Claims),它们有特定的含义:

声明 全称 含义
iss Issuer 令牌的签发者
sub Subject 令牌的主题(通常是用户ID)
aud Audience 令牌的接收者
exp Expiration Time 过期时间
nbf Not Before 生效时间
iat Issued At 签发时间
jti JWT ID 令牌的唯一标识

除了注册声明,你可以添加自定义声明。例如:

{
  "sub": "user-12345",
  "name": "张三",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

重要提示:Payload只是Base64URL编码,不是加密!任何人都可以解码并读取其中的内容。因此,永远不要在JWT中存储敏感信息,如密码、信用卡号或身份证号。

Signature:保证令牌不被篡改

Signature是JWT的安全核心,用于验证令牌的完整性和真实性。签名过程如下:

  1. 将编码后的Header和Payload用点号连接:

    base64UrlEncode(header) + "." + base64UrlEncode(payload)
    
  2. 使用Header中指定的算法和密钥对连接后的字符串进行签名。以HS256为例:

    HMACSHA256(
      base64UrlEncode(header) + "." + base64UrlEncode(payload),
      secret
    )
    
  3. 将签名结果进行Base64URL编码,形成JWT的第三部分。

服务器收到JWT后,会使用相同的密钥和算法重新计算签名,并与令牌中的签名进行比对。如果一致,说明令牌没有被篡改;如果不一致,则拒绝该令牌。

JWT的认证流程

理解JWT的结构后,来看它在实际认证中是如何工作的。

sequenceDiagram
    participant User as 用户
    participant Client as 客户端
    participant Server as 服务器
    
    User->>Client: 输入用户名密码
    Client->>Server: POST /login {username, password}
    Server->>Server: 验证凭据
    Server->>Server: 生成JWT
    Server-->>Client: 返回JWT
    Client->>Client: 存储JWT
    
    Note over User,Server: 后续请求
    
    Client->>Server: GET /api/user<br/>Authorization: Bearer <token>
    Server->>Server: 验证JWT签名
    Server->>Server: 解析Payload获取用户信息
    Server-->>Client: 返回用户数据

完整的认证流程包含以下步骤:

  1. 用户登录:用户向服务器发送用户名和密码。

  2. 服务器验证:服务器验证凭据是否正确。

  3. 签发令牌:验证通过后,服务器生成JWT并返回给客户端。

  4. 客户端存储:客户端将JWT存储起来(通常存储在localStorage或Cookie中)。

  5. 携带令牌请求:客户端在后续请求的Authorization头部中携带JWT:

    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    
  6. 服务器验证:服务器验证JWT的签名、过期时间等信息。

  7. 返回响应:验证通过后,服务器处理请求并返回响应。

JWT与Session:两种认证模型的对比

传统的Session认证和JWT认证是两种不同的模型,各有优缺点。

Session认证的工作方式

传统Session认证流程:

  1. 用户登录后,服务器创建一个Session,存储用户信息
  2. 服务器返回一个Session ID给客户端(通常存储在Cookie中)
  3. 后续请求时,客户端自动发送Cookie中的Session ID
  4. 服务器根据Session ID查找对应的Session数据

JWT认证的特点

JWT采用无状态设计,服务器不需要存储任何会话信息:

特性 Session JWT
存储位置 服务器端 客户端
扩展性 需要Session共享 天然支持分布式
跨域支持 需要额外配置 原生支持
注销能力 立即生效 需等过期或额外逻辑
信息容量 仅Session ID 可携带自定义数据

什么时候选择JWT

JWT特别适合以下场景:

  • 分布式系统和微服务架构:多个服务之间无需共享Session存储
  • 移动应用和单页应用(SPA):前后端分离架构
  • 单点登录(SSO):一次登录,多个系统共享身份
  • API服务:无状态的RESTful API

但对于传统的Web应用,尤其是需要精细控制会话生命周期的场景,Session仍然是一个可靠的选择。

签名算法的选择

JWT支持多种签名算法,最常见的有三种:

HS256:对称加密

HS256(HMAC SHA-256)使用同一个密钥进行签名和验证。它的特点是:

  • 计算速度快
  • 实现简单
  • 密钥必须妥善保管,任何拥有密钥的人都可以签发令牌

适合单服务内部使用,但不适合需要多方验证的场景。

RS256:非对称加密

RS256(RSA SHA-256)使用公私钥对:

  • 私钥用于签名,只有授权服务器持有
  • 公钥用于验证,可以分发给任何需要验证JWT的服务
  • 计算较慢,但安全性更高

适合微服务架构和第三方集成的场景。

ES256:椭圆曲线签名

ES256(ECDSA P-256)是一种更现代的非对称算法:

  • 比RSA更短的密钥,相同的强度
  • 签名和验证速度更快
  • 实现复杂度稍高

对于新项目,如果性能和密钥长度是考虑因素,ES256是一个不错的选择。

根据Curity的安全建议,在性能和安全性之间,ES256和EdDSA是当前最推荐的算法选择

安全最佳实践

使用JWT时,安全性是首要考虑因素。以下是关键的安全实践:

1. 验证所有必要的声明

服务器在验证JWT时,必须检查:

  • 签名:确保令牌未被篡改
  • 过期时间(exp):拒绝已过期的令牌
  • 签发者(iss):确认令牌来自可信来源
  • 接收者(aud):确认令牌是颁发给你的服务
// 示例验证逻辑
function verifyJWT(token, secret) {
  const decoded = jwt.verify(token, secret, {
    issuer: 'https://api.example.com',
    audience: 'my-app'
  });
  
  if (decoded.exp < Date.now() / 1000) {
    throw new Error('Token expired');
  }
  
  return decoded;
}

2. 设置合理的过期时间

JWT一旦签发,在过期之前都无法撤销。因此,应该设置较短的过期时间——通常几分钟到几小时。对于需要长期保持登录的场景,可以使用Refresh Token机制。

3. 安全存储令牌

客户端存储JWT有两种主要方式:

localStorage:容易受到XSS(跨站脚本)攻击,恶意JavaScript可以读取其中的令牌。

HttpOnly Cookie:设置了HttpOnly标志的Cookie无法被JavaScript读取,可以防止XSS攻击。但需要注意防范CSRF(跨站请求伪造)攻击,通常配合SameSite属性使用。

RFC 8725建议,对于Web应用,优先使用HttpOnly Cookie存储JWT

4. 不要存储敏感信息

JWT的Payload是Base64URL编码,任何人都可以解码。永远不要在其中存储:

  • 密码或密码哈希
  • 个人敏感信息(身份证号、银行卡号)
  • 内部系统信息(数据库连接字符串、API密钥)

5. 使用HTTPS传输

JWT应该始终通过HTTPS传输,防止中间人攻击窃取令牌。

Refresh Token机制

为了解决JWT过期时间短导致用户频繁登录的问题,业界引入了Refresh Token机制。

工作原理:

  1. 用户登录时,服务器返回两个令牌:

    • Access Token:有效期短(如15分钟),用于访问资源
    • Refresh Token:有效期长(如7天),用于获取新的Access Token
  2. 当Access Token过期时,客户端使用Refresh Token请求新的Access Token。

  3. 服务器验证Refresh Token后,签发新的Access Token(可选:同时签发新的Refresh Token)。

  4. 如果Refresh Token也过期或被撤销,用户需要重新登录。

这种设计既保证了安全性(Access Token有效期短),又提供了良好的用户体验。

sequenceDiagram
    participant Client as 客户端
    participant Auth as 认证服务器
    participant API as API服务器
    
    Client->>Auth: 登录请求
    Auth-->>Client: Access Token + Refresh Token
    
    Client->>API: 请求资源 (Access Token)
    API-->>Client: 返回数据
    
    Note over Client: Access Token过期
    
    Client->>Auth: 刷新令牌 (Refresh Token)
    Auth-->>Client: 新的 Access Token
    
    Client->>API: 请求资源 (新Access Token)
    API-->>Client: 返回数据

常见问题解答

JWT可以被撤销吗?

标准JWT一旦签发,在过期前无法撤销。如果需要撤销功能,需要在服务端维护一个黑名单,或使用较短的过期时间配合Refresh Token。

如何处理令牌泄露?

如果怀疑JWT泄露,应该立即让用户重新登录,并签发新的令牌。在服务端可以维护一个令牌版本号,强制使旧令牌失效。

JWT适合作为Session的替代品吗?

这取决于具体需求。JWT的设计初衷是无状态认证,不是替代Session。对于需要精细会话管理、实时权限更新的场景,Session仍然更合适。

总结

JWT是一种简洁、自包含的令牌格式,通过签名保证数据的完整性。它的三段式结构——Header、Payload、Signature——各司其职,共同构成一个安全的认证凭证。

理解JWT的关键在于:

  • Payload不是加密,不要存储敏感信息
  • 签名算法的选择影响性能和安全性
  • 无状态设计带来了扩展性,但也限制了撤销能力
  • 安全实践(HTTPS、短过期时间、HttpOnly Cookie)缺一不可

在实际项目中,应该根据具体需求选择JWT或Session,而不是盲目跟风。对于分布式系统、移动应用和API认证,JWT是一个优秀的选择;对于传统Web应用,Session可能更简单可靠。


参考资料