JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.
JWT 是一种数据格式,它最终形态是几段 base64 编码过的字符串拼接而成,并且带了数字签名,可以被生成的一端验证数据完整性。
JWT 有两种形式,JWS(JSON Web Signature)和 JWE(JSON Web Encryption)。一般的应用场景是 JWS,也称 signed JWT,只保证数据完整性,不保证安全性。而 JWE 可以同时保证完整性和安全性。但是 JWE 相对复杂且应用场景少,在这里不做描述。下面的内容都是基于 JWS。
JWT 一般用于 授权(authorization)及 信息交换(information exchange)。
快速上手
对 JWT 的官方理解,看 RFC7519 及 JWT Introduction。由于这里通过一个授权的例子快速理解:
对于这样一个场景,第三步返回的 JWT 可以是这样子的(使用未 base64 编码过的内容及额外空行使其更好理解):
{ "alg": "HS256", "typ": "JWT" }
.
{ "sub": "4938475", "exp": 1577836800, "session_id": "394d1e840f" }
.
eyJ0eXAiOiJKV1QiLA0K
这三部分分别是 JOSE Header、JWS Payload 及 JWS Signature。服务端保存密钥(secret),用它生成 signature。这也是客户端将 JWT 回传时服务端可以验证的基础:
signature = HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Header 很好理解。Payload 中关键点是其中的字段。规范中定义了三类字段:
- Registered Claim Names:已被注册的字段;这部分是最关键的,可以实现一些会话管理相关的逻辑
- Public Claim Names:看不太懂,似乎没引入什么新字段?
- Private Claim Names:用户自己定义的字段。除了不要跟 registered 和 public 的名字冲突外,对命名规则没有要求。Auth0 则 要求 用户加上 URL 格式的 namespace,如
https://www.mysite.com/
;加完后的 claim name 类似https://www.mysite.com/favorite_color
上面的例子中,sub
和 exp
是 registered 的,有限定格式和用途;session_id
是 privated,用户自己定义的。
参考:
Registered Claim Names
Auth0 的 文档 描述了这些字段的含义,我在此基础上加了示例:
字段名 | 含义 | 示例 |
---|---|---|
iss (issuer) | Issuer of the JWT | 如 API 的域名,如 "https://api.github.com/" |
sub (subject) | Subject of the JWT (the user) | 如标识一个用户,如用户 ID "3984719" |
aud (audience) | Recipient for which the JWT is intended | 如使用这个 JWT 的客户端,比如配合 OAuth 使用时的 client ID |
exp (expiration time) | Time after which the JWT expires | 1490922820 ,注意是数字 |
nbf (not before time) | Time before which the JWT must not be accepted for processing | 1490886820 |
iat (issued at time) | Time at which the JWT was issued; can be used to determine age of the JWT | 1490886820 |
jti (JWT ID) | Unique identifier; can be used to prevent the JWT from being replayed (allows a token to be used only once) | "39d8fa3e" ,在「安全」一节描述 |
Auth0 的 文档 描述了它如何使用 JWT 的(不重要的字段被省略,并加了注释):
{
"iss": "https://YOUR_DOMAIN/",
"sub": "auth0|USER-ID",
"aud": "YOUR_CLIENT_ID",
"exp": 1490922820,
"iat": 1490886820
}
个人觉得 sub
exp
比较重要,其他似乎意义不大。可以结合具体框架再做分析。
安全
- 签名用的 key 一定要保证安全。不然黑客可以随意生成可以通过验证的 token
- 不要在 token 中放敏感信息。JWT 只保证数据的完整、不被篡改,但是并不加密数据
- 给 token 一个过期时间。如果没有过期时间,这个 token 就永久有效,可能会受到 重放攻击。JWT 亦提供了一个 jti 声明,用来给每个 token 标记一个单独的 ID,这样可以单独或者配合
aud
做黑名单,来收回对某些被黑客利用的 token 的权限 - 使用 HTTPS;不要明文传输 token