前文《》介绍了目前应用最多的会话管理方案还是基于token的方案,不仅有很多实现,而且还有现成的标准可用,这个标准就是JSON Web Token(JWT)。这篇文章就来重点介绍一下JWT。
JWT是一个开放标准(RFC 7519),它定义了一种紧凑和自包含的方式,以JSON字符串的形式在各方之间安全地传输信息。JWT标准本身没有提供技术实现,但是大部分的编程语言都提供了自己的技术实现,所以实际在用的时候,只要根据自己需要,选用合适的实现库即可。
使用JWT传输的数据,实际上是一个字符串,这个字符串就是JWT字符串。JWT字符串有两个特点:
- 紧凑:JWT串字节数很少,能通过url 参数、http请求提交的数据或者http header的方式来传递;
- 自包含:JWT串可以包含很多信息,比如用户id、角色等,服务端拿到JWT串以后就能提取这些关键信息,从而避免再通过数据库查询获取。
JWT请求流程
JWT的请求流程如上图所示,主要包括了JWT字符串的生成和验证:
- 浏览器向服务器发送POST请求,带有用户名和密码
- 服务器验证后用密钥创建一个JWT字符串
- 服务器返回JWT给浏览器
- 浏览器再次请求,将JWT放在请求头中
- 服务器验证JWT签名,提取用户信息
- 服务器返回响应
一个JWT字符串由三个部分组成:header(头部)、payload(载荷)和signature(签名)。这三个部分在JWT字符串中用英文句号“.”分隔开来。
header部分是由下面格式的json结构生成出来:
{
"alg": "HS256",
"typ": "JWT"
}
这个json中的typ属性,用来标识字符串是一个JWT字符串;alg属性用来说明所使用的签名和摘要算法。一般签发JWT的时候,header对应的json结构只需要这两个属性就够了。JWT的header部分是把这个json结构,经过Base64Url编码之后生成出来的。
payload部分是用来承载要传递的数据,它的json结构实际上是对要传递的数据的一组声明,这些声明被JWT标准称为claims,每一个属性值对就是一个claim。
JWT标准里面定义的标准claim有:
- iss(Issuser):代表这个JWT的签发主体;
- sub(Subject):代表这个JWT的主体,即它的所有人;
- aud(Audience):代表这个JWT的接收对象;
- exp(Expiration time):是一个时间戳,代表这个JWT的过期时间;
- nbf(Not Before):是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的;
- iat(Issued at):是一个时间戳,代表这个JWT的签发时间;
- jti(JWT ID):是JWT的唯一标识。
当然我们可以按照自己的想法来自定义payload的结构:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
payload部分也是把这个json结构经过Base64Url编码之后生成出来。
signature是签名部分。签名过程是把 header 和 payload 对应的json结构分别进行Base64url编码之后得到的两个串,用英文句点号拼接起来,再根据 alg 字段指定的签名算法生成出来。算法不同,签名结果也会不同,但不同的算法最终要解决的问题是一样的。
以 alg: HS256 为例来说明签名如何生成。HS256其实包含的是两种算法:HMAC算法和SHA256算法,前者用于生成摘要,后者用于对摘要进行数字签名。这两个算法也可以用HMACSHA256来统称,运用HMACSHA256实现签名的算法如下:
HMACSHA256(
base64UrlEncode(header) "."
base64UrlEncode(payload),
secret)
最终的JWT字符串就是把header、payload和signature三个部分,用英文句号“.”连接组成。
不同颜色代表JWT不同部分
JWT的验证JWT的验证主要是对签名的验证,以及payload中的各个标准claim进行验证。只有验证成功的JWT,才能被当做有效的凭证来使用。
签名验证
当服务端接收到JWT的时候,首先要对JWT的完整性进行验证,也就是签名认证。验证的方法其实很简单:
- 首先把header做base64url解码,就能知道JWT用的什么算法做的签名;
- 然后用这个算法,再次用同样的逻辑对header和payload重做一次签名;
- 比较重做的签名与JWT本身包含的第三部分的串是否相同,如果不同,说明这个JWT可能被篡改过,属于验证失败。
接收方验证签名的时候必须使用生成签名相同的密钥。因此服务端必须要对密钥妥善安全保管。
标准claim验证
- iss(Issuser):验证这个claim的值与签发JWT时是否一致。
- sub(Subject):验证与签发时的claim值是否一致。
- aud(Audience):验证是否包括在签发时的claim中。比如签发时这个claim的值是“[‘b.com’,’c.com’]”,验证时claim的值至少要包含b.com、c.com的其中一个。
- exp(Expiration time):验证的时间是否超过这个claim指定的时间,超过则验证失败。
- nbf(Not Before):验证的时间是否小于这个claim指定的时间,小于则验证失败。
- iat(Issued at):验证的时间与这个claim指定的时间相差的时间是否大于指定值,如果大于则验证失败;
- jti(JWT ID):验证与签发时的claim值是否一致。
在签发JWT的时候,一般只用sub跟exp两个claim即可,用sub存储用户id,用exp存储过期时间。在claim验证的时候仅验证exp这个claim,以实现会话的有效期管理。
总结JWT有以下优势:
- 因为json的通用性,所以JWT可以跨语言使用,Java、Nodejs、Python等均可支持。
- 在payload部分,可以存储一些业务逻辑所需要的非敏感信息。
- JWT的构成非常简单,字节占用很小,非常便于传输。
- 不需要在服务端保存会话信息, 易于应用的扩展
使用JWT时需要注意的是:不要在JWT的payload部分存放敏感信息,因为该部分是可以解密的。密钥一定要保存好,尽量使用Https协议来进行Http请求。
参考资料:https://jwt.io/introduction/
我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法,请大家多多关注,欢迎留言和转发,希望与大家互动交流,谢谢。