Skip to content

基于 JWT 的鉴权机制是如何辨别用户携带的令牌是否被篡改的

1. 引子

我现在正在参照某教程开发一项模拟银行业务的后端项目,其中就需要认证请求发送者的身份,实现让用户只能访问到自己的账户而不会访问到别人的账户的功能。 JSON Web Token(缩写 JWT)是一种身份认证方案,也是目前最流行的跨域认证解决方案,其本质就是一个字符串书写规范,作用是用来在用户和服务器之间传递安全可靠的信息。

2. JWT是什么

根据维基百科的定义,JSON WEB Token(JWT,读作 [/dʒɒt/]),是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)。 在目前前后端分离的开发过程中,基于 token 认证方案是最常见的身份验证方案,流程如下

1.假设用户要登录一个应用,用户就需要输入用户名和密码,然后发送给运行着这个应用的服务器。 2.服务器验证过用户发来的用户名和密码后,就会生成一个包含了用户登录状态相关信息的 Token。 3.服务端返回 Token 给用户,Token 一般会被存储在用户浏览器的内存里 4.以后用户发送请求的时候只要在HTTP头部加上这个Token,服务器收到请求后从 HTTP 头部拿出Token后,对Token进行核实,服务器只需从 Token 中取出用户登录状态的信息即可确认用户身份。

3. JWT的数据结构

Payload 部分

服务器给用户签发的 JWT 中关于用户登录状态的信息放在 Payload 这一部分当中,例如:

{
  "用户名": "张三",
  "角色": "卖家",
  "到期时间": "2018年7月1日0点0分"
}

Header 部分

而 Header 部分则记录了该 JWT 的元数据,通常是下面这个洋子的

{
  "alg": "HS256",
  "typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

最后,将上面的两个 JSON 对象使用 Base64URL 算法转成字符串。就组成了JWT 三个部分其中的 Header 部分和 Payload 部分。

Signature 部分

Signature 部分是辨识请求中携带的令牌是不是由服务器签发的判断起着非常关键的作用。
现在,我们的服务器接收到用户的登录请求后已经做好了 JWT 令牌的 Header 部分和 Payload 部分。在签发前的最后一步,服务器将用一个只有服务器才知道的密钥(假设为字符串 “secret”)然后,使用 Header 里面指定的算法(默认是 HMAC SHA256),按照下面的公式产生Signature。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
= SHA256(opad ⊕ secret || SHA256(ipad ⊕ secret || base64UrlEncode(header) + "." + base64UrlEncode(payload)))

算出 Signature 以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

4. 那么在这种鉴权机制中,服务器是怎么知道请求中的JWT是否是伪造的呢?

在解说识别的过程之前,我们要先了解一下什么是散列函数。散列函数的特性是我们把不相同的数据输入散列函数后,都会得到唯一与之对于的散列值。输入的数据有一点点不同,散列函数得出散列值就有天壤之别,并且无法根据散列值还原出输入的原始数据是什么(输入的数据就好比是人,散列值好比是指纹)。用 HMACSHA256 算法生成 JWT 令牌的 Signature 部分的过程可以概括为: 首先把密钥拿去和内部填充常数 ipad 进行异或运算后的结果拼接上前面的 Header 和 Payload, 再将由这三者拼接成的数据放到 SHA256 散列函数中去生成专属它的散列值。

假设在这个时候,某个骇客想冒充张三的账户访问项目中上架商品的接口。此时他不知道张三的账号密码但是通过某种办法得到了用户令牌中 Header 和 Payload 的结构,还刚好正确地推算出了张三令牌的过期时间,于是构造出了一个和上文中一模一样的 Header 和 Payload。但是他还没有服务器签名的密钥,于是自己随便用了一个字符串(假设它是 “114514”)作为密钥放进前面提到的 HMACSHA256() 当中去生成散列值作为 Signature,暂且称为 HMACSHA256(header + “.” + payload, “114514”). 再将这个瞎造 Signature 和 Header 以及 Payload的组成一个内容为 header + “.” + payload + “.” + HMACSHA256(header + “.” + payload, “114514”) 的 JWT , 暂且称为 Cracker’sJWT 。

另一方面,服务器在收到 Cracker’sJWT 后,会用自己的密钥 “secret” 和Cracker’sJWT 中的 Header, Payload 一起拿去给 HMACSHA256 制成散列值 HMACSHA256(header + “.” + payload, “secret”). 那么此时仅仅因为两者中密钥参数的不同,得到散列值就有非常大的差别。服务器一眼就能鉴别出 Cracker’sJWT 并非自己给用户签发的令牌了。

5. 总结

要被散列的原始数据和散列值之间的单射关系以及无法从散列值还原出原始数据这两点,是保证签名真实有效的技术基础。在这里我还要特别感谢绚香猫和绚香音昨天和我讲解 JWT 的运作原理,真是辛苦她们了。

贡献者

The avatar of contributor named as 朵琳 朵琳

页面历史

撰写