如何安全使用 JWT 作为访问凭证
目录
一、背景介绍:为什么我们需要更好的访问凭证?
1.1 什么是「访问凭证」?
在互联网世界中,当一个应用程序(比如你的手机 App)想要访问另一个服务器上的数据时,它需要出示一个「身份证明」来告诉服务器:「我是谁,我有权限拿到这些数据」。这个身份证明,就是我们所说的 「访问凭证」(Access Credential)。
你可以把它想象成进入一栋大楼时需要刷的门禁卡——没有卡就进不去,而卡上面记录了你是谁、你能去哪些楼层。
1.2 传统方式:静态 API Key 的隐患
长期以来,最常见的访问凭证是 静态 API Key——一串固定不变的字符串,就像一把永远不换锁的钥匙。开发者把这个 Key 放在代码或配置文件中,每次请求都带上它来证明身份。
这种方式在过去或许够用,但随着互联网安全威胁的不断升级,静态 API Key 暴露出越来越多的安全问题:
| 问题 | 说明 | 通俗比喻 |
|---|---|---|
| 一旦泄露,后果严重 | API Key 通常是长期有效的。如果它被意外提交到 GitHub 代码仓库、写在日志里,或者通过网络被截获,攻击者就可以冒充你的身份无限期地访问数据。 | 家门钥匙被人复制了,而你浑然不知 |
| 权限过于粗放 | 大多数 API Key 无法精细地控制「谁能做什么」。拿到 Key 的人往往获得了全部权限,无法做到「只允许读取,不允许修改」这样的细粒度控制。 | 一把万能钥匙,能打开大楼里所有的门 |
| 无法溯源和审计 | 一个 API Key 可能被多个人或多个系统共用,一旦出现安全事件,很难追溯是谁、在什么时候、用这个 Key 做了什么。 | 全公司共用一张门禁卡,出了事说不清是谁 |
| 缺乏过期机制 | 静态 Key 没有内建的过期时间。除非人工去轮换(revoke 并重新生成),否则它会一直有效。 | 一张永远不过期的身份证,丢了也不会自动作废 |
1.3 Agent 时代的新挑战
我们正在进入一个 AI Agent(智能代理) 的新时代。越来越多的 AI 应用需要自主地代表用户去调用各种 API 和服务——浏览网页、发送邮件、操作数据库、调用第三方接口。在这个场景下,静态 API Key 的问题被进一步放大:
- Agent 需要访问更多服务: 一个 AI Agent 可能同时对接十几个不同的服务,每个服务都用 API Key,密钥管理变成噩梦。
- Agent 的调用频率极高: AI Agent 可能在短时间内发起大量请求,一旦 Key 泄露,被恶意利用的速度和规模远超人工操作。
- Agent 间的信任传递更复杂: Agent A 调用 Agent B,Agent B 再调用服务 C,这种链式调用需要一套可靠的信任传递机制,而静态 Key 无法胜任。
因此,业界迫切需要一种 更安全、更灵活、更现代化的访问凭证方案。这就是本文要介绍的主角——JWT(JSON Web Token)。
二、JWT 核心概念详解
2.1 什么是 JWT?
JWT 的全称是 JSON Web Token(读作 "jot"),由 RFC 7519 标准定义。简单来说,JWT 是一种将信息安全地编码成一段紧凑字符串的标准格式。
你可以把 JWT 想象成一张「智能门禁卡」:
- 它上面记录了持卡人的身份信息(比如用户 ID、角色等)
- 它有明确的有效期(过期后自动失效)
- 它带有防伪标记(数字签名),一旦被篡改立刻能被发现
一个标准的 JWT 由三个部分组成,用英文句点(.)分隔:
xxxxx.yyyyy.zzzzz
分别对应:Header(头部) · Payload(载荷) · Signature(签名)。
下图直观地展示了 JWT 的三段式结构:

图 1:JWT 由 Header、Payload、Signature 三部分组成,使用 "." 分隔后拼接成一个紧凑的字符串
下面逐一解释每个部分。
Header(头部)
头部通常包含两个信息:令牌的类型(JWT)和所使用的签名算法。例如:
{
"alg": "RS256",
"typ": "JWT"
}
这段 JSON 会被 Base64Url 编码,成为 JWT 的第一部分。其中 alg 字段指定了签名算法,这是安全验证的核心,后面我们会重点讨论。
Payload(载荷)
载荷是 JWT 的主体部分,包含了我们想传递的实际信息。这些信息以「声明」(Claims)的形式存在。例如:
{
"sub": "user123",
"name": "张三",
"role": "admin",
"exp": 1700000000
}
JWT 标准定义了一组「注册声明」(Registered Claims),这些是被广泛使用的标准字段:
| 缩写 | 全称 | 含义 |
|---|---|---|
iss |
Issuer | 签发者,即谁签发了这个令牌 |
sub |
Subject | 主体,即这个令牌是关于谁的(通常是用户 ID) |
aud |
Audience | 受众,即这个令牌是给谁用的(目标服务) |
exp |
Expiration | 过期时间,超过此时间令牌失效 |
nbf |
Not Before | 生效时间,在此时间之前令牌不可用 |
iat |
Issued At | 签发时间,即令牌的创建时间 |
jti |
JWT ID | 令牌唯一标识,防止令牌被重放 |
重要提示: Payload 中的数据仅仅是 Base64Url 编码,并非加密。也就是说,任何人都可以解码并读取其中的内容。因此,绝对不要在 Payload 中放置密码、密钥等敏感信息。
Signature(签名)
签名是 JWT 安全性的核心保障。它的作用是确保令牌内容没有被篡改。签名的生成过程可以简单理解为:
签名 = 签名算法( Base64Url(Header) + "." + Base64Url(Payload), 密钥 )
验证方收到 JWT 后,会用同样的方式重新计算签名,如果计算结果与 JWT 中携带的签名一致,就说明这个令牌是真实可信的、没有被篡改过。
给产品经理的比喻: 签名就像纸币上的防伪水印。你不需要去银行验证每一张钞票,只要对着光看一下水印是否正确就行。如果有人涂改了钞票上的金额数字,水印验证就会失败——JWT 的签名验证也是同样的道理。
2.2 JWT 家族:JWS、JWE、JWK 和 JWA
JWT 并不是一个孤立的标准,它是一个标准家族的一部分。理解这些相关标准有助于你全面把握 JWT 的安全体系:
| 标准 | RFC 编号 | 一句话概括 | 通俗理解 |
|---|---|---|---|
| JWS (JSON Web Signature) | RFC 7515 | 定义了如何对 JWT 进行数字签名,保证数据完整性和来源可信性 | 「带签名的 JWT」——日常使用的 JWT 绝大多数都是这个格式 |
| JWE (JSON Web Encryption) | RFC 7516 | 定义了如何对 JWT 进行加密,保护 Payload 中的敏感信息 | 「加密的 JWT」——不仅签名防篡改,还加密防偷看 |
| JWK (JSON Web Key) | RFC 7517 | 定义了密钥的 JSON 表示格式,通过 JWKS 端点公开发布公钥 | 「钥匙的标准格式」——大家按同一个格式交换密钥 |
| JWA (JSON Web Algorithms) | RFC 7518 | 定义了可以使用的加密算法列表(如 RS256、ES256) | 「算法菜单」——选择正确且安全的算法是安全使用 JWT 的关键 |
一般来说,我们讨论的「JWT」默认指的就是 JWS 格式——即经过签名但未加密的令牌。
2.3 OAuth 2.0 和 OIDC 中的 JWT
OAuth 2.0 是当前最主流的授权框架(RFC 6749),它定义了应用如何安全地获取访问权限。OIDC(OpenID Connect) 则是在 OAuth 2.0 之上增加了身份认证层的协议。
在 OAuth 2.0 的体系中,用户(或应用)通过授权服务器获得一个 访问令牌(Access Token),然后拿着这个令牌去访问资源服务器上的数据。而 JWT 正是承载 Access Token 的理想格式。
RFC 9068 专门定义了 OAuth 2.0 中 JWT 格式 Access Token 的标准规范(JSON Web Token Profile for OAuth 2.0 Access Tokens)。它规定了 JWT Access Token 中必须包含哪些声明、如何签名、如何验证等关键细节。
下图展示了一个典型的 JWT Access Token 签发与验证的完整流程:

图 2:OAuth 2.0 体系下,JWT Access Token 从签发到验证的完整生命周期
具体步骤如下:
- 用户登录认证: 用户向授权服务器(Authorization Server)提交用户名密码或其他认证方式。
- 授权服务器签发 JWT: 验证通过后,授权服务器使用私钥对 JWT 进行签名,生成包含用户身份、权限范围(scope)、有效期等信息的 Access Token。
- 客户端携带 JWT 访问资源: 客户端将 JWT 放在 HTTP 请求的
Authorization头中(Bearer Token 方式),发送给资源服务器。 - 资源服务器验证 JWT: 资源服务器从授权服务器的 JWKS 端点获取公钥,验证 JWT 的签名和各项声明,确认合法后提供数据。
这种模式的核心优势: 资源服务器无需每次都去授权服务器「问一下」这个令牌是否有效(这是传统 opaque token 的做法),它可以自行验证 JWT 的合法性,大大提升了性能和可扩展性。这对于需要高频调用的 AI Agent 场景尤为重要。
三、如何安全验证一个 JWT Access Token?
JWT 的强大之处在于它是「自包含」的——验证方可以仅凭令牌自身的信息来判断其合法性。但这也意味着,如果验证步骤不够严谨,就可能被攻击者钻空子。
以下是验证 JWT Access Token 的完整安全检查清单:

图 3:JWT 安全验证的六步检查清单——任何一步失败都应拒绝令牌
3.1 第一步:验证签名(最关键)
签名验证是整个安全体系的 基石。如果签名验证失败或被绕过,后续所有检查都毫无意义。
必须做到:
严格指定允许的签名算法: 在验证时,服务端必须明确指定接受哪些算法(如只接受 RS256),而 绝不能 让 JWT 自身的 Header 中的
alg字段来「告诉」验证方该用什么算法。这是防止「算法混淆攻击」的关键措施。使用非对称签名算法: 推荐使用
RS256(RSA + SHA-256)或ES256(ECDSA + SHA-256)。非对称算法使用「私钥签名,公钥验签」的模式,即使公钥泄露也不会危及安全。禁止
none算法: JWT 规范中定义了一个名为"none"的算法,表示不做签名。在生产环境中,验证方必须显式拒绝此算法,否则攻击者可以伪造无签名的令牌来绕过验证。正确管理密钥: 通过 JWKS(JSON Web Key Set)端点获取签名公钥,定期轮换密钥,并支持多密钥验证(通过
kid字段匹配正确的公钥)。
3.2 第二步:验证时间相关声明
时间声明确保令牌在正确的时间窗口内使用。
| 声明 | 是否必须检查 | 说明 |
|---|---|---|
exp |
必须 | 当前时间如果超过 exp 的值,令牌就已过期,必须拒绝。建议 Access Token 的有效期设置得尽可能短(如 5-15 分钟)。 |
nbf |
如果存在则必须 | 当前时间必须大于或等于 nbf 的值,令牌才有效。 |
iat |
建议检查 | 可用于判断令牌是否「太旧」,即使它还没过期。 |
关于时钟偏移: 不同服务器的系统时钟可能有微小差异,通常允许几秒(如 30 秒)的容忍度,但不宜过大。
3.3 第三步:验证签发者(iss)
iss 声明标识了令牌的签发方。验证方必须检查 iss 是否在自己信任的签发者列表中。
例如,你的系统只信任 https://auth.example.com 签发的令牌,那么任何 iss 不是这个值的令牌都应被拒绝。这可以防止攻击者使用其他授权服务器签发的令牌来冒充身份。
3.4 第四步:验证受众(aud)
aud 声明指定了令牌的目标接收方。验证方必须检查 aud 中是否包含自己的标识符。
这是为了防止「令牌混用」攻击:假设用户从同一个授权服务器获得了两个令牌,一个给服务 A,一个给服务 B。如果服务 A 不检查 aud,攻击者就可以拿给服务 B 的令牌来访问服务 A 的资源。
3.5 第五步:验证其他关键声明
scope/scp(权限范围): 检查令牌是否拥有执行当前操作所需的权限。例如,一个只有"read"scope 的令牌不应被允许执行"write"操作。sub(主体): 确认令牌中的用户标识符是否合法。jti(令牌 ID): 如果你的系统需要防止令牌重放攻击,可以记录已使用的jti,拒绝重复使用同一令牌。typ(令牌类型): RFC 9068 建议 JWT Access Token 应包含值为"at+jwt"的typ声明,以区分不同用途的令牌(如 ID Token 的typ值不同)。验证方应检查此字段。
3.6 完整的验证流程总结
将上述步骤综合起来,一个安全的 JWT 验证流程如下:
| 步骤 | 验证项 | 说明 | 失败处理 |
|---|---|---|---|
| 1 | 格式检查 | 确认 JWT 包含三个 Base64Url 编码的部分,用 . 分隔 |
返回 400 Bad Request |
| 2 | 解析 Header | 读取 alg 和 kid 字段,确认算法在允许列表中 |
返回 401 Unauthorized |
| 3 | 获取公钥 | 根据 kid 从 JWKS 端点获取对应的公钥 |
返回 401 Unauthorized |
| 4 | 验证签名 | 使用公钥和指定算法验证 JWT 签名的完整性 | 返回 401 Unauthorized |
| 5 | 验证 exp / nbf / iat | 检查令牌是否在有效时间窗口内 | 返回 401 Unauthorized |
| 6 | 验证 iss | 确认签发者在信任列表中 | 返回 401 Unauthorized |
| 7 | 验证 aud | 确认令牌的目标受众包含当前服务 | 返回 401 Unauthorized |
| 8 | 验证 scope / 权限 | 确认令牌拥有当前操作所需的权限范围 | 返回 403 Forbidden |
| 9 | 验证 typ | 确认令牌类型为 "at+jwt"(适用于 RFC 9068) |
返回 401 Unauthorized |
给产品经理的比喻: JWT 验证就像是机场安检。第一步(签名验证)相当于检查护照的防伪水印是否真实;第二步(时间验证)是检查护照是否过期;第三四步(iss 和 aud 验证)是检查你是不是从正确的航空公司柜台过来的、是不是这个航班的旅客;最后(scope 验证)是确认你的登机牌允许你进入商务舱还是经济舱。每一步都不可省略。
四、前车之鉴:JWT 验证不当的真实安全事件
理论上的安全建议往往不如真实的安全事故来得有说服力。以下是几个因 JWT 校验不当而导致的知名安全事件,它们深刻地说明了为什么每一个验证步骤都至关重要。
下图概括了四种最常见的 JWT 攻击类型:

图 4:四种最常见的 JWT 攻击类型及其核心原理
4.1 "none" 算法攻击——多个 JWT 库集体沦陷(2015)
事件概述
2015 年,安全研究员 Tim McLean 发现包括 node-jsonwebtoken、PyJWT、php-jwt 等 多个主流 JWT 库 存在严重的安全漏洞。这些库在验证 JWT 签名时,会信任令牌 Header 中声明的 alg 字段。攻击者只需将 alg 设置为 "none"(表示无需签名),就可以完全绕过签名验证,伪造任意令牌。
攻击原理简述
正常的 JWT Header: { "alg": "RS256", "typ": "JWT" } --> 需要验证签名
攻击者的 Header: { "alg": "none", "typ": "JWT" } --> 跳过签名验证!
攻击者伪造 Payload 中的用户身份信息,配合 "alg": "none" 的 Header,生成的令牌不需要任何签名即可通过验证。
影响
攻击者无需知道任何密钥即可伪造合法令牌,冒充任意用户身份。几乎所有主流编程语言的 JWT 库都受到了影响,波及大量线上服务。
根本原因
这些库的验证逻辑 完全依赖令牌自身声明的算法(即 Header 中的 alg 字段),而没有让验证方自行指定接受哪些算法。这相当于让「来访者自己决定用什么方式过安检」。
参考链接: Auth0 官方博文:Critical Vulnerabilities in JSON Web Token Libraries
4.2 算法混淆攻击——RS256 降级为 HS256
事件概述
这是与 "none" 算法攻击同期发现的另一类更隐蔽的攻击。当服务端使用非对称算法(如 RS256)时,它使用 私钥签名,公钥验签。但如果验证方信任 JWT Header 中的 alg 字段,攻击者可以将 alg 改为 HS256(对称算法),然后用已知的 公钥 作为 HMAC 密钥来签名令牌。由于验证方的代码看到 HS256,就会用相同的「公钥」来做 HMAC 验签——结果验证通过!
攻击原理简述
正常流程 (RS256):
签名: 私钥(Header + Payload) --> 验签: 公钥(Header + Payload, Signature) ✓
攻击流程 (RS256 → HS256):
攻击者: HMAC-SHA256(Header + Payload, 公钥) --> 服务端看到 HS256,用「公钥」做 HMAC 验签 ✓
公钥是公开的,攻击者轻松获取!
持续影响
多年后,类似的漏洞仍在不断出现:
- CVE-2022-29217(PyJWT): PyJWT 2.4.0 之前的版本中,攻击者可以利用算法混淆漏洞绕过签名验证,影响所有使用 PyJWT 进行非对称签名验证的 Python 应用。
- CVE-2024-54150(cjwt): C 语言的 JWT 库 cjwt 中发现的算法混淆漏洞(CVSS 7.5),攻击者可以将 RS256 令牌伪装为 HS256 令牌来绕过验证。
根本原因
验证方没有在代码中 硬编码(或白名单化)允许的签名算法,而是依赖令牌中的声明,导致攻击者可以操控算法选择。
参考链接:
4.3 jsonwebtoken 库多个高危漏洞(2022)
事件概述
2022 年 12 月,GitHub 上下载量最大的 JWT 库之一——Node.js 的 jsonwebtoken(每周超过 3600 万次下载)——被披露存在 多个安全漏洞:
| CVE 编号 | 问题描述 | 潜在影响 |
|---|---|---|
| CVE-2022-23529 | 不安全的密钥输入验证,可注入恶意密钥对象 | 可能导致远程代码执行 |
| CVE-2022-23539 | 对弱密钥类型的校验不足,允许不安全的密钥长度 | 削弱密码学保护 |
| CVE-2022-23540 | 未指定算法时,验证函数缺乏算法校验 | 存在签名验证绕过风险 |
| CVE-2022-23541 | 不安全的密钥检索实现 | 特定配置下可能导致验证绕过 |
影响
由于 jsonwebtoken 库被海量 Node.js 项目依赖(包括许多大型企业应用),这些漏洞的影响范围极其广泛。IBM Maximo Application Suite 等商业产品也发布了相应的安全公告。
根本原因
库的实现在多个层面缺乏严格的 输入验证 和 安全默认值:未强制要求指定算法白名单,对密钥输入缺乏类型和长度校验等。
参考链接:
4.4 kid 字段注入攻击
事件概述
JWT Header 中的 kid(Key ID)字段用于告诉验证方应该使用哪个密钥来验证签名。在某些实现中,服务端会直接将 kid 的值用于数据库查询或文件路径读取。如果对 kid 字段缺乏输入验证,攻击者可以注入恶意内容:
SQL 注入场景:
-- 正常查询
SELECT key FROM keys WHERE kid = 'my-key-id'
-- 攻击者将 kid 设置为:
-- ' UNION SELECT 'attacker-controlled-key' --
-- 查询变为:
SELECT key FROM keys WHERE kid = '' UNION SELECT 'attacker-controlled-key' --'
目录遍历场景:
正常路径: /keys/my-key-id.pem
攻击者的 kid: ../../../dev/null --> 验证使用空密钥
攻击者的 kid: ../../../etc/passwd --> 读取服务器上的任意文件
影响
攻击者可能实现签名绕过、数据泄露甚至远程代码执行,具体取决于注入点和后端实现。
根本原因
开发者在处理 JWT Header 中的字段时,没有像对待用户输入一样进行严格的验证和过滤。kid 字段来自不受信任的令牌,必须被视为潜在的恶意输入。
参考链接:
4.5 fast-jwt 库签名验证绕过(2025)
事件概述
2025 年初,Node.js 的 fast-jwt 库(5.0.6 之前版本)被发现存在签名验证绕过漏洞(CVE-2025-30144)。该库在校验 JWT 签名时未正确验证签名的完整性,攻击者可以通过构造特殊的令牌来绕过签名检查。
影响
所有使用受影响版本 fast-jwt 库的应用都可能面临认证绕过的风险。攻击者可以伪造令牌来冒充任意用户。
根本原因
库的签名验证逻辑存在实现缺陷,未能严格按照 JWS 规范(RFC 7515)执行完整的签名验证流程。
4.6 教训总结
从上述事件中,我们可以提炼出以下关键教训:
| 攻击类型 | 防御措施 | 相关章节 |
|---|---|---|
none 算法攻击 |
在验证端硬编码允许的算法白名单,显式拒绝 "none" |
3.1 |
| 算法混淆攻击 | 不信任令牌中声明的算法,由验证端指定算法;使用类型安全的密钥接口 | 3.1 |
| 库实现缺陷 | 及时更新依赖;选择活跃维护的库;关注安全公告 | 5 |
kid 注入攻击 |
将 JWT Header 中的所有字段视为不可信输入,严格验证和过滤 | 3.1 |
| 签名验证绕过 | 选择经过安全审计的库;编写集成测试覆盖异常令牌场景 | 3.1 |
五、总结
在 AI Agent 快速发展的今天,访问凭证的安全性从未像现在这样重要。静态 API Key 的种种安全隐患——长期有效、权限粗放、难以溯源——使其越来越难以满足现代应用的安全需求。JWT 作为一种标准化、自包含、可验证的令牌格式,为我们提供了一条更安全的道路。
但 JWT 本身并不是银弹。它的安全性完全取决于你是否正确地使用和验证它。 历史上的众多安全事件反复证明:一个验证步骤的疏忽,就可能让整个安全体系形同虚设。
以下是本文最核心的六条安全建议,值得每一位开发者和技术决策者铭记:
永远不要信任令牌中声明的算法。 在验证端硬编码允许的签名算法白名单(推荐 RS256 或 ES256),显式拒绝
"none"算法。这一条措施可以防御多种最高危的攻击。完整验证所有关键声明。 包括
exp(过期时间)、iss(签发者)、aud(受众)、scope(权限范围)和typ(令牌类型)。任何一项的遗漏都可能成为攻击入口。使用短生命周期的令牌。 Access Token 的有效期建议控制在 5-15 分钟以内,搭配 Refresh Token 机制来获取新令牌。
将 JWT Header 视为不可信输入。
kid等字段必须经过严格验证和过滤,防止注入攻击。选择成熟、活跃维护的 JWT 库。 及时关注安全公告和版本更新,确保使用最新的安全补丁。
遵循 RFC 9068 标准。 使用标准化的 JWT Access Token 格式,可以最大程度地降低实现错误的风险。
安全不是一次性的工作,而是持续的实践。希望这篇文档能帮助你理解 JWT 的核心安全原理,并在实际项目中正确地使用它。当你面对一个 JWT 令牌时,请始终记住:信任是需要验证的,每一步验证都不可或缺。
六、参考文档
RFC 标准文档
- RFC 7519 - JSON Web Token (JWT) —— JWT 核心标准
- RFC 7515 - JSON Web Signature (JWS) —— JWS 签名标准
- RFC 7516 - JSON Web Encryption (JWE) —— JWE 加密标准
- RFC 7517 - JSON Web Key (JWK) —— JWK 密钥格式标准
- RFC 7518 - JSON Web Algorithms (JWA) —— JWT 算法标准
- RFC 9068 - JWT Profile for OAuth 2.0 Access Tokens —— OAuth 2.0 JWT Access Token 标准
- RFC 6749 - The OAuth 2.0 Authorization Framework —— OAuth 2.0 框架标准
- RFC 9700 - Best Current Practice for OAuth 2.0 Security —— OAuth 2.0 安全最佳实践
安全研究与漏洞报告
- Critical Vulnerabilities in JSON Web Token Libraries (Auth0)
- Algorithm Confusion Attacks (PortSwigger)
- CVE-2022-23529 - jsonwebtoken Vulnerability (Unit42)
- CVE-2022-29217 - PyJWT Algorithm Confusion (NVD)
- CVE-2024-54150 - cjwt Algorithm Confusion (NVD)
- CVE-2025-30144 - fast-jwt Signature Bypass (NVD)