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 交互,也会增加请求响应时间,影响用户体验。

目录
相关文章
|
3月前
|
XML JSON 前端开发
【Web前端揭秘】XML与JSON:数据界的双雄对决,你的选择将如何改写Web世界的未来?
【8月更文挑战第26天】本文深入探讨了XML和JSON这两种广泛使用的数据交换格式在Web前端开发中的应用。XML采用自定义标签描述数据结构,适用于复杂层次数据的表示,而JSON则以键值对形式呈现数据,更为轻量且易解析。通过对两种格式的示例代码、结构特点及应用场景的分析,本文旨在帮助读者更好地理解它们的差异,并根据实际需求选择最合适的数据交换格式。
60 1
|
1月前
|
存储 JSON 前端开发
JSON与现代Web开发:数据交互的最佳选择
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也便于机器解析和生成。它以文本格式存储数据,常用于Web应用中的数据传输,尤其是在客户端和服务器之间。
44 0
|
2月前
|
JSON 算法 安全
Web安全-JWT认证机制安全性浅析
Web安全-JWT认证机制安全性浅析
30 2
|
3月前
|
Java Spring 容器
彻底改变你的编程人生!揭秘 Spring 框架依赖注入的神奇魔力,让你的代码瞬间焕然一新!
【8月更文挑战第31天】本文介绍 Spring 框架中的依赖注入(DI),一种降低代码耦合度的设计模式。通过 Spring 的 DI 容器,开发者可专注业务逻辑而非依赖管理。文中详细解释了 DI 的基本概念及其实现方式,如构造器注入、字段注入与 setter 方法注入,并提供示例说明如何在实际项目中应用这些技术。通过 Spring 的 @Configuration 和 @Bean 注解,可轻松定义与管理应用中的组件及其依赖关系,实现更简洁、易维护的代码结构。
51 0
|
3月前
|
JSON Java API
解码Spring Boot与JSON的完美融合:提升你的Web开发效率,实战技巧大公开!
【8月更文挑战第29天】Spring Boot作为Java开发的轻量级框架,通过`jackson`库提供了强大的JSON处理功能,简化了Web服务和数据交互的实现。本文通过代码示例介绍如何在Spring Boot中进行JSON序列化和反序列化操作,并展示了处理复杂JSON数据及创建RESTful API的方法,帮助开发者提高效率和应用性能。
144 0
|
3月前
|
JSON Java API
【Azure Developer】如何验证 Azure AD的JWT Token (JSON Web 令牌)?
【Azure Developer】如何验证 Azure AD的JWT Token (JSON Web 令牌)?
|
3月前
|
前端开发 JavaScript
【Azure 环境】前端Web通过Azure AD获取Token时发生跨域问题(CORS Error)
【Azure 环境】前端Web通过Azure AD获取Token时发生跨域问题(CORS Error)
|
3月前
|
API
【Azure API 管理】在 Azure API 管理中使用 OAuth 2.0 授权和 Azure AD 保护 Web API 后端,在请求中携带Token访问后报401的错误
【Azure API 管理】在 Azure API 管理中使用 OAuth 2.0 授权和 Azure AD 保护 Web API 后端,在请求中携带Token访问后报401的错误
|
3月前
|
JSON 数据格式
【应用服务 App Service】在Azure Web App的部署文件中,是否可以限制某些文件无法被访问?(如json)
【应用服务 App Service】在Azure Web App的部署文件中,是否可以限制某些文件无法被访问?(如json)
|
1月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
100 3