JWT 介绍和场景示例
9月 14, 2025
一、JWT 是什么?#
JWT 的英文全称是 JSON Web Token。它是一种开放的、行业标准的方法,用于在双方之间安全地传输信息作为 JSON 对象。
最关键的特点是:这些信息是经过数字签名的,因此可以被验证和信任。
JWT 最常见的应用场景就是认证和授权。
- 认证:用户登录后,每个后续请求都会包含 JWT,服务器通过验证 JWT 来确认用户的身份。这就是我们接下来要详细介绍的机制。
- 授权:一旦用户登录,JWT 中可以包含用户的角色和权限,服务器可以根据这些信息来决定用户是否有权访问特定资源。
二、为什么需要 JWT?—— 解决 Session 的问题#
在理解 JWT 之前,先看看传统的基于 Session 的认证有什么痛点:
- 服务器内存开销:用户的登录状态(Session)存储在服务器内存中。用户量巨大时,服务器内存压力很大。
- 扩展性问题:在分布式或微服务架构中,用户的请求可能被负载均衡到不同的服务器上。如果 Session 只存在一台服务器上,下一次请求可能就找不到这个 Session 了(除非使用 Session 粘滞或分布式 Session 方案,但这增加了复杂性)。
- CSRF 攻击:基于 Cookie 的 Session 容易受到跨站请求伪造攻击,需要额外的手段来防御。
JWT 如何解决? JWT 是一种无状态的认证机制。服务器在用户登录验证成功后,生成一个包含用户信息的 JWT 并返回给客户端。客户端保存此 JWT,并在后续请求中带上它。 服务器不需要保存任何用户的认证状态,因为它可以通过验证 JWT 的签名来确认令牌的合法性和内容的有效性。这使得应用更容易扩展。
三、JWT 的结构#
一个 JWT 看起来像这样的一长串字符串,由三部分组成,用点 . 分隔:
xxxxx.yyyyy.zzzzz
例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
这三部分分别是:
1. Header (头部)#
通常由两部分组成:
typ:令牌类型,就是JWT。alg:签名算法,如HMAC SHA256或RSA。
这个 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 的认证流程#
整个过程可以清晰地分为登录和访问两个阶段:
登录(认证)
- 用户:使用用户名和密码请求登录。
- 服务器:验证用户名和密码。
- 服务器:验证成功后,根据用户信息(如用户ID、角色等)生成 JWT 的 Header 和 Payload。
- 服务器:使用密钥生成 Signature,并将三部分组合成一个完整的 JWT 字符串。
- 服务器:将这个 JWT 返回给客户端(通常在 HTTP Response 的
Authorization头或 Body 中)。
访问受保护资源(授权)
- 客户端:收到并存储 JWT(通常存储在
localStorage、sessionStorage或 Cookie 中)。 - 客户端:在后续向服务器发起的请求中,在 HTTP Request 的
Authorization头中带上这个 JWT(格式通常是:Bearer <token>)。 - 服务器:接收到请求后,从
Authorization头中提取 JWT。 - 服务器:验证 JWT:
a. 检查结构:检查令牌是否由三部分组成。
b. 验证签名:使用自己的密钥,对收到的 Header 和 Payload 部分重新计算签名,并与收到的 Signature 部分进行比对。如果不同,说明令牌被篡改,立即拒绝请求。
c. 检查有效期:检查 Payload 中的
exp字段,看令牌是否已过期。 d. 检查签发者/受众(可选):根据业务需要,检查iss和aud等字段。 - 服务器:验证通过后,从 Payload 中解析出用户信息(如用户ID),然后处理业务逻辑并返回结果。
- 客户端:收到并存储 JWT(通常存储在
五、JWT 的优点与注意事项#
优点:#
- 无状态与可扩展:服务器不需要存储会话信息,减轻了服务器压力,天然支持分布式部署。
- 跨域支持:可以轻松解决跨域问题,适用于单点登录和 API 认证。
- 多平台支持:JSON 格式通用,可用于 Web、APP 等各种客户端。
注意事项与安全最佳实践:#
- 保护好密钥:密钥是生成和验证签名的核心,一旦泄露,攻击者可以伪造任何 JWT。
- 不要存放敏感信息:Payload 只是经过编码,并非加密(除非使用 JWE)。任何人都可以解码看到内容。
- 使用 HTTPS:防止传输过程中的 JWT 被拦截。
- 设置合理的过期时间:JWT 一旦签发,在过期前一直有效。因此应设置较短的过期时间以提高安全性。对于敏感操作,可以使用刷新令牌机制。
- 选择合适的存储方式:
- localStorage/sessionStorage:不易防范 XSS 攻击。
- Cookie(带有
HttpOnly和Secure标志):可以防范 XSS,但要额外处理 CSRF 攻击。 需要根据安全需求权衡选择。
- 令牌注销问题:由于 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(例如在 localStorage 或 HttpOnly 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 并返回资源#
资源服务器收到请求后,会执行以下验证步骤:
- 提取令牌:从
Authorization头中提取出 JWT 字符串。 - 验证签名(最关键的一步):
- 用点(
.)分割字符串,得到三部分:Header、Payload 和 Signature。 - 使用相同的密钥(只有服务器知道)和 Header 中指定的算法(这里是
HS256),对收到的Header.Payload部分重新计算签名。 - 将计算出的新签名与收到的 Signature 进行比对。如果两者一致,证明令牌未被篡改;如果不一致,立即返回
401 Unauthorized错误。
- 用点(
- 检查有效期:解码 Payload(第二部分),检查
exp(Expiration Time)字段,确认令牌是否在有效期内。如果已过期,返回401 Unauthorized。 - 检查其他声明(可选):例如,可以检查
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
Header (解码后):
{ "alg": "HS256", // 使用 HMAC-SHA256 算法进行签名 "typ": "JWT" // 类型是 JWT }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)过期。”
Signature: 这是服务器使用密钥对
Header.Payload签名后得到的结果,用于防止前两部分被篡改。
总结#
通过这个具体的 HTTP 例子,你可以清晰地看到:
- 登录获取令牌:客户端用凭证换回一个 JWT。
- 携带令牌访问:客户端在后续请求的
Authorization头中以Bearer模式携带此 JWT。 - 服务器无状态验证:服务器不查数据库,仅通过验证签名和检查声明来判断用户身份和权限。
- 安全通信:整个过程在 HTTPS 的保护下进行,确保了传输安全。
这就是 JWT 在现代 Web API 和微服务中作为认证机制的核心工作流程。
需要数据库的场景#
使用 JWT 后是否需要数据库,以及何时引入,是架构设计中的一个关键决策。
核心结论#
JWT 的核心认证机制(验证令牌真伪和检查过期)是无状态的,不需要数据库。 但是,一个完整、安全、可用的生产级认证系统,几乎总是需要数据库来弥补 JWT 的固有缺陷,尤其是为了实现“主动令牌失效”功能。
是否“一开始就要准备”取决于您的应用场景和对安全性的要求。对于大多数面向用户的应用,答案是:是的,从一开始就应该规划好数据库方案。
一、什么情况下需要数据库?#
数据库在 JWT 方案中主要解决以下四个问题:
1. 核心需求:令牌的主动失效(最普遍的原因)#
这是引入数据库最主要、最常见的原因。JWT 一旦签发,在到期前始终有效,服务器无法单方面作废它。但在以下场景中,你必须能够立即让一个令牌失效:
- 用户登出:用户主动退出登录,当前的令牌应立即失效。
- 密码修改:用户修改密码后,所有之前签发的令牌(可能在其他设备上)都应立即失效。
- 账户封禁/删除:管理员封禁某个用户后,该用户的所有有效令牌必须立刻失效。
- 安全事件:检测到令牌泄露或可疑活动时,需要强制使特定令牌失效。
解决方案:令牌黑名单(Token Blacklist)
- 需要一个极快的数据库(首选 Redis,因为其高速和天然的 TTL 支持)。
- 当发生上述需要失效令牌的事件时,将尚未过期的 JWT 的唯一标识(建议使用
jti- JWT ID)存入黑名单,并为其设置一个生存时间(TTL),这个 TTL 等于该 JWT 剩余的过期时间。 - 在验证 JWT 时,流程变为:
- 验证签名(无状态)
- 检查
exp是否过期(无状态) - 查询黑名单数据库,检查当前令牌的
jti是否存在于黑名单中(有状态查询)
- 如果存在于黑名单,则拒绝访问。
2. 增强安全:刷新令牌模式#
为了安全,访问令牌(Access Token)的过期时间通常设置较短(如 15-30 分钟)。当它过期后,用户不需要重新登录,而是使用一个刷新令牌(Refresh Token) 来获取新的访问令牌。
- 刷新令牌:生命周期很长(几天、几周甚至几个月),但一次性使用。它唯一的作用就是去换一个新的访问令牌。
- 为什么需要数据库?:服务器必须在数据库中记录每个用户发出的刷新令牌(或其哈希值)。当使用刷新令牌换取新访问令牌时,服务器需要:
- 验证该刷新令牌是否有效且未被撤销。
- 使当前使用的这个刷新令牌失效(防止重复使用)。
- 签发一个新的访问令牌和一个新的刷新令牌(可选,推荐)并更新数据库。
这个机制保证了即使访问令牌泄露,危害时间也很短;而刷新令牌被严格管理在服务器端,泄露风险极低。
3. 审计与合规性#
对于企业级应用或需要满足合规要求(如 GDPR, SOC 2)的系统,你需要记录和追踪令牌的使用情况。
- 数据库用于记录:
- 令牌的签发时间、给哪个用户、对应的
jti。 - 令牌的最近使用时间。
- 令牌是否已被主动撤销。
- 令牌的签发时间、给哪个用户、对应的
- 这些日志对于安全审计、排查问题至关重要。
4. 动态权限管理#
如果用户的权限( roles / permissions )可能在令牌有效期内发生变化(例如,管理员收回了某个用户的编辑权限),那么仅靠 JWT Payload 里固化的权限信息就不够了。
- 解决方案:即使 JWT 验证通过,服务器仍然需要在处理每个请求时,根据用户ID去数据库中查询其最新的、实时的权限,然后判断是否允许执行当前操作。
- 在这种情况下,JWT 只负责解决“你是谁”(认证)的问题,而“你能做什么”(授权)的问题则由数据库实时决定。
二、是否一开始就要准备数据库方案?#
这是一个经典的“设计先行”问题。我的建议如下:
场景一:优先考虑“是”的情况(推荐用于大多数项目)#
如果你的应用是:
- 面向公众的 Web/App 应用:用户有明确的“退出登录”需求。
- 对安全性要求较高:需要处理密码修改、账户封禁等功能。
- 期望长期发展:未来肯定会需要上述功能。
那么,你应该从一开始就设计和集成数据库(特别是 Redis)方案。
- 原因:
- 降低后期重构成本:后期再加入黑名单或刷新令牌机制,意味着要修改核心的认证中间件和所有相关接口,风险高,工作量大。
- 保障上线即安全:避免应用在早期就存在“无法强制下线用户”的安全漏洞。
- 架构一致性:从一开始就建立正确的、可扩展的认证流程。
初期简易实现:即使刚开始用户量不大,你也可以用一个简单的 Redis 或甚至一张 SQL 表来实现黑名单功能。虽然初期可能没有查询压力,但正确的逻辑已经就位。
场景二:可以暂时“否”的情况#
在某些特定场景下,你可以初期不使用数据库:
- 内部微服务间通信:服务 A 调用服务 B,使用的 JWT 是短期有效的(几分钟),只用于认证服务身份,没有“用户登出”的概念。过期即失效,足够安全。
- 简单的、无状态的 API 网关:网关验证 JWT 后,将用户信息转发给内部服务,内部服务不再重复验证。
- 一次性或原型验证(MVP):唯一目标是快速验证市场想法,安全性和完善的身份管理不是当前阶段的重点。但要非常清楚这是一个需要尽快偿还的技术债。
即使在这些场景下,也最好在技术设计文档中注明:“当前认证方案为纯无状态 JWT,不支持令牌撤销。如需此功能,需引入 Redis 黑名单机制”。
总结与建议#
| 功能需求 | 是否需要数据库? | 数据库用途 |
|---|---|---|
| 仅做认证,令牌短期有效,无需撤销 | 否 | - |
| 支持用户登出、改密后令牌立即失效 | 是 | Redis 黑名单 |
| 使用刷新令牌机制 | 是 | 数据库存储刷新令牌状态 |
| 需要审计日志 | 是 | 数据库记录令牌签发与使用 |
| 权限在令牌有效期内会动态变化 | 是 | 数据库查询实时权限 |
给您的最终建议:
对于绝大多数项目,请从一开始就为 JWT 方案配备一个数据库(首选 Redis)来处理令牌黑名单。 这将为你提供一个既享受了 JWT 无状态扩展性优势,又具备了关键安全控制能力的、健壮的认证系统。把这视为一个必要的基础建设,而不是一个可选的优化项。
用户登出场景#
用户主动登出(Logout)是 JWT 方案中最经典、最需要解决的问题。因为它直接挑战了 JWT “一旦签发,无法作废” 的核心特性。
解决这个问题的关键在于:从“无状态”转变为“有状态”,引入一个额外的存储层(通常是数据库)来记录那些“虽然未过期但已失效”的令牌。
以下是几种主流的解决方案,从简单到复杂排列。
方案一:令牌黑名单(Token Blacklist) - 最常用方案#
这是最直接、最普遍的解决方案。核心思想是:用户登出时,服务器将这个仍然有效的令牌标记为“已废弃”,并在后续每次验证时检查这个黑名单。
实现步骤:#
- 选择存储:选择一个高速的、支持设置过期时间(TTL)的存储。Redis 是绝佳选择,因为它速度快且原生支持 TTL。
- 登出操作:
- 用户点击“退出登录”,客户端发起登出请求(需要携带当前的 JWT)。
- 服务器验证该 JWT 的有效性(签名、过期时间)。
- 服务器将这个有效的 JWT 的唯一标识存入黑名单。通常有两种标识方式:
- 使用
jti(JWT ID):在签发 JWT 时,就在 Payload 中塞入一个唯一标识符(如 UUID)。登出时,将这个jti存入 Redis,并为其设置一个 TTL(这个 TTL 应等于该 JWT 剩余的过期时间)。 - 使用整个 Token:直接将整个 JWT 字符串作为 Key 存入 Redis(价值较低,因为很长)。
- 使用
- 修改验证逻辑:
- 在原有的 JWT 验证流程(检查签名、检查
exp)之后,增加一步: - 查询黑名单:提取当前请求 JWT 中的
jti,去 Redis 中查询是否存在。如果存在,则拒绝访问,返回401 Unauthorized。
- 在原有的 JWT 验证流程(检查签名、检查
流程示意图:#
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)#
这个方案不直接解决“撤销”,而是通过极大地缩短访问令牌的生命周期,来降低“需要撤销”的场景带来的风险。它通常与方案一(黑名单)结合使用,以处理刷新令牌本身的撤销。
令牌设置:
- 访问令牌 (Access Token):有效期非常短,例如 5-15 分钟。用于访问业务接口。
- 刷新令牌 (Refresh Token):有效期很长,例如 7 天或更长。它不用于访问业务,唯一的功能是去换一个新的 Access Token。
登出操作:
- 用户登出时,客户端直接丢弃本地的 Access Token 和 Refresh Token。
- 关键步骤:服务器端需要立即使该用户的 Refresh Token 无效。这意味着你需要将 Refresh Token 或其标识(如与用户ID关联)在数据库中标记为已撤销。这样,即使用户的 Refresh Token 被盗,也无法再获取新的 Access Token。
- 那个可能被泄露的、短期有效的 Access Token,最多只能在剩下的几分钟内被滥用,风险可控。
效果:用户登出后,即使 Access Token 未被加入黑名单,它也会在很短时间内(几分钟)自然过期。而由于 Refresh Token 已被服务器撤销,攻击者无法获取新的令牌,从而实现了实质上的登出。
优点:#
- 大大减少了对黑名单的依赖(理论上只黑刷新令牌即可)。
- 安全性更高,Access Token 泄露的影响窗口期极短。
缺点:#
- 系统更复杂,需要维护 Refresh Token 的签发、验证和撤销机制。
- 仍然需要一个数据库(如 Redis)来管理已撤销的 Refresh Token。
方案三:有状态会话存储(Stateful Session Store) - 不推荐#
这种方案完全放弃了 JWT 的无状态优势,退回到了类似传统 Session 的模式。
- 在数据库中(如 Redis)存储一个
session_id到用户信息的映射。 - 签发 JWT 时,Payload 中只存储一个
session_id。 - 每次验证 JWT 时,不仅要验证签名和过期时间,还必须用这个
session_id去数据库中查询该会话是否仍然有效。 - 用户登出时,直接删除数据库中的这个
session_id记录。
为什么不推荐?#
- 丧失了 JWT 的核心优势:每次请求都需要数据库查询,性能瓶颈和单点故障问题又回来了。
- 设计矛盾:既然每次都要查库,那为什么不直接用传统的 Session ID?JWT 的自包含特性在此方案中毫无用处。
总结与实践建议#
| 方案 | 核心思想 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 令牌黑名单 | 记录已注销的有效令牌 | 实现简单,性能影响小 | 引入了状态 | 最通用,推荐大多数应用采用 |
| 短期令牌+刷新令牌 | 缩短访问令牌寿命,撤销刷新令牌 | 安全性高,泄露风险窗口小 | 架构更复杂 | 对安全性要求极高的应用(如银行、支付) |
| 有状态会话 | JWT 只带会话ID,状态完全存于服务器 | 控制力最强 | 性能差,违背 JWT 初衷 | 不推荐,除非有特殊需求 |
给你的最终建议:
对于绝大多数 Web 或移动应用,采用“方案一(令牌黑名单)”并配合“方案二(短期访问令牌)” 是最佳实践:
- 设置 Access Token 过期时间为 15-30 分钟。
- 实现 Refresh Token 机制,用于获取新的 Access Token。
- 使用 Redis 来维护两个名单:
- Access Token 黑名单:用于用户登出时,废弃那些还未过期的 Access Token。
- 失效的 Refresh Token 名单:用于用户登出或修改密码时,立即使 Refresh Token 失效。
这样,你就在享受 JWT 无状态验证带来的性能好处的同时,拥有了立即让用户“登出”的能力,实现了安全与性能的平衡。