一句话概括
JSON Web Token (JWT) 是一种紧凑且 URL 安全的方式,用于在双方之间传递声明(claims),其可被验证和信任,通常用于身份验证和授权。
它解决了什么问题
在很久很久以前——比方说,21 世纪初那会儿——如果你登录一个网站,服务器会为你创建一个“会话(session)”。这就像服务器上的一个小文件,记录着“用户 123 已登录,购物车里放了一只橡皮鸡”。然后服务器会给你的浏览器一个带会话 ID 的小 cookie,就像一张衣帽间的寄存牌。在之后的每一次请求中,你的浏览器都会出示这张“寄存牌”,服务器查找对应的文件,然后想起你是谁。
对于单个、庞大的单体服务器来说,这套机制运行良好。但后来,Web 世界迎来了大爆炸。我们有了微服务、单页应用(SPA)和移动应用,它们都和同一个后端打交道。现在,你的登录请求可能发给了服务器 A,但下一个获取你个人资料的请求可能就跑到了服务器 B。服务器 B 怎么知道服务器 A 上的会话文件呢?
你可以强制用户总是和同一个服务器通信(也就是“粘性会话”),但这会造成瓶颈。你也可以创建一个所有服务器共享的中心化会话数据库(比如 Redis),但这又增加了一个需要管理的基础设施,也多了一个潜在的故障点。
核心问题在于状态性(statefulness)。服务器必须“记住”你。
JWT(读作 'jot')则彻底颠覆了这种思路。要是用户能自己带着身份证明(就像一本护照),那会怎么样?令牌(Token)本身就包含了服务器需要的所有信息:用户是谁、他们被允许做什么、以及他们的访问权限何时过期。这样,服务器在两次请求之间就无需记住任何事情。这就是无状态(stateless)认证,也是构建可扩展、分布式系统的关键。服务器只需要检查这本“护照”(JWT)是否有效、有没有被伪造。
工作原理揭秘
JWT 不是一坨看不懂的乱码。它是一个结构非常明确的字符串,由三个部分组成,并用点(.)分隔。
xxxxx.yyyyy.zzzzz
我们来逐一分解每个部分。
Header (头部):类型标签
第一部分是头部(Header)。它是一个简单的 JSON 对象,包含了关于令牌本身的元数据,主要是所使用的签名算法和令牌类型。
{
"alg": "HS256",
"typ": "JWT"
}
alg: 签名算法。HS256表示这个令牌是用 HMAC-SHA256 签名的,这是一种对称算法(稍后详述)。其他常见选项包括RS256(使用 RSA 公私钥对)。typ: 令牌类型。对 JWT 来说,这里就是 "JWT"。
这个 JSON 对象随后会经过 Base64Url 编码,生成令牌的第一部分。Base64 是一种编码方案,不是加密。它只是把二进制数据转换成可以在网络上传输的文本字符串。把它想象成在明信片背面写上“这是一张明信片”——任何截获它的人都能读懂。
Payload (载荷):声明信息部
第二部分是载荷(Payload)。这部分是干货。它是另一个 JSON 对象,包含了“声明(claims)”,也就是关于用户(即“主体”)的陈述以及其他有用数据。
{
"sub": "10987-23456-98765",
"name": "Grace Hopper",
"admin": true,
"iat": 1516239022,
"exp": 1516242622
}
声明分为三种:
- 注册声明 (Registered Claims): 这是一组预定义的、推荐使用的声明,以提供互操作性。它们不是强制性的,但超级有用。
| 声明 | 名称 | 描述 |
|---|---|---|
iss |
Issuer (签发者) | 令牌是谁签发的 (例如, https://api.mycoolsite.com)。 |
sub |
Subject (主题) | 令牌是关于哪个用户或实体的 (例如, 用户 ID)。 |
aud |
Audience (受众) | 令牌是发给谁的 (例如, https://api.mycoolsite.com)。 |
exp |
Expiration Time (过期时间) | 令牌何时过期。一个数字类型的 Unix 时间戳 (从 epoch 开始的秒数)。 |
iat |
Issued At (签发时间) | 令牌是何时签发的。同样是 Unix 时间戳。 |
- 公共声明 (Public Claims): 这些是你创建的自定义声明,但为了避免命名冲突,它们应该在 IANA JSON Web Token Claims registry 中定义,或者是一个包含防冲突命名空间的 URI。
- 私有声明 (Private Claims): 这些是最常见的自定义声明,用于在同意使用它们的各方之间共享信息(就像我们例子中的
admin: true)。这里就是你放置应用特定数据的地方。
和头部一样,整个载荷 JSON 对象也会经过 Base64Url 编码,形成 JWT 的第二部分。再次强调,这不是加密。永远不要在载荷里放密码之类的敏感信息。
Signature (签名):防伪封条
这部分提供了安全性。签名用于验证 JWT 的发送者确实是它所声称的那个人,并确保消息在传输过程中没有被篡改。
签名的创建过程是:将编码后的头部、编码后的载荷和一个密钥(secret)一起,通过头部指定的算法进行处理。对于我们的 HS256 例子,过程如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
关键就在于:这个过程用到了一个只有服务器才知道的secret(密钥)。当服务器收到一个 JWT 时,它会用收到的头部和载荷,以及它自己保存的密钥,重新执行完全相同的计算。如果它生成的签名与令牌上的签名匹配,服务器就能确定两件事:
- 真实性 (Authenticity): 这个令牌是由知道密钥的人(也就是服务器自己)创建的。
- 完整性 (Integrity): 头部和载荷没有被篡改过。如果攻击者把载荷里的
"admin": false改成了"admin": true,签名就会对不上了。
这个签名,就是我们这本“护照”上的防伪全息封条。
真实世界的应用
微服务迷宫
一家快速增长的电商公司 "ScaleFast" 决定将其庞大的单体后端拆分成一系列微服务:一个管用户,一个管订单,一个管库存,等等。旧系统使用服务器端会话。但在新世界里,OrderService(订单服务)怎么知道一个请求真的来自一个已登录的用户,而不需要每次都去调用 UserService(用户服务)呢?那样做太慢了,也违背了解耦的初衷。
解决方案就是 JWT。当用户登录时,新的 AuthService(认证服务)会签发一个包含 userId 和用户 roles(角色)的 JWT。用户的浏览器随后在每次请求其他微服务时,都在 Authorization 请求头里带上这个 JWT。OrderService 和 InventoryService 不需要和 AuthService 通信;它们只需要知道共享的密钥。它们可以独立地验证 JWT 的签名,信任里面的 userId,然后处理请求。
经验: JWT 是微服务认证的通用语,它让服务变得无状态且可独立验证。
单页应用传奇
一位名叫 Alex 的开发者正在构建一个酷炫的 React 仪表盘。前端是一个托管在静态服务器上的单页应用(SPA),它与一个独立的后端 API 通信。Alex 最初在跟传统的基于 cookie 的认证方式作斗争,结果因为前端和后端在不同域上,陷入了跨域资源共享(CORS)问题的噩梦。
团队转而使用 JWT。现在,用户用用户名和密码登录后,API 会返回一个 JWT。Alex 的 React 应用将这个令牌存储在内存中,并附加到每个 API 调用上:Authorization: Bearer <the-jwt>。API 后端是无状态的;它只需检查每个传入请求的 bearer token。再也没有 CORS 和 cookie 带来的头痛了。
经验: JWT 提供了一种干净、可移植的凭证,非常适合将现代前端应用与后端 API 解耦。
常见的误区和陷阱
- 在载荷中存放敏感数据。 快住手!载荷是 Base64Url 编码的,可以被轻易逆向解码。它没有被加密。任何拿到令牌的人都可以读取载荷。把它当作一张明信片,而不是一封密封的信。
- 忘记验证签名。 如果边检员不检查护照上的防伪特征,那这些特征还有什么意义?仅仅解码载荷并信任其内容而不验证签名,是一个灾难性的安全漏洞。攻击者可以随心所欲地伪造任何他们想要的载荷。
- 盲目信任
alg头部。 过去一个著名的漏洞是,攻击者创建一个令牌,并将头部改为{"alg": "none"}。一些配置不当的库看到 "none" 后,就会通过……什么都不做……来“验证”签名,从而接受了这个伪造的令牌。你的服务器应该始终强制使用一个特定的、预期的算法(例如HS256)。 - 泄露你的对称密钥。 对于像
HS256这样的 HMAC 算法,密钥就是王国的钥匙。如果它泄露了,任何人都可以为任何用户伪造具有任何权限的令牌。像保护密码一样保护它。 - 不设置过期时间(
exp声明)。 一个永久有效的令牌是一个巨大的隐患。如果它被泄露,攻击者就可以无限期地使用它。始终设置一个合理的短过期时间,并使用刷新令牌(refresh token)机制来实现更长久的会话。
为什么你应该关注它
只要你在分布式环境中处理身份验证或授权,就应该想到 JWT。
- 你正在为单页应用(SPA)或移动客户端构建 API。
- 你正在设计一个微服务架构,其中服务之间需要相互信任请求。
- 你需要无状态的身份验证,以便在没有共享会话存储的情况下进行水平扩展。
- 你正在实现一次性的授权流程,比如密码重置链接或电子邮件验证,这种场景下,一个自包含、可过期的令牌是完美的选择。
它是当今安全表示声明的现代标准,理解它的优点——以及缺点——对今天的开发者来说是必备技能。
深入了解
- RFC 7519: JSON Web Token (JWT) 的官方规范。真理之源。
- jwt.io: 一个极好的资源,提供在线调试器和几乎所有语言的库列表。
- OWASP JWT Cheat Sheet: 一份关于使用 JWT 的安全最佳实践和陷阱的重要指南(其中的建议与语言无关)。
- MDN Web Docs: Authorization header: 了解常用于传输 JWT 的
Bearer认证方案。 - Wikipedia: JSON Web Token: 对该概念及其历史的一个很好的高层次概述。