JWT 介绍和场景示例

9月 14, 2025
Frontend, Algorithms, 系统设计, HTTP, ByAI

一、JWT 是什么?#

JWT 的英文全称是 JSON Web Token。它是一种开放的、行业标准的方法,用于在双方之间安全地传输信息作为 JSON 对象。

最关键的特点是:这些信息是经过数字签名的,因此可以被验证和信任

JWT 最常见的应用场景就是认证授权

  • 认证:用户登录后,每个后续请求都会包含 JWT,服务器通过验证 JWT 来确认用户的身份。这就是我们接下来要详细介绍的机制。
  • 授权:一旦用户登录,JWT 中可以包含用户的角色和权限,服务器可以根据这些信息来决定用户是否有权访问特定资源。

二、为什么需要 JWT?—— 解决 Session 的问题#

在理解 JWT 之前,先看看传统的基于 Session 的认证有什么痛点:

  1. 服务器内存开销:用户的登录状态(Session)存储在服务器内存中。用户量巨大时,服务器内存压力很大。
  2. 扩展性问题:在分布式或微服务架构中,用户的请求可能被负载均衡到不同的服务器上。如果 Session 只存在一台服务器上,下一次请求可能就找不到这个 Session 了(除非使用 Session 粘滞或分布式 Session 方案,但这增加了复杂性)。
  3. CSRF 攻击:基于 Cookie 的 Session 容易受到跨站请求伪造攻击,需要额外的手段来防御。

JWT 如何解决? JWT 是一种无状态的认证机制。服务器在用户登录验证成功后,生成一个包含用户信息的 JWT 并返回给客户端。客户端保存此 JWT,并在后续请求中带上它。 服务器不需要保存任何用户的认证状态,因为它可以通过验证 JWT 的签名来确认令牌的合法性和内容的有效性。这使得应用更容易扩展。


三、JWT 的结构#

一个 JWT 看起来像这样的一长串字符串,由三部分组成,用点 . 分隔: xxxxx.yyyyy.zzzzz

例如: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

这三部分分别是:

1. Header (头部)#

通常由两部分组成:

  • typ:令牌类型,就是 JWT
  • alg:签名算法,如 HMAC SHA256RSA

这个 JSON 对象会被 Base64Url 编码,形成 JWT 的第一部分。

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

2. Payload (负载)#

Payload 包含了你要传输的“声明”(Claims)。声明是关于实体(通常是用户)和其他数据的陈述。有三种类型的声明:

  • 注册声明:预定义的一组声明,不是强制性的,但推荐使用。例如:
    • iss:签发者
    • exp:过期时间
    • sub:主题
    • aud:受众
  • 公共声明:可以随意定义,但为了避免冲突,应在 IANA JSON Web Token Registry 中定义或使用一个包含抗冲突命名空间的名称。
  • 私有声明:自定义的声明,用于在同意使用它们的各方之间共享信息。

示例 Payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

同样,这个 JSON 对象也会被 Base64Url 编码,形成 JWT 的第二部分。

注意:Payload 是可读的,所以不要在其中存放敏感信息(如密码)。

3. Signature (签名)#

这是 JWT 最核心的部分,用于防止令牌被篡改。

签名部分的生成方式如下: Signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )

  • 将编码后的 Header 和编码后的 Payload 用一个点 . 连接起来。
  • 使用 Header 中指定的算法(如 HS256)和一个只有服务器才知道的密钥对这个字符串进行加密。
  • 加密后的结果再进行 Base64Url 编码,就得到了签名。

签名的关键作用:服务器持有密钥,可以验证前两部分内容在传输过程中是否被篡改。任何对 Header 或 Payload 的修改,如果没有密钥,都无法生成正确的签名,服务器验证时就会失败。


四、JWT 的认证流程#

整个过程可以清晰地分为登录和访问两个阶段:

  1. 登录(认证)

    • 用户:使用用户名和密码请求登录。
    • 服务器:验证用户名和密码。
    • 服务器:验证成功后,根据用户信息(如用户ID、角色等)生成 JWT 的 Header 和 Payload。
    • 服务器:使用密钥生成 Signature,并将三部分组合成一个完整的 JWT 字符串。
    • 服务器:将这个 JWT 返回给客户端(通常在 HTTP Response 的 Authorization 头或 Body 中)。
  2. 访问受保护资源(授权)

    • 客户端:收到并存储 JWT(通常存储在 localStoragesessionStorage 或 Cookie 中)。
    • 客户端:在后续向服务器发起的请求中,在 HTTP Request 的 Authorization 头中带上这个 JWT(格式通常是:Bearer <token>)。
    • 服务器:接收到请求后,从 Authorization 头中提取 JWT。
    • 服务器验证 JWT: a. 检查结构:检查令牌是否由三部分组成。 b. 验证签名:使用自己的密钥,对收到的 Header 和 Payload 部分重新计算签名,并与收到的 Signature 部分进行比对。如果不同,说明令牌被篡改,立即拒绝请求。 c. 检查有效期:检查 Payload 中的 exp 字段,看令牌是否已过期。 d. 检查签发者/受众(可选):根据业务需要,检查 issaud 等字段。
    • 服务器:验证通过后,从 Payload 中解析出用户信息(如用户ID),然后处理业务逻辑并返回结果。

五、JWT 的优点与注意事项#

优点:#

  1. 无状态与可扩展:服务器不需要存储会话信息,减轻了服务器压力,天然支持分布式部署。
  2. 跨域支持:可以轻松解决跨域问题,适用于单点登录和 API 认证。
  3. 多平台支持:JSON 格式通用,可用于 Web、APP 等各种客户端。

注意事项与安全最佳实践:#

  1. 保护好密钥:密钥是生成和验证签名的核心,一旦泄露,攻击者可以伪造任何 JWT。
  2. 不要存放敏感信息:Payload 只是经过编码,并非加密(除非使用 JWE)。任何人都可以解码看到内容。
  3. 使用 HTTPS:防止传输过程中的 JWT 被拦截。
  4. 设置合理的过期时间:JWT 一旦签发,在过期前一直有效。因此应设置较短的过期时间以提高安全性。对于敏感操作,可以使用刷新令牌机制。
  5. 选择合适的存储方式
    • localStorage/sessionStorage:不易防范 XSS 攻击。
    • Cookie(带有 HttpOnlySecure 标志):可以防范 XSS,但要额外处理 CSRF 攻击。 需要根据安全需求权衡选择。
  6. 令牌注销问题:由于 JWT 是无状态的,服务器无法在用户退出时直接让一个未过期的 JWT 失效。通常的解决方案是使用令牌黑名单(但这又引入了状态)或设置较短的过期时间。

总结#

JWT 通过数字签名的方式,创造了一种安全、可靠且无状态的认证令牌。其核心机制是:服务器签发一个包含用户信息和签名的令牌,客户端在后续请求中出示该令牌,服务器通过验证签名来信任令牌中的内容,从而确认用户身份。它在现代 Web 应用和微服务架构中扮演着至关重要的角色,但在使用时必须遵循安全最佳实践。

参考链接#

场景实例#

我们通过一个完整的、具体的 HTTP 请求示例来深入说明 JWT 的认证机制。我们将模拟一个简单的用户登录和获取资源的流程,使用最常见的 curl 命令来展示。


场景设定#

  • 认证服务器https://api.example.com/auth
  • 资源服务器https://api.example.com/profile
  • 用户:用户名 alice,密码 password123

第 1 步:用户登录,获取 JWT#

客户端向认证服务器的登录端点发送 POST 请求,携带用户的凭证。

HTTP 请求 (客户端 -> 服务器)

POST /auth/login HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "username": "alice",
  "password": "password123"
}

使用 curl 命令:

curl -X POST https://api.example.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice", "password":"password123"}'

HTTP 响应 (服务器 -> 客户端) 登录成功,服务器生成 JWT 并将其放在响应体中返回给客户端。

HTTP/1.1 200 OK
Content-Type: application/json
...

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiQWxpY2UiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2OTQyMzAwMDAsImV4cCI6MTY5NDIzMzYwMH0.4Q_s_6cFJoCx4GqFQxnmUqMTpO_jiK5Q3NcJgZzL5kA",
  "token_type": "Bearer",
  "expires_in": 3600
}

响应体解析

  • access_token:这就是我们需要的 JWT。它包含了经过 Base64Url 编码的 Header、Payload 和 Signature。
  • token_type:通常是 Bearer,表示客户端持有此令牌即可访问资源,无需其他证明。
  • expires_in:令牌的有效期,这里是 3600 秒(1小时)。

此时,客户端(如浏览器)需要安全地存储这个 access_token(例如在 localStorageHttpOnly Cookie 中)。


第 2 步:使用 JWT 访问受保护的资源#

现在,用户想获取自己的个人资料信息。客户端需要在请求的 Authorization 头中附带上一步获取的 JWT。

HTTP 请求 (客户端 -> 服务器)

GET /profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiQWxpY2UiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2OTQyMzAwMDAsImV4cCI6MTY5NDIzMzYwMH0.4Q_s_6cFJoCx4GqFQxnmUqMTpO_jiK5Q3NcJgZzL5kA

使用 curl 命令:

curl -X GET https://api.example.com/profile \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiQWxpY2UiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2OTQyMzAwMDAsImV4cCI6MTY5NDIzMzYwMH0.4Q_s_6cFJoCx4GqFQxnmUqMTpO_jiK5Q3NcJgZzL5kA"

关键点

  • 方法:GET
  • 头信息:Authorization: Bearer <JWT>
  • 请求体中不需要再发送任何用户凭证。

第 3 步:服务器验证 JWT 并返回资源#

资源服务器收到请求后,会执行以下验证步骤:

  1. 提取令牌:从 Authorization 头中提取出 JWT 字符串。
  2. 验证签名(最关键的一步):
    • 用点(.)分割字符串,得到三部分:Header、Payload 和 Signature。
    • 使用相同的密钥(只有服务器知道)和 Header 中指定的算法(这里是 HS256),对收到的 Header.Payload 部分重新计算签名。
    • 将计算出的新签名与收到的 Signature 进行比对。如果两者一致,证明令牌未被篡改;如果不一致,立即返回 401 Unauthorized 错误。
  3. 检查有效期:解码 Payload(第二部分),检查 exp(Expiration Time)字段,确认令牌是否在有效期内。如果已过期,返回 401 Unauthorized
  4. 检查其他声明(可选):例如,可以检查 iss(Issuer)字段是否来自可信的认证服务器,或者 role 字段是否具有访问该资源的权限。

如果所有验证都通过,服务器认为请求来自合法用户 alice。然后它从 Payload 中解析出用户ID(sub: "123456"),并从数据库中找到该用户的资料信息返回。

HTTP 响应 (服务器 -> 客户端)

HTTP/1.1 200 OK
Content-Type: application/json
...

{
  "userId": "123456",
  "username": "alice",
  "email": "alice@example.com",
  "bio": "Hello, world!"
}

JWT 内容解析(幕后)#

让我们解码一下示例中的 JWT,看看它到底包含了什么信息:

JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiQWxpY2UiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2OTQyMzAwMDAsImV4cCI6MTY5NDIzMzYwMH0.4Q_s_6cFJoCx4GqFQxnmUqMTpO_jiK5Q3NcJgZzL5kA

  1. Header (解码后):

    {
      "alg": "HS256",  // 使用 HMAC-SHA256 算法进行签名
      "typ": "JWT"     // 类型是 JWT
    }
  2. Payload (解码后):

    {
      "sub": "123456",     // Subject (主题),通常是用户ID
      "name": "Alice",     // 自定义声明:用户名
      "role": "admin",     // 自定义声明:用户角色,用于授权
      "iat": 1694230000,   // Issued At (签发时间),Unix 时间戳
      "exp": 1694233600    // Expiration Time (过期时间),Unix 时间戳
    }

    这个 Payload 告诉服务器:“这是一个用户ID为 123456、名叫 Alice 的管理员,此令牌签发于 2023-09-09 10:06:40,并将于 1 小时后(11:06:40)过期。”

  3. Signature: 这是服务器使用密钥对 Header.Payload 签名后得到的结果,用于防止前两部分被篡改。

总结#

通过这个具体的 HTTP 例子,你可以清晰地看到:

  1. 登录获取令牌:客户端用凭证换回一个 JWT。
  2. 携带令牌访问:客户端在后续请求的 Authorization 头中以 Bearer 模式携带此 JWT。
  3. 服务器无状态验证:服务器不查数据库,仅通过验证签名和检查声明来判断用户身份和权限。
  4. 安全通信:整个过程在 HTTPS 的保护下进行,确保了传输安全。

这就是 JWT 在现代 Web API 和微服务中作为认证机制的核心工作流程。

需要数据库的场景#

使用 JWT 后是否需要数据库,以及何时引入,是架构设计中的一个关键决策。

核心结论#

JWT 的核心认证机制(验证令牌真伪和检查过期)是无状态的,不需要数据库。 但是,一个完整、安全、可用的生产级认证系统,几乎总是需要数据库来弥补 JWT 的固有缺陷,尤其是为了实现“主动令牌失效”功能。

是否“一开始就要准备”取决于您的应用场景和对安全性的要求。对于大多数面向用户的应用,答案是:是的,从一开始就应该规划好数据库方案。


一、什么情况下需要数据库?#

数据库在 JWT 方案中主要解决以下四个问题:

1. 核心需求:令牌的主动失效(最普遍的原因)#

这是引入数据库最主要、最常见的原因。JWT 一旦签发,在到期前始终有效,服务器无法单方面作废它。但在以下场景中,你必须能够立即让一个令牌失效:

  • 用户登出:用户主动退出登录,当前的令牌应立即失效。
  • 密码修改:用户修改密码后,所有之前签发的令牌(可能在其他设备上)都应立即失效。
  • 账户封禁/删除:管理员封禁某个用户后,该用户的所有有效令牌必须立刻失效。
  • 安全事件:检测到令牌泄露或可疑活动时,需要强制使特定令牌失效。

解决方案:令牌黑名单(Token Blacklist)

  • 需要一个极快的数据库(首选 Redis,因为其高速和天然的 TTL 支持)。
  • 当发生上述需要失效令牌的事件时,将尚未过期的 JWT 的唯一标识(建议使用 jti - JWT ID)存入黑名单,并为其设置一个生存时间(TTL),这个 TTL 等于该 JWT 剩余的过期时间。
  • 在验证 JWT 时,流程变为
    1. 验证签名(无状态)
    2. 检查 exp 是否过期(无状态)
    3. 查询黑名单数据库,检查当前令牌的 jti 是否存在于黑名单中(有状态查询)
  • 如果存在于黑名单,则拒绝访问。

2. 增强安全:刷新令牌模式#

为了安全,访问令牌(Access Token)的过期时间通常设置较短(如 15-30 分钟)。当它过期后,用户不需要重新登录,而是使用一个刷新令牌(Refresh Token) 来获取新的访问令牌。

  • 刷新令牌:生命周期很长(几天、几周甚至几个月),但一次性使用。它唯一的作用就是去换一个新的访问令牌。
  • 为什么需要数据库?:服务器必须在数据库中记录每个用户发出的刷新令牌(或其哈希值)。当使用刷新令牌换取新访问令牌时,服务器需要:
    1. 验证该刷新令牌是否有效且未被撤销。
    2. 使当前使用的这个刷新令牌失效(防止重复使用)。
    3. 签发一个新的访问令牌一个新的刷新令牌(可选,推荐)并更新数据库。

这个机制保证了即使访问令牌泄露,危害时间也很短;而刷新令牌被严格管理在服务器端,泄露风险极低。

3. 审计与合规性#

对于企业级应用或需要满足合规要求(如 GDPR, SOC 2)的系统,你需要记录和追踪令牌的使用情况。

  • 数据库用于记录
    • 令牌的签发时间、给哪个用户、对应的 jti
    • 令牌的最近使用时间。
    • 令牌是否已被主动撤销。
  • 这些日志对于安全审计、排查问题至关重要。

4. 动态权限管理#

如果用户的权限( roles / permissions )可能在令牌有效期内发生变化(例如,管理员收回了某个用户的编辑权限),那么仅靠 JWT Payload 里固化的权限信息就不够了。

  • 解决方案:即使 JWT 验证通过,服务器仍然需要在处理每个请求时,根据用户ID去数据库中查询其最新的、实时的权限,然后判断是否允许执行当前操作。
  • 在这种情况下,JWT 只负责解决“你是谁”(认证)的问题,而“你能做什么”(授权)的问题则由数据库实时决定。

二、是否一开始就要准备数据库方案?#

这是一个经典的“设计先行”问题。我的建议如下:

场景一:优先考虑“是”的情况(推荐用于大多数项目)#

如果你的应用是:

  • 面向公众的 Web/App 应用:用户有明确的“退出登录”需求。
  • 对安全性要求较高:需要处理密码修改、账户封禁等功能。
  • 期望长期发展:未来肯定会需要上述功能。

那么,你应该从一开始就设计和集成数据库(特别是 Redis)方案。

  • 原因
    1. 降低后期重构成本:后期再加入黑名单或刷新令牌机制,意味着要修改核心的认证中间件和所有相关接口,风险高,工作量大。
    2. 保障上线即安全:避免应用在早期就存在“无法强制下线用户”的安全漏洞。
    3. 架构一致性:从一开始就建立正确的、可扩展的认证流程。

初期简易实现:即使刚开始用户量不大,你也可以用一个简单的 Redis 或甚至一张 SQL 表来实现黑名单功能。虽然初期可能没有查询压力,但正确的逻辑已经就位。

场景二:可以暂时“否”的情况#

在某些特定场景下,你可以初期不使用数据库:

  • 内部微服务间通信:服务 A 调用服务 B,使用的 JWT 是短期有效的(几分钟),只用于认证服务身份,没有“用户登出”的概念。过期即失效,足够安全。
  • 简单的、无状态的 API 网关:网关验证 JWT 后,将用户信息转发给内部服务,内部服务不再重复验证。
  • 一次性或原型验证(MVP):唯一目标是快速验证市场想法,安全性和完善的身份管理不是当前阶段的重点。但要非常清楚这是一个需要尽快偿还的技术债。

即使在这些场景下,也最好在技术设计文档中注明:“当前认证方案为纯无状态 JWT,不支持令牌撤销。如需此功能,需引入 Redis 黑名单机制”。

总结与建议#

功能需求是否需要数据库?数据库用途
仅做认证,令牌短期有效,无需撤销-
支持用户登出、改密后令牌立即失效Redis 黑名单
使用刷新令牌机制数据库存储刷新令牌状态
需要审计日志数据库记录令牌签发与使用
权限在令牌有效期内会动态变化数据库查询实时权限

给您的最终建议:

对于绝大多数项目,请从一开始就为 JWT 方案配备一个数据库(首选 Redis)来处理令牌黑名单。 这将为你提供一个既享受了 JWT 无状态扩展性优势,又具备了关键安全控制能力的、健壮的认证系统。把这视为一个必要的基础建设,而不是一个可选的优化项。

用户登出场景#

用户主动登出(Logout)是 JWT 方案中最经典、最需要解决的问题。因为它直接挑战了 JWT “一旦签发,无法作废” 的核心特性。

解决这个问题的关键在于:从“无状态”转变为“有状态”,引入一个额外的存储层(通常是数据库)来记录那些“虽然未过期但已失效”的令牌。

以下是几种主流的解决方案,从简单到复杂排列。


方案一:令牌黑名单(Token Blacklist) - 最常用方案#

这是最直接、最普遍的解决方案。核心思想是:用户登出时,服务器将这个仍然有效的令牌标记为“已废弃”,并在后续每次验证时检查这个黑名单。

实现步骤:#

  1. 选择存储:选择一个高速的、支持设置过期时间(TTL)的存储。Redis 是绝佳选择,因为它速度快且原生支持 TTL。
  2. 登出操作
    • 用户点击“退出登录”,客户端发起登出请求(需要携带当前的 JWT)。
    • 服务器验证该 JWT 的有效性(签名、过期时间)。
    • 服务器将这个有效的 JWT 的唯一标识存入黑名单。通常有两种标识方式:
      • 使用 jti (JWT ID):在签发 JWT 时,就在 Payload 中塞入一个唯一标识符(如 UUID)。登出时,将这个 jti 存入 Redis,并为其设置一个 TTL(这个 TTL 应等于该 JWT 剩余的过期时间)。
      • 使用整个 Token:直接将整个 JWT 字符串作为 Key 存入 Redis(价值较低,因为很长)。
  3. 修改验证逻辑
    • 在原有的 JWT 验证流程(检查签名、检查 exp之后,增加一步:
    • 查询黑名单:提取当前请求 JWT 中的 jti,去 Redis 中查询是否存在。如果存在,则拒绝访问,返回 401 Unauthorized

流程示意图:#

graph TD
    A[客户端请求: 携带JWT] --> B{服务器验证JWT}
    B -- 签名无效? --> C[拒绝访问: 401]
    B -- 签名有效? --> D{检查exp是否过期}
    D -- 已过期? --> C
    D -- 未过期? --> E[查询Redis黑名单]
    E -- jti存在? --> C
    E -- jti不存在? --> F[允许访问, 处理请求]
    
    G[用户登出] --> H[将jti存入Redis, 并设置TTL]

优点:#

  • 实现相对简单,理解直观。
  • 性能影响小:每次验证只增加一次 Redis 查询(Redis 速度极快)。
  • 资源自动清理:利用 Redis 的 TTL 自动删除过期黑名单条目,避免数据库无限膨胀。

缺点:#

  • 引入了状态,破坏了 JWT 的纯粹无状态性。
  • 在分布式系统中,需要保证所有服务器节点都连接到同一个共享的 Redis 集群。

方案二:短期令牌 + 刷新令牌(Short-lived Access Token + Refresh Token)#

这个方案不直接解决“撤销”,而是通过极大地缩短访问令牌的生命周期,来降低“需要撤销”的场景带来的风险。它通常与方案一(黑名单)结合使用,以处理刷新令牌本身的撤销。

  1. 令牌设置

    • 访问令牌 (Access Token):有效期非常短,例如 5-15 分钟。用于访问业务接口。
    • 刷新令牌 (Refresh Token):有效期很长,例如 7 天或更长。它不用于访问业务,唯一的功能是去换一个新的 Access Token。
  2. 登出操作

    • 用户登出时,客户端直接丢弃本地的 Access Token 和 Refresh Token。
    • 关键步骤:服务器端需要立即使该用户的 Refresh Token 无效。这意味着你需要将 Refresh Token 或其标识(如与用户ID关联)在数据库中标记为已撤销。这样,即使用户的 Refresh Token 被盗,也无法再获取新的 Access Token。
    • 那个可能被泄露的、短期有效的 Access Token,最多只能在剩下的几分钟内被滥用,风险可控。
  3. 效果:用户登出后,即使 Access Token 未被加入黑名单,它也会在很短时间内(几分钟)自然过期。而由于 Refresh Token 已被服务器撤销,攻击者无法获取新的令牌,从而实现了实质上的登出。

优点:#

  • 大大减少了对黑名单的依赖(理论上只黑刷新令牌即可)。
  • 安全性更高,Access Token 泄露的影响窗口期极短。

缺点:#

  • 系统更复杂,需要维护 Refresh Token 的签发、验证和撤销机制。
  • 仍然需要一个数据库(如 Redis)来管理已撤销的 Refresh Token。

方案三:有状态会话存储(Stateful Session Store) - 不推荐#

这种方案完全放弃了 JWT 的无状态优势,退回到了类似传统 Session 的模式。

  1. 在数据库中(如 Redis)存储一个 session_id 到用户信息的映射。
  2. 签发 JWT 时,Payload 中只存储一个 session_id
  3. 每次验证 JWT 时,不仅要验证签名和过期时间,还必须用这个 session_id 去数据库中查询该会话是否仍然有效
  4. 用户登出时,直接删除数据库中的这个 session_id 记录。

为什么不推荐?#

  • 丧失了 JWT 的核心优势:每次请求都需要数据库查询,性能瓶颈和单点故障问题又回来了。
  • 设计矛盾:既然每次都要查库,那为什么不直接用传统的 Session ID?JWT 的自包含特性在此方案中毫无用处。

总结与实践建议#

方案核心思想优点缺点适用场景
令牌黑名单记录已注销的有效令牌实现简单,性能影响小引入了状态最通用,推荐大多数应用采用
短期令牌+刷新令牌缩短访问令牌寿命,撤销刷新令牌安全性高,泄露风险窗口小架构更复杂对安全性要求极高的应用(如银行、支付)
有状态会话JWT 只带会话ID,状态完全存于服务器控制力最强性能差,违背 JWT 初衷不推荐,除非有特殊需求

给你的最终建议:

对于绝大多数 Web 或移动应用,采用“方案一(令牌黑名单)”并配合“方案二(短期访问令牌)” 是最佳实践:

  1. 设置 Access Token 过期时间为 15-30 分钟
  2. 实现 Refresh Token 机制,用于获取新的 Access Token。
  3. 使用 Redis 来维护两个名单:
    • Access Token 黑名单:用于用户登出时,废弃那些还未过期的 Access Token。
    • 失效的 Refresh Token 名单:用于用户登出或修改密码时,立即使 Refresh Token 失效。

这样,你就在享受 JWT 无状态验证带来的性能好处的同时,拥有了立即让用户“登出”的能力,实现了安全与性能的平衡。

本文共 8592 字,上次修改于 Jun 8, 2026,以 CC 署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可。

相关文章

» Cookie 机制

» 跨域相关问题

» 浏览器中的 HTTP 缓存使用策略

» 说说实际工作中 GraphQL 的使用体验

» 零宽度空格与 CSS 换行行为