JSON Web Token

OAuth2在移动端实践是不安全的,有不少的产品(包括某些上亿用户量的App),也会出现将client_secret保存在客户端的情况。钥匙丢了、锁却不能换,对于项目而言无疑是很被动的。

因此需要在终端或者整体方案设计上,考虑规避这类问题。

Sign In With Apple在WWDC2019推出后,今年几乎是强制性的接入(发觉大小公司并没有什么抵抗?)。尽管晚了一点,但作为苹果生态的重要一环,终究是出场了。要知道,微信、微博成长为大平台,早期支持授权登录是非常重要的。

与「使用微信登录」、「使用微博登录」一样,Sign In With Apple其实也是基于OAuth2OpenID Connect的规范来实现授权登录的。那么面对千万级别以上的开发者、全球的用户量,苹果怎么从技术方案上保证数据安全呢?其中的一个方式便是使用JSON Web Token

JSON Web Token概念

JSON Web Token(简称JWT)是一个规范,规范性文件请看RFC-7519

看看IETF的对JWT的简介:

JSON Web Token (JWT) is a compact, URL-safe means of representing
claims to be transferred between two parties. The claims in a JWT
are encoded as a JSON object that is used as the payload of a JSON
Web Signature (JWS) structure or as the plaintext of a JSON Web
Encryption (JWE) structure, enabling the claims to be digitally
signed or integrity protected with a Message Authentication Code
(MAC) and/or encrypted.

claim是一种对所有权的宣示。JWT主要是通过对claims做签名,来保证这种所有权的有效性。这从下面介绍的JWT的结构我们可以知道。

JSON Web Token结构

看一个例子:

1
2
3
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiJ1MjAyMDAxMDEiLCJuYW1lIjoiSmFzb24gQ2hhbiIsImlhdCI6MTU3Nzg4MjUxMH0
.kB8ZprG8nGbNhR7AgcBoHOJaI1NucNMheN9IzoVdJK4

可以看到JWT令牌是一个由.点号分隔为3段的字符串(无分行),并且每段是Base64编码的。

Base64解码后可以看到:
第一部分是:(Header)

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

第二部分是:(Payload)

1
2
3
4
5
{
"sub": "u20200101",
"name": "Jason Chan",
"iat": 1577882510
}

第三部分是:(Signature)

1
fͅhZ#Snp!xH΅]$

事实上,一个JWT就是由三部分组成:头部、有效载荷、签名。格式是Header.Payload.Signature,三部分Base64编码后通过点号连接。

其中Header中指定了签名的算法,alg为算法的缩写,通常的比如RS256HS256。例子中是HS256,也就是HMAC SHA256)。typ为类型的缩写,可选的有[JWS] 与 [JWE]——这两个也是常用的JWT的两种应用。

Header也可以有额外的字段,比如对于非对称加密签名的算法,可能有kid字段(也就是秘钥id)等。

Payload是包含声明的有效载荷。声明是关于实体(比如用户)与其他元数据。

声明可以分为三大类,Registered Claims、Public Claims和Private Claims。其中Registered Claims是标准规定的有特定的含义的声明字段,也是最常用到的,规范里的声明字段有:

1
2
3
4
5
6
7
"iss" (Issuer) Claim
"sub" (Subject) Claim
"aud" (Audience) Claim
"exp" (Expiration Time) Claim
"nbf" (Not Before) Claim
"iat" (Issued At) Claim
"jti" (JWT ID) Claim

一般sub主题是主要的声明。

Public Claims则可以理解为保留字段,比如OpenID的工作组就定义了一组字段,可以参考IANA JWT。对于实际应用上,如果不考虑外部交互,拓展一般用私有声明字段即可。

也可以有自定义的声明字段。

Signature签名部分,根据header中指定的算法进行签名。比如例子中的HS256,是这样的:

1
2
3
HMACSHA256(
base64Encode(header) + "." + base64Encode(payload),
secret)

签名算法一般是HS256RS256ES256HS256HMAC SHA256的摘要散列算法,RS256ES256则是使用非对称密钥进行签名校验。

JWS与JWE

JWT是规范,而JWSJWE就是两个实现了。JWS主要明确签名的细节,关心的是数据防篡改,但数据是可能被窥探的;JWE则关心数据的私密安全性,主要明确数据加解密的细节。

JWS支持的签名算法前面提到的HMAC SHARSA SHAECDSA SHA系列,每个系列根据密钥长度有不同安全强度。JWT也是可以不签名的(header中alg为none),不签名的JWT被称为不安全的JWT,签名的其实就是JWS了。JWS签名需要用到公钥或者共享密钥来验签,这个密钥被称为JSON Web Key,也就是JWK

为了支持公钥验签,JWS会有额外的header声明。比如若使用私有部署的证书来签名,则可以声明kid来提供jwk的索引。苹果也是这么干的,苹果提供了服务地址来下载它用来验签的公钥。苹果下发到前端的identityToken就是一个使用苹果的私钥签名的JWT,后面有机会再介绍苹果的授权流程。除了kidJWS还支持X509证书相关的字段,比如:

  • x5u: 指向X509公共证书的URL
  • x5c: X509证书链
  • x5t:X509证书的SHA-1指纹
  • x5t#S256: X509证书的SHA-256指纹

JWE的结构稍微有些差异,JWE令牌由五个关键组件构建,主要包括:JOSE头、JWE加密密钥、JWE初始化向量、JWE密文和JWE身份验证标签。

  • The protected header,类似于JWS的头部;
  • The encrypted key,用于加密密文和其他加密数据的对称密钥;
  • The initialization vector,初始IV值,有些加密方式需要额外的或者随机的数据;
  • The encrypted data,密文数据;
  • The authentication tag,由算法产生的附加数据,来防止密文被篡改

关注JWE的头部的一些参数字段:
alg:定义用于加密 Content Encryption Key(CEK)的算法,必须为RSA-OAEP
enc:定义用于加密载荷数据以及提供认证标签,必须为A128CBC-HS256
cty:定义载荷的Content Type,必须为“JWT”
kid:可选,密钥索引号
zip:可选,定义内容明文压缩算法

具体的加解密流程可以参考文档说明。

JSON Web Token的潜在风险

JWT的规范不是作为一个数据加密的用途的,比如JWS的头部及有效载荷实际是明文传输的,主要是验证数据的有效性,避免被篡改。因此会有些潜在的问题需要注意的:

  1. 敏感信息泄露
  2. JWT的重放风险
  3. 算法修改攻击
  4. 对称加密签名密钥破解

数据的传输阶段,有效载荷是明文的,所以不要传输敏感信息。而作为身份验证的一种手段,JWT存在伪造身份恶意攻击的风险。比如JWT的重放风险,虽然这个用其他的身份验证方式同样存在的。另一种问题是算法修改攻击,校验方应该限定Header中的sig,谨慎使用JWT库。如果JWT支持none算法,可能直接跳过签名的认证;或者攻击时将RS256修改为HS256,同样有签名验证的漏洞。可以参考这篇文章

本文参考:Introduction to JSON Web Tokens

Author: Jason

Permalink: http://blog.knpc21.com/ios/json-web-token/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。

Comments