JWT(JSON Web Token)

简介: JWT 介绍 (https://jwt.io/)JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑和自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。

JWT 介绍 (https://jwt.io/)

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑和自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。作为标准,它没有提供技术实现,但是大部分的语言平台都有按照它规定的内容提供了自己的技术实现,所以实际在用的时候,只要根据自己当前项目的技术平台,到官网上选用合适的实现库即可。

使用JWT来传输数据,实际上传输的是一个字符串,这个字符串就是所谓的 json web token 字符串。所以广义上,JWT是一个标准的名称;狭义上,JWT指的就是用来传递的那个token字符串。这个串有两个特点:

  1. 紧凑:指的是这个串很小,能通过 url 参数,http 请求提交的数据以及 http header 的方式来传递;
  2. 自包含:这个串可以包含很多信息,比如用户的 id、角色等,别人拿到这个串,就能拿到这些关键的业务信息,从而避免再通过数据库查询等方式才能得到它们。

通常一个JWT是长这个样子的:

img_55daa2c29b97c0cab551235835ad16cb.png

要知道一个JWT是怎么产生以及如何用于会话管理,只要弄清楚JWT的数据结构以及它签发和验证的过程即可。

一. JWT的数据结构以及签发过程

一个JWT实际上是由三个部分组成:header(头部)payload(载荷)signature(签名)。这三个部分在JWT里面分别对应英文句号分隔出来的三个串:

img_c3a57f051202e79a35259ce79b0e398f.png

先来看header部分的结构以及它的生成方法。header部分是由下面格式的 json 结构生成出来:

img_7f97931bb091358bdf1e96250c8ae378.png

这个 json 中的typ属性,用来标识整个token字符串是一个JWT字符串;它的alg属性,用来说明这个JWT签发的时候所使用的签名和摘要算法,常用的值以及对应的算法如下:

img_cb52a1d202fcb685914005d6f1b30174.png

typ跟alg属性的全称其实是type跟algorithm,分别是类型跟算法的意思。之所以都用三个字母来表示,也是基于JWT最终字串大小的考虑,同时也是跟JWT这个名称保持一致,这样就都是三个字符了…typ跟alg是JWT中标准中规定的属性名称,虽然在签发JWT的时候,也可以把这两个名称换掉,但是如果随意更换了这个名称,就有可能在JWT验证的时候碰到问题,因为拿到JWT的人,默认会根据typ和alg去拿JWT中的header信息,当你改了名称之后,显然别人是拿不到header信息的,他又不知道你把这两个名字换成了什么。JWT作为标准的意义在于统一各方对同一个事情的处理方式,各个使用方都按它约定好的格式和方法来签发和验证token,这样即使运行的平台不一样,也能够保证token进行正确的传递。

一般签发JWT的时候,header对应的 json 结构只需要typ和alg属性就够了。JWT的header部分是把前面的 json 结构,经过 Base64Url 编码之后生成出来的:


img_66362649749870c5a032c1cc46ba55a5.png

(在线 base64 编码:http://www1.tc711.com/tool/BASE64.htm)

再来看payload部分的结构和生成过程。payload部分是由下面类似格式的 json 结构生成出来:

img_e4a7cabe2a2d74a05f08360e135d60e9.png

(在线 base64 编码:http://www1.tc711.com/tool/BASE64.htm)

再来看payload部分的结构和生成过程。payload部分是由下面类似格式的 json 结构生成出来:

img_e7cf02adf06158bf84ca9c25f69e1650.png

(在线 base64 编码: http://www1.tc711.com/tool/BASE64.htm)

最后看signature部分的生成过程。签名是把headerpayload对应的 json 结构进行 base64url 编码之后得到的两个串用英文句点号拼接起来,然后根据header里面alg指定的签名算法生成出来的。算法不同,签名结果不同,但是不同的算法最终要解决的问题是一样的。以alg: HS256为例来说明前面的签名如何来得到。按照前面alg可用值的说明,HS256 其实包含的是两种算法:HMAC 算法和 SHA256 算法,前者用于生成摘要,后者用于对摘要进行数字签名。这两个算法也可以用 HMACSHA256 来统称。运用 HMACSHA256 实现signature的算法是:

img_63fe523b96fd0a13a026e8a8484003d3.png

正好找到一个在线工具能够测试这个签名算法的结果,比如我们拿前面的header和payload串来测试,并把“secret”这个字符串就当成密钥来测试:

img_46bd13efc7d41205fbb0313b4bcc0cd4.png

最后的结果 B 其实就是 JWT 需要的 signature。不过对比我在介绍 JWT 的开始部分给出的 JWT 的举例:


img_d2cb487ca949216f10fe10d28ac4bfbb.png

会发现通过在线工具生成的headerpayload都与这个举例中的对应部分相同,但是通过在线工具生成的signature与上面图中的signature有细微区别,在于最后是否有“=”字符。这个区别产生的原因在于上图中的JWT是通过JWT的实现库签发的JWT,这些实现库最后编码的时候都用的是 base64url 编码,而前面那些在线工具都是 bas64 编码,这两种编码方式不完全相同,导致编码结果有区别。

以上就是一个JWT包含的全部内容以及它的签发过程。接下来看看该如何去验证一个JWT是否为一个有效的JWT

二.JWT的验证过程

这个部分介绍JWT的验证规则,主要包括签名验证和payload里面各个标准claim的验证逻辑介绍。只有验证成功的JWT,才能当做有效的凭证来使用。

先说签名验证。当接收方接收到一个JWT的时候,首先要对这个JWT的完整性进行验证,这个就是签名认证。它验证的方法其实很简单,只要把header做 base64url 解码,就能知道JWT用的什么算法做的签名,然后用这个算法,再次用同样的逻辑对headerpayload做一次签名,并比较这个签名是否与JWT本身包含的第三个部分的串是否完全相同,只要不同,就可以认为这个JWT是一个被篡改过的串,自然就属于验证失败了。接收方生成签名的时候必须使用跟JWT发送方相同的密钥,意味着要做好密钥的安全传递或共享。

再来看payloadclaim验证,拿前面标准的claim来一一说明:

iss(Issuser):如果签发的时候这个claim的值是“a.com”,验证的时候如果这个claim的值不是“a.com”就属于验证失败;
sub(Subject):如果签发的时候这个claim的值是“liuyunzhuge”,验证的时候如果这个claim的值不是“liuyunzhuge”就属于验证失败;
(Audience):如果签发的时候这个claim的值是“[‘b.com’,’c.com’]”,验证的时候这个claim的值至少要包含 b.com,c.com 的其中一个才能验证通过;
exp(Expiration time):如果验证的时候超过了这个claim指定的时间,就属于验证失败;
nbf(Not Before):如果验证的时候小于这个claim指定的时间,就属于验证失败;
iat(Issued at):它可以用来做一些 maxAge 之类的验证,假如验证时间与这个claim指定的时间相差的时间大于通过 maxAge 指定的一个值,就属于验证失败;
jti(JWT ID):如果签发的时候这个claim的值是“1”,验证的时候如果这个claim的值不是“1”就属于验证失败;
需要注意的是,在验证一个JWT的时候,签名认证是每个实现库都会自动做的,但是payload的认证是由使用者来决定的。因为JWT里面可能不会包含任何一个标准的claim,所以它不会自动去验证这些claim

以登录认证来说,在签发JWT的时候,完全可以只用subexp两个claim,用sub存储用户的id,用exp存储它本次登录之后的过期时间,然后在验证的时候仅验证exp这个claim,以实现会话的有效期管理。

JWT SSO

场景一:用户发起对业务系统的第一次访问,假设他第一次访问的是系统 A 的 some/page 这个页面,它最终成功访问到这个页面的过程是:


img_6c19b594beda63d0bbce2fb37d07f81d.png

在这个过程里面,我认为理解的关键点在于:

它用到了两个cookie(jwt和sid)和三次重定向来完成会话的创建和会话的传递;

jwt的cookie是写在 systemA.com 这个域下的,所以每次重定向到 systemA.com 的时候,jwt这个cookie只要有就会带过去;

sid的cookie是写在 cas.com 这个域下的,所以每次重定向到 cas.com 的时候,sid这个cookie只要有就会带过去;

在验证jwt的时候,如何知道当前用户已经创建了 sso 的会话?
因为jwt的payload里面存储了之前创建的 sso 会话的sessionid,所以当 cas 拿到jwt,就相当于拿到了sessionid,然后用这个sessionid去判断有没有的对应的session对象即可。

还要注意的是:CAS 服务里面的session属于服务端创建的对象,所以要考虑sessionid唯一性以及session共享(假如 CAS 采用集群部署的话)的问题。sessionid的唯一性可以通过用户名密码加随机数然后用 hash 算法如 md5 简单处理;session共享,可以用memcached或者redis这种专门的支持集群部署的缓存服务器管理session来处理。

由于服务端session具有生命周期的特点,到期需自动销毁,所以不要自己去写session的管理,免得引发其它问题,到 github 里找开源的缓存管理中间件来处理即可。存储session对象的时候,只要用sessionid作为 key,session对象本身作为value,存入缓存即可。session对象里面除了sessionid,还可以存放登录之后获取的用户信息等业务数据,方便业务系统调用的时候,从session里面返回会话数据。

场景二:用户登录之后,继续访问系统 A 的其它页面,如 some/page2,它的处理过程是:


img_440ee745db4c10d6de51e603ed794a03.png

从这一步可以看出,即使登录之后,也要每次跟 CAS 校验jwt的有效性以及会话的有效性,其实jwt的有效性也可以放在业务系统里面处理的,但是会话的有效性就必须到 CAS 那边才能完成了。当 CAS 拿到jwt里面的sessionid之后,就能到session缓存服务器里面去验证该sessionid对应的session对象是否存在,不存在,就说明会话已经销毁了(退出)。

场景三:用户登录了系统 A 之后,再去访问其他系统如系统 B 的资源,比如系统 B 的 some/page,它最终能访问到系统 B 的 some/page 的流程是:


img_29c8d8aad40c9edd5e2d340a91cebaea.png

这个过程的关键在于第一次重定向的时候,它会把sid这个cookie带回给 CAS 服务器,所以 CAS 服务器能够判断出会话是否已经建立,如果已经建立就跳过登录页的逻辑。

场景四:用户继续访问系统 B 的其它资源,如系统 B 的 some/page2:


img_4225597740b0c4c6cc10fa4bd2194fec.png

这个场景的逻辑跟场景二完全一致。

场景五:退出登录,假如它从系统 B 发起退出,最终的流程是:


img_a862c5949ebcd604ca602c76ba584177.png

最重要的是要清除sid的cookie,jwt的cookie可能业务系统都有创建,所以不可能在退出的时候还挨个去清除那些系统的cookie,只要sid一清除,那么即使那些jwt的cookie在下次访问的时候还会被传递到业务系统的服务端,由于jwt里面的sid已经无效,所以最后还是会被重定向到 CAS 登录页进行处理。

方案总结
以上方案两个关键的前提:

整个会话管理其实还是基于服务端的session来做的,只不过这个session只存在于 CAS 服务里面;
CAS 之所以信任业务系统的jwt,是因为这个jwt是 CAS 签发的,理论上只要认证通过,就可以认为这个jwt是合法的。
jwt本身是不可伪造,不可篡改的,但是不代表非法用户冒充正常用法发起请求,所以常规的几个安全策略在实际项目中都应该使用:

使用 https
使用 http-only 的cookie,针对sid和jwt
管理好密钥
防范 CSRF 攻击。
尤其是 CSRF 攻击形式,很多都是钻代码的漏洞发生的,所以一旦出现 CSRF 漏洞,并且被人利用,那么别人就能用获得的jwt,冒充正常用户访问所有业务系统,这个安全问题的后果还是很严重的。考虑到这一点,为了在即使有漏洞的情况将损害减至最小,可以在jwt里面加入一个系统标识,添加一个验证,只有传过来的jwt内的系统标识与发起jwt验证请求的服务一致的情况下,才允许验证通过。这样的话,一个非法用户拿到某个系统的jwt,就不能用来访问其它业务系统了。

在业务系统跟 CAS 发起 attach/validate 请求的时候,也可以在 CAS 端做些处理,因为这个请求,在一次 SSO 过程中,一个系统只应该发一次,所以只要之前已经给这个系统签发过 jwt 了,那么后续 同一系统的 attach/validate 请求都可以忽略掉。

总的来说,这个方案的好处有:

完全分布式,跨平台,CAS 以及业务系统均可采用不同的语言来开发;
业务系统如系统 A 和系统 B,可实现服务端无状态
假如是自己来实现,那么可以轻易的在 CAS 里面集成用户注册服务以及第三方登录服务,如微信登录等。
它的缺陷是:

第一次登录某个系统,需要三次重定向;
登录后的后续请求,每次都需要跟 CAS 进行会话验证,所以 CAS 的性能负载会比较大
登陆后的后续请求,每次都跟 CAS 交互,也会增加请求响应时间,影响用户体验。

目录
相关文章
|
21天前
|
JSON JavaScript 前端开发
❤Nodejs 第九章(token身份认证和express-jwt的安装认识)
【4月更文挑战第9天】Node.js第九章介绍了Token身份认证,特别是JWT(JSON Web Token)作为跨域认证的解决方案。JWT由Header、Payload和Signature三部分组成,用于在客户端和服务器间安全传输用户信息。前端收到JWT后存储在localStorage或sessionStorage中,并在请求头中发送。Express-JWT是一个中间件,用于解析JWT。基本用法包括设置secret和algorithms。注意安全问题,避免混合使用不同算法以防止降级攻击。
40 0
|
14天前
|
XML JSON 前端开发
【Web 前端】XML和JSON的区别?
【4月更文挑战第22天】【Web 前端】XML和JSON的区别?
【Web 前端】XML和JSON的区别?
|
15天前
|
JSON 安全 API
【专栏】四种REST API身份验证方法:基本认证、OAuth、JSON Web Token(JWT)和API密钥
【4月更文挑战第28天】本文探讨了四种REST API身份验证方法:基本认证、OAuth、JSON Web Token(JWT)和API密钥。基本认证简单但不安全;OAuth适用于授权第三方应用;JWT提供安全的身份验证信息传递;API密钥适合内部使用。选择方法时需平衡安全性、用户体验和开发复杂性。
|
1月前
|
XML JSON JavaScript
使用JSON和XML:数据交换格式在Java Web开发中的应用
【4月更文挑战第3天】本文比较了JSON和XML在Java Web开发中的应用。JSON是一种轻量级、易读的数据交换格式,适合快速解析和节省空间,常用于API和Web服务。XML则提供更强的灵活性和数据描述能力,适合复杂数据结构。Java有Jackson和Gson等库处理JSON,JAXB和DOM/SAX处理XML。选择格式需根据应用场景和需求。
|
1月前
|
JSON JavaScript 数据格式
jwt-auth插件实现了基于JWT(JSON Web Tokens)进行认证鉴权的功能
jwt-auth插件实现了基于JWT(JSON Web Tokens)进行认证鉴权的功能
39 1
|
1月前
|
JSON 前端开发 Java
Json格式数据解析
Json格式数据解析
|
2月前
|
存储 JSON Apache
揭秘 Variant 数据类型:灵活应对半结构化数据,JSON查询提速超 8 倍,存储空间节省 65%
在最新发布的阿里云数据库 SelectDB 的内核 Apache Doris 2.1 新版本中,我们引入了全新的数据类型 Variant,对半结构化数据分析能力进行了全面增强。无需提前在表结构中定义具体的列,彻底改变了 Doris 过去基于 String、JSONB 等行存类型的存储和查询方式。
揭秘 Variant 数据类型:灵活应对半结构化数据,JSON查询提速超 8 倍,存储空间节省 65%
|
4天前
|
XML JSON API
转Android上基于JSON的数据交互应用
转Android上基于JSON的数据交互应用
|
11天前
|
JSON JavaScript Java
从前端Vue到后端Spring Boot:接收JSON数据的正确姿势
从前端Vue到后端Spring Boot:接收JSON数据的正确姿势
22 0
|
13天前
|
JSON 数据格式 Python
Python标准库中包含了json模块,可以帮助你轻松处理JSON数据
【4月更文挑战第30天】Python的json模块简化了JSON数据与Python对象之间的转换。使用`json.dumps()`可将字典转为JSON字符串,如`{"name": "John", "age": 30, "city": "New York"}`,而`json.loads()`则能将JSON字符串转回字典。通过`json.load()`从文件读取JSON数据,`json.dump()`则用于将数据写入文件。
17 1