Hi,这里是桑小榆。上篇文章中,我们一起探讨了 OAuth 协议的原理以及授权认证流程,本次我们一起探讨 jwt 令牌作为授权协议的传输介质。
OAuth协议规范了几个参与角色的授权标准,安全可控的授予第三方应用,第三方应用获取到用户授予的权限之后,与资源服务器进行交互。那么在进行交互的时候,必然需要一种传输介质,且需要携带用户身份的信息,使得服务器之间能够识别并认证。这个传输的介质就是我们此次探讨的 jwt。
jwt
,全称 Json Web Token
,也就是日常说的 token 令牌。它是通过数字签名的方式,以 json 对象为载体,在不同服务终端之间安全传输信息。
我们可以看到jwt的定义,它具有和 json 一样的特性,非常轻量的传输方式,且易于人阅读和编写,利于机器的解析和生成。
它像我们的居民身份证一样,一旦相关权威部门识别你是合法公民且会颁发一个适用于本土的证件,通过这个证件可以任意出入需要出示证件的地方。那么jwt也是一样,在服务器认证你的合法用户身份之后,会生成一个json对象,发送给用户,例如:
{
"姓名": "桑小榆",
"角色": "管理员",
"到期时间": "2022年10月1日 10点10分"
}
复制代码
之后,用户每需要与服务器通讯的时候,携带具有身份信息的json。服务器完全只需要验证用户携带的身份信息。
当然,用户信息不会以明文的方式携带,不然很容易被不法分子截获进行篡改,那这个令牌就变得无意义了。但凡颁发任何具备流通的信息,都需要具备防伪标识。例如身份证,系统架构师资格证,人民币等都具备复杂的防伪信息。
那 jwt 在生成的时候,通常是加上签名来防止篡改的。形成一个标准的 jwt 格式如下:
▲图/ jwt解析组成部分
我们可以看到左边生生签名之后的 jwt 格式是很长得一串字符组成,中间由(.)隔开分成三部分。
我们看到右边解析之后,是jwt的组成格式。
Header 头部
Payload 载荷
Signature 签名
以上组成jwt格式为:Header.Payload.Signature
复制代码
那么我们将依次探讨这三部分。
Header头部
header部分是一个json对象,描述jwt的一些元数据,也就是属性信息。通常格式如下:
{
"alg": "HS256",
"typ": "JWT"
复制代码
上面的代码中,alg
属性表示采用签名的算法(algorithm
),默认是 hmac sha256
,写成 hs256
。这里通常有几种比较常用的签名算法rs256,hs256,base64
。
rs256(带有sha-256的 rsa 签名)是一种非对称算法,它使用公钥/私钥对
:身份提供者拥有用于生成签名的私钥(秘密)密钥,而 jwt 的消费者获得公钥验证签名。由于与私钥相反,公钥不需要保持安全,因此大多数身份提供者都可以让消费者轻松获取和使用(通常通过元数据 url)。
hs256(带有 sha-256 的hmac)涉及散列函数和一个密钥的组合,该密钥在两方之间共享,用于生成用作签名的散列
。由于生成签名和验证签名都使用相同的密钥,因此必须注意确保密钥不被泄露。
typ
属性表示这个令牌(token)的类型(type),jwt 令牌统一写为jwt
。
最后,将上面的 json 对象使用 base64url
算法转成字符串。
Payload 载荷
payload
部分也是一个 json 对象,用来存放实际需要传递的数据,通常包含一些签发的信息。jwt 规定了7个官方字段,供选用。
iss (issuer): jwt签发者.
sub (subject): jwt所面向的用户.
aud (audience): 接收jwt的一方.
exp (expiration time): jwt的过期时间,这个过期时间必须要大于签发时间.
nbf (Not Before): 定义在什么时间之前,该jwt都是不可用的.
iat (Issued At): jwt的签发时间.
jti (JWT ID): jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击.
复制代码
当然除了官方定义的七种之外,还可以自己定义一些私有字段。例如上图 jwt 格式中的 payload 格式,就是是自定义了一些私有字段。
{
"sub": "1234567890",
"name": "桑小榆呀",
"iat": 1516239022
复制代码
注意,jwt 默认是不加密的,任何人都可以读到,所以不要把私密信息放在这个部分。所以,payload 部分要使用 base64url 算法转成字符串。
Signature 签名
signature
部分是对前header和payload的签名,防止数据篡改。
首先,需要指定一个256位的密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 header 里面指定的签名算法(默认是 hmac sha256
),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
复制代码
算出签名以后,把 header、payload、signature
三个部分拼成一个字符串,每个部分之间用点(.
)分隔,就可以返回给用户。
Base64Url 算法
我们在上面的文章中也多次提到,header和payload部分需要使用base64url算法。base64url算法跟base64算法基本类似,但也有一些不同。
jwt作为一个token令牌,有些使用场景可能会用到url,例如 https://jwt.io/?token=xxx
。加密流程首先会对url的明文进行加密,其次在base64加密的基础上,会对url里面特殊字符进行处理:=
被省略、+
替换成-
,/
替换成_
。这就是base64url加密算法。
JWT 使用
我们探讨完了 jwt 的组成之后,回归到使用上。在身份验证中,客户端使用身份凭据成功登录时,将返回一个 Token 令牌。由于令牌是凭据,因此必须非常小心以防止出现安全问题。通常情况下不应该将令牌保留超过所需的时间。
虽然我们可以将Token存储在Cookie和localStorage当中,进行自动发送。但会缺乏安全性,和跨域的问题。更好的做法是在authorization
标头中使用bearer
模式。标头的内容应如下所示:
Authorization: Bearer <token>
复制代码
如果令牌在authorization
标头中发送,则跨域资源共享 (CORS
) 不会成为问题,因为它不使用 cookie。配合https传输也能大大提高了安全性。
▲图/ jwt简易授权流程
JWT 特点
最后我们总结 jwt 的几个特点:
-
jwt 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
-
jwt 不加密的情况下,不能将秘密数据写入 jwt 。
-
jwt 不仅可以用于认证,也可以用于交换信息。有效使用 jwt ,可以降低服务器查询数据库的次数。
-
jwt 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 jwt 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
-
jwt 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,jwt 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
-
为了减少盗用,jwt 不应该使用 http 协议明码传输,要使用 https 协议传输。
我们一起探讨完了 jwt 相关知识。下篇我们将以代码实操的方式来演示 jwt 的生成以及使用,并且配合 oidc
一套标准的授权流程。