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 签名,全部在你的浏览器中。令牌与秘密从不被发送到任何地方。