引言
本文由我的同事 @like 投稿,介绍了 JWT(JSON Web Token)是什么,以及它的一些应用场景。
背景
前段时间看 Kubernetes 认证相关的内容,发现如果 Pod 要和 API Server 交互,需要为该 Pod 创建 ServiceAccount 并绑定相应的角色。比如 Dashboard 的配置文件如下:
1 | # ------------------- Dashboard Service Account ------------------- # |
这里创建了名称为 kubernetes-dashboard
的 ServiceAccount,并绑定到角色 kubernetes-dashboard-minimal
,最后在 PodSpec
的 serviceAccountName
字段引用了该 ServiceAccount。
究竟 ServiceAccount 中存放了哪些内容,Kubernetes 背后又做了哪些事情呢?
1 | kubectl -n kube-system get serviceaccount kubernetes-dashboard -o yaml |
1 | apiVersion: v1 |
与 ServiceAccount 关联的 Secret 中持有 API Server 的 CA 证书和签名的 JSON Web Token(JWT)。
1 | kubectl -n kube-system get secret kubernetes-dashboard -o yaml |
1 | apiVersion: v1 |
这个签名的 JWT 可以用作 bearer token 来认证该 ServiceAccount,即在 HTTP 请求中添加头部 Authorization: Bearer <JWT>
。通常情况下,该 Secret 会被挂载到 Pod 下每个容器的 /var/run/secrets/kubernetes.io/serviceaccount
路径,用以 API 访问。
JWT
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. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
从JWT的定义看出其特点:
- 信息格式为 json
- 带签名,保证内容不被篡改
目前 JWT 的主要用途是认证,用户成功登录后,认证服务器生成 token 并返回。接下来客户端所有的请求都会携带该 JWT,应用服务器验证 token 的合法性,识别出用户身份。
JWT数据结构
JWT 包含三部分内容,相互之间用 ‘.’ 分隔
- Header
- Payload
- Signature
所以 JWT 字符串看起来如下:
1 | xxxxx.yyyyy.zzzzz |
Header
头部由两部分组成: token 类型,为固定值 JWT
;使用的签名算法,如 HMAC SHA256 或 RSA。
例子:
1 | { |
这个 JSON 对象经过 Base64Url 编码后形成 JWT 的第一部分。
Payload
JWT 的第二部分是 Payload,包含若干条声明(对用户的陈述),这些声明分为三种类型:
- Registered claims - 预先定义的,包括以下7个字段, 注意:这些声明的名称都是三字符长度,所以说 JWT 是紧凑的
- iss (Issuer): 签发人
- sub (Subject): 主题
- aud (Audience): 受众
- exp (Expiration Time): 过期时间
- nbf (Not Before): 生效时间
- iat (Issued At): 签发时间
- jti (JWT ID): 编号
- Public claims - 第三方扩展的,为避免冲突,使用前参考 IANA JSON Web Token Registry
- Private claims - 由通信双方自定义的
同样,Payload 部分也要使用 Base64Url 编码。
Signature
Signature 部分是对 Header 和 Payload 的签名,防止数据篡改。
首先,指定一个密钥,该密钥只有认证服务器知道,然后根据 Header 中指定的签名算法,生成签名。
例如你想使用 HMAC SHA256 算法,那么生成签名的公式如下:
1 | HMACSHA256( |
签名能保证数据在传输过程中不被篡改,如果生成签名时使用的是私钥,能同时验证 JWT 生成方的身份。
注意:签名只保证信息不被篡改,Header 和 Payload 对任何人都是可读的。所以不要放一些敏感信息,除非是加密的。
最后把三部分字符串用逗号连接起来,就得到最终的 JWT。
实例
回到开头的例子,我们拿到了与 ServiceAccount 关联的JWT,利用官方提供的 Debugger 进行解码得到
Header:
1 | { |
Payload:
1 | { |
可以看出,Kubernetes 使用 RS256
(RSA Signature With SHA-256)算法生成签名,默认使用的密钥是 API Server 的 TLS 私钥。这里采取 sub
字段的值作为当前的用户名, 进行后续鉴权。
注意: Kubernetes 的 Secret 中保存的数据是 base64 编码的,所以通过 kubectl 拿到的 token 需要先解码
另一个应用场景是 OpenID
,在 OAuth 2.0 上扩展的认证协议,不在这里详细阐述。这里以 Google OAuth Playground 为例进行说明,整体过程与 OAuth 2.0 类似: 发送认证请求到 google -> 用户授权返回 code -> 拿 code 换取 access_token。不过这里的 scope 不再是需要授权的资源路径,而是 openid email/profile
。认证 URL 可能如下:
1 | https://accounts.google.com/o/oauth2/v2/auth? |
最后返回的结果除了 access_token
,还包括 id_token
(由 Google 生成的标明用户身份的 JSON Web Token)。下面让我们看下里面有什么内容
Header:
1 | { |
Payload
1 | { |
可以看到该 JWT 包含了用户名、头像、语言等 profile 信息。
如果我们拿到这样的 JWT,怎么验证其是否是 Google 生成的?
Google把公钥放在 https://www.googleapis.com/oauth2/v3/certs 这个路径上
1 | { |
可以看到,其 kid
与前面 JWT 的 header 中的 kid
匹配,下面我们用这个 key 来验证签名的合法性。
1 | String e = <上面key的e字段>; |
执行上述代码后,会显示如下异常信息:
1 | JWT expired at 2019-12-21T12:18:01Z. Current time: 2019-12-22T09:22:02Z, a difference of 75841420 milliseconds. |
哈哈 过期啦😂
参考内容
[1] JWT Introduction
[2] JSON Web Token入门教程
[3] Kubernetes Authenticating
[4] Google OpenID Connect