JWT 是什么

一个 JSON Web Token(,定义于 RFC 7519)是一种紧凑、自包含的方式,用来携带一组 claims,即关于某实体(通常是用户)的陈述外加一些元数据,其形式可供接收方验证。它是大多数现代 Web 认证背后的令牌格式:一个 OpenID Connect 的 ID 令牌就是一个 JWT,而 2.0 的访问令牌也常常是。

“自包含”是核心思想。与一个服务器必须在数据库中查找的随机会话 id 不同,JWT 把 claims 携带在自身之内,经过签名,使接收方无需一次往返便可信任它们。这使 JWT 对无状态、分布式系统颇为便利,并在末尾讨论一个重要的取舍。

三个段

一个 JWT 是三个以点连接的 Base64URL 编码的段:

header.payload.signature

每个段各司其职:

  • 头部(header)是一个小的 JSON 对象,指明签名算法与令牌类型,例如 {"alg":"HS256","typ":"JWT"}alg 值告诉接收方如何验证签名。
  • 载荷(payload)是 claims 的 JSON 对象。它持有具有标准含义的已注册 claims(iss 签发者、sub 主体、aud 受众、exp 过期、nbf 不早于、iat 签发于、jti 令牌 id),以及签发者添加的任何自定义 claims。
  • 签名(signature)是用头部中的算法,对编码后的头部与载荷一并计算而成。它正是使前两个段可信的东西。

由于头部与载荷只是 Base64URL 编码的,并非加密,任何持有令牌的人都能将其解码并读取。这是最重要的一条须内化的性质:JWT 的载荷是可读的,并非保密的。 切勿在其中放入密码、卡号或任何敏感之物。

签名:信任如何建立

签名正是把一个 JWT 与任何客户端能伪造的字符串区分开来的东西。它如何产生取决于算法族:

  • 、HS384、HS512) 用一个共享秘密签名。签名是 HMAC-SHA256(base64url(header) + "." + base64url(payload), secret)。它是对称的:同一个秘密既创建又验证,因此签发者与验证者必须共享它。这正是 HMAC 工具所计算的构造。
  • 及其他) 用一个私钥签名,并用相匹配的公钥验证。这是非对称的:唯有签发者能签名,而任何持有公钥者皆可验证,这适用于由许多独立服务检查的令牌。

验证会针对编码后的头部与载荷重新计算或核对签名。若任一段哪怕一个字节被改动,签名便不再相符,令牌即被拒绝。

有两个经典陷阱值得知道。alg: none 值声明一个未签名的令牌;一个不明确拒绝它的验证者,可能被诱使去信任伪造的 claims。而在算法混淆攻击中,攻击者取一个 RS256 令牌,把头部切换为 HS256,并用公钥作为 HMAC 秘密对其签名,这对那些从令牌中取算法、而非将其固定的验证者会得手。两者的防御相同:由验证者决定哪种算法可接受,而非由令牌决定。

解码不是验证

这是两种不同的操作,混淆二者是常见的 bug 之源。解码只是把各段从 Base64URL 解码,以揭示头部与 claims:无须密钥、无须信任,任何人都能做。验证用正确的密钥核对签名,并确认时间 claims。只应依据一个已验证令牌的 claims 行事。

时间也要紧。exp(过期)与 nbf(不早于)是界定令牌有效期窗口的 Unix 时间戳,而 iat 记录它何时被签发。一个正确的验证者会拒绝一个已过期或尚未生效的令牌,通常允许一个小的时钟偏移容差。

JWT 不是什么

一个 JWT 是经过签名,而非加密的(加密是一个单独的标准,JWE)。它证明是谁签发了这些 claims、以及它们未被篡改;它并不隐藏它们。

一个已签名的 JWT 也靠它自身不易被吊销。 因为 claims 活在令牌之内、验证无需服务器查找,一个令牌会一直有效直到过期,即便你想更早将其取消。务实的答案是短的存活期,外加一个单独的机制(一个刷新令牌、一个吊销列表,或令牌内省),当需要即时吊销时使用。

JWT 工具 解码一个令牌的头部与 claims、以平实的语言读出它的过期与时间,并针对你粘贴的一个秘密验证一个 HS256、HS384 或 HS512 签名,全部在你的浏览器中。令牌与秘密从不被发送到任何地方。