
什么是 JSON Web Token?
JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。由于其经过数字签名,这些信息是可验证且值得信赖的。JWT 可以使用密钥(通过 HMAC 算法)或使用 RSA 或 ECDSA 公钥/私钥对进行签名。
虽然 JWT 也可以加密以在各方之间提供保密性,但我们会重点关注签名令牌。签名令牌可以验证其内部声明(claims)的完整性,而加密令牌会隐藏这些声明。当使用公钥/私钥对来签名令牌时,该签名还能够证明只有持有私钥的一方才是令牌的签名者。
何时应该使用 JSON Web Token?
- 授权(Authorization):这是使用 JWT 的最常见场景。一旦用户登录成功,之后的每个请求都会携带 JWT,从而允许用户访问该令牌所允许的路由、服务和资源。单点登录(Single Sign On)是目前广泛使用 JWT 的一个特性,因为它开销很小,并且可以轻松地跨不同域名使用。
- 信息交换(Information Exchange):JSON Web Token 是在各方之间安全传输信息的良好方式。因为 JWT 可以进行签名(例如,使用公钥/私钥对),你可以确定发送者确实是他们所声称的那样。此外,由于签名是基于头部和载荷进行计算的,因此可以验证内容未被篡改。
JSON Web Token 的结构是什么?
在其紧凑形式中,JSON Web Token 由用点号(.
)分隔的三部分组成:
- Header(头部)
- Payload(载荷)
- Signature(签名)
因此,JWT 通常看起来像下面这样:
xxxxx.yyyyy.zzzzz
下面让我们分解这几个不同的部分。
Header(头部)
头部通常包含两部分信息:令牌的类型(即 JWT)和所使用的签名算法(如 HMAC SHA256 或 RSA)。
例如:
{
"alg": "HS256",
"typ": "JWT"
}
然后,会对这个 JSON 进行 Base64Url 编码,形成 JWT 的第一部分。
Payload(载荷)
令牌的第二部分是载荷,其中包含声明(claims)。声明是关于某个实体(通常是用户)以及附加数据的语句。声明分为三种类型:注册声明、公共声明和私有声明。
- 注册声明(Registered claims):这是一组预定义的声明,它们不是强制性的,但推荐使用,以提供一组有用的、可互操作的声明。其中一些包括:iss(发行者),exp(过期时间),sub(主题),aud(受众),以及其他。
[!NOTE]
请注意,声明名称只有三个字符,这是因为 JWT 旨在保持紧凑。
- 公共声明(Public claims):可以由 JWT 的使用者自行定义。但为了避免冲突,应该在 IANA JSON Web Token Registry 中定义,或者定义为包含无冲突命名空间的 URI。
- 私有声明(Private claims):这些是自定义的声明,用于在彼此同意使用它们的各方之间共享信息,既不是注册声明也不是公共声明。
一个示例载荷:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后会对载荷进行 Base64Url 编码,从而形成 JSON Web Token 的第二部分。
[!CAUTION]
请注意,对于签名令牌来说,这些信息虽然防止被篡改,但任何人都可以读取。因此不要在 JWT 的载荷或头部中放置秘密信息,除非它已被加密。
Signature(签名)
要创建签名部分,需要将编码后的头部、编码后的载荷、一个密钥,以及头部中指定的算法组合起来签名。
例如,如果要使用 HMAC SHA256 算法,签名的创建方式如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名用于验证消息在传输过程中未被篡改,并且对于使用私钥签名的令牌,它还能验证 JWT 的发送者确实是其所声明的。
整体组合
最终的输出是三个用点号分隔的 Base64-URL 字符串,能够在 HTML 和 HTTP 环境中轻松传递,同时相比于基于 XML 的标准(如 SAML)更加紧凑。
下面演示了一个包含上述头部和载荷,并使用密钥签名的 JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6InB1cmV4dXMiLCJpYXQiOjE1MTYyMzkwMjJ9.FfQXaf4S39w9Y3vb_4xTEVA1nPvWu0dEQZkhrMgIbW0
如果你想亲自尝试 JWT 并将这些概念付诸实践,可以使用 jwt.io Debugger 来解码、验证和生成 JWT。
JSON Web Token 是如何工作的?
在身份验证中,当用户使用其凭证成功登录后,会返回一个 JSON Web Token。由于令牌本身就是一种凭证,因此必须非常小心以避免安全问题。通常情况下,不应将令牌的存储时间延长到超过所需的范围。
你也不应该将敏感的会话数据存储在浏览器存储中,因为安全性不足。
当用户想要访问受保护的路由或资源时,用户代理应当发送 JWT,通常是通过在 Authorization 头部中使用 Bearer 模式进行传递。该头部的内容应如下所示:
Authorization: Bearer <token>
在某些情况下,这可以是一种无状态的授权机制。服务器端的受保护路由会检查 Authorization
头部中是否存在有效的 JWT,如果存在,用户就可以访问受保护的资源。如果 JWT 包含必要的数据,有些操作可能无需查询数据库,但这并非总是适用。
需要注意的是,如果你通过 HTTP 头部发送 JWT,应该尽量避免令牌过大。一些服务器不接受超过 8 KB 的头部数据。如果你试图在 JWT 中嵌入过多信息(例如包含所有用户权限),那可能需要使用替代方案,例如 Auth0 Fine-Grained Authorization。
如果令牌是通过 Authorization
头部发送的,跨域资源共享(CORS)将不会成为问题,因为它不使用 Cookie。
下面的时序图展示了如何获取 JWT 并使用它来访问 API 或资源:
- 应用或客户端向授权服务器请求授权。这通常通过各种授权流程完成。例如,一个典型的 OpenID Connect 兼容的 Web 应用会使用 授权码流程 通过
/oauth/authorize
端点请求授权。 - 当授权被授予后,授权服务器会向应用返回一个访问令牌。
- 应用使用该访问令牌来访问受保护的资源(如 API)。
[!CAUTION]
请注意,对于签名令牌,令牌内包含的所有信息都对用户或其他各方公开,尽管他们无法修改它。这意味着你不应该将敏感信息放入令牌中。
为什么要使用 JSON Web Token?
JSON Web Token 的优点包括:
- 紧凑:JWT 非常紧凑,适合在 HTTP 头部中传递。
- 灵活:JWT 可以包含多种类型的声明,包括注册声明、公共声明和私有声明。
- 安全:JWT 可以签名,以确保消息在传输过程中未被篡改。
JSON Web Token 的缺点包括:
- 不适合存储大量数据。
- 不适合存储敏感信息。
- 不适合存储大量数据。