JWT(JSON Web Token)

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是长这个样子的:

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

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

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

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

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

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 编码之后生成出来的:


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

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

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

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


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

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

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


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


会发现通过在线工具生成的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 这个页面,它最终成功访问到这个页面的过程是:


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

它用到了两个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,它的处理过程是:


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

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


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

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


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

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


最重要的是要清除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 交互,也会增加请求响应时间,影响用户体验。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容

  • 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的...
    Dearmadman阅读 1,060,270评论 214 1,020
  • 常规用户认证需求 用户先通过账号密码登录获取授权后,再获取用户详情信息 对比传统Session和JWT实现该需求的...
    hellsam阅读 2,956评论 0 8
  • 什么是 JWT? JWT(Json Web Token),是一个开源biaozhun,轻量,携带者用户信息的 js...
    芝麻香油阅读 741评论 0 2
  • JWT是Json web token的缩写,用做webapi的请求校验 基本组成部分:Header.Payload...
    针织衫阅读 274评论 0 1
  • JWT是URL-safe的,可以用来查询字符参数 JWT分成三部分,每部分之间用.隔开。header.payloa...
    losspm阅读 2,649评论 0 1