这篇文章将介绍 JWT(JSON Web Token)的基础知识,并通过一个实际例子加深对 JWT 的理解。
什么是 JWT
JSON Web 令牌(JSON Web Token)是一种开放标准(RFC7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。此信息是经过数字签名的,因此可以验证和信任。可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对对 JWT 进行签名。
尽管 JWT 可以加密以在各方之间提供机密性,但重点还是 签名令牌
。签名令牌可以验证其中 包含的声明
的完整性,而加密令牌则对他人隐藏这些声明。当使用一对公钥/私钥来对令牌进行签名时,还能证明签名的确来自于 持有私钥的一方
。
JWT 的使用场景
以下是 JWT 有用的一些场景:
-
认证:这是使用 JWT 的最常见场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌所允许的路由、服务和资源。单点登录是一种广泛使用了 JWT 的功能,因为它的开销小,并且能够轻松地跨不同域使用
-
信息交换:JWT 是在各方之间安全地传输信息的好方法。由于 JWT 可以被签名(例如使用公钥/私钥对),因此可以确保发送者的确是他们所声称的身份。此外,由于签名是基于 header 和 payload 计算的,因此还可以验证内容是否未被篡改
JWT 结构
在其紧凑形式中,JWT 由三个部分组成,由点 .
分隔,它们是:
- Header
- Payload
- Signature
因此,JWT 通常如下所示。
1 | xxxxx.yyyyy.zzzzz |
Header(头部)
Header 通常由两部分组成:令牌的类型(JWT)和正在使用的签名算法,例如 HMAC SHA256 或 RSA。
1 | { |
然后,该 JSON 经过 Base64Url 编码以构成 JWT 的第一部分。
Payload(有效载荷)
JWT 的第二部分是 payload(有效载荷),它包含了 声明
(claims),声明
可以认为是关于实体(通常是用户)及其额外数据的说明。有 3 种 声明
:已注册声明
(registered)、公共声明
(public)、私有声明
(private)。
已注册声明
:一组预定义的声明,不是强制性的,但建议使用,以提供一组有用的、可互操作的声明。其中一些是:iss(颁发者)、exp(过期时间)、sub(主题)、aud(受众 )等公共声明
:这些声明可以由使用 JWT 的用户随意定义。但为避免冲突,应在 IANA JSON Web 令牌注册表 中定义它们,或将其定义为包含抗冲突命名空间
的 URI私有声明
:这些是自定义声明,用于在同意使用它们的各方之间共享信息,既不是注册声明,也不是公开声明
例如一个有效的负载可以是
1 | { |
然后对 Payload 进行 Base64URL 编码,以形成 JWT 的第二部分。
需要注意,对于签名令牌,此信息虽然可以防止篡改,但任何人都可以读取。除非 JWT 已加密,否则不要将机密信息放在 JWT 的 payload 或 header 元素中。
Signature(签名)
要创建签名部分,需要使用 已编码的 header
、已编码的 payload
、秘钥
,并按照 header
中所指定的算法,对它进行签名。例如如果使用 HMAC SHA256 算法,将按照如下方式计算签名:
1 | HMACSHA256( |
签名用于验证消息在整个过程中没有被更改,并且在使用私钥签名的令牌的情况下,它还可以验证 JWT 的发送者是否是它所声称的身份。
汇总
最终得到的 JWT 是由 .
分隔的三个 BASE64URL
编码的字符串,可以在 HTML 和 HTTP 环境中轻松传递。如下显示了一个 JWT:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.he0ErCNloe4J7Id0Ry2SEDg09lKkZkfsRiGsdX_vgEg |
可以在 jwt.io Debugger 中解码、验证和生成 JWT。
JWT 如何工作
在身份验证中,当用户使用其凭证成功登录时,将返回 JSON Web 令牌。由于令牌是凭据,因此必须非常小心以防止出现安全问题。通常,令牌的保留时间不应超过所需时间。由于缺乏安全性,也不应将敏感会话数据存储在浏览器存储中。
每当用户想要访问受保护的路由或资源时,用户都应该发送 JWT,通常保存在 Authorization
头部(使用 Bearer 验证类型)。
1 | Authorization: Bearer <token> |
在某些情况下,它是一种在无状态授权机制。服务器在 Authorization
头部中检查是否存在有效的 JWT,如果存在,则允许用户访问受保护的资源。如果 JWT 包含必要的数据,那么服务器上某些操作所需要的数据库查询也可以减少。
需要注意,如果在 HTTP header 中发送 JWT 令牌,需要避免 JWT 太大,因为某些服务器可能不接受超过 8KB 的标头。如果需要在 JWT 令牌中嵌入太多信息,例如包含所有用户的权限,那么可能需要替代方案,例如 Auth0 Fine-Grained Authorization。
如果令牌是在 Authorization header 中发送,则跨域资源共享(CORS)不会有问题,因为它不是使用 Cookie。
如下展示了如何获取 JWT 并访问 API 或资源:
- 应用程序或客户端向授权服务器请求授权,这是通过一条独立的认证流来实现的。例如,典型的符合 OpenID Connect 的 Web 应用程序会使用 授权代码流
- 授予授权后,授权服务器将向应用程序返回访问令牌
- 应用程序使用访问令牌访问受保护的资源(如 API)
请注意,使用签名令牌时,令牌中包含的所有信息都会公开给用户(虽然他们无法更改它)。这意味着不应将机密信息放在令牌中。
为什么使用 JWT 令牌
相比于 简单 Web 令牌
(Simple Web Tokens,SWT)和 安全断言标记语言令牌
(Security Assertion Markup Language Tokens,SAML),JWT 具有以下优点:
- 由于 JSON 不如 XML 详细,因此在编码时,其大小也更小。因此 JWT 相比于 SAML 更加紧凑,这使得 JWT 成为在 HTML 和 HTTP 环境中传递的好选择
- 在安全性方面,SWT 只能由使用 HMAC 算法的共享密钥进行签名,而 JWT 和 SAML 令牌可以使用 X.509 证书形式的公钥/私钥对进行签名。相比于 JSON 签名的简单性,对 XML 进行签名而不引入安全漏洞是比较困难的一件事
- JSON 的解析器在多数编程语言中很常见,这使得使用 JWT 更加容易(相比于 SAML)
JWT 和 OAuth 2.0
需要注意,JWT 并不等价于 OAuth。JWT 令牌只是一个签名的 JSON 对象。尽管 JWT 是 OAuth2 身份验证中最常用的 bearer token
,但其实 JWT
可以用于任何可用的地方。简单来说:
- OAuth 是一种协议,允许身份提供商与用户登录的服务分开。例如,每当使用 Facebook 登录其他服务(Yelp、Spotify 等)时,你都在使用 OAuth
- OAuth 定义了几种用于传递身份验证数据的选项,其中最流行的方法就是
bearer token
。bearer token
只是一个字符串,只有经过身份验证的用户才能持有,因此携带该 token 就能证明你的身份。而 JWT 就是一个好的bearer token
- 由于
bearer token
用于身份验证,因此必须对其进行保密。这也是为什么要在 TLS 通信中才使用bearer token
JWT 示例
最后我们来看一个 JWT 的实例,以加深对 JWT 的理解。这个例子来自于 golang-jwt 的一个例子。
1 | package main |
如下展示该程序的用法:
1 | # echo {\"foo\":\"bar\"} | ./jwt -key ../../test/sample_key -alg RS256 -sign - |
由于前两段只是 base64URL 编码的字符串,我们可以直接解码他们。或者直接使用该程序的 -show
选项来展示:
1 | # echo {\"foo\":\"bar\"} | ./jwt -key ../../test/sample_key -alg RS256 -sign - | ./jwt -show - |
使用 -veritfy
选项来验证签名:
1 | # echo {\"foo\":\"bar\"} | ./jwt -key ../../test/sample_key -alg RS256 -sign - | ./jwt -key ../../test/sample_key.pub -alg RS256 -verify - |