“长短令牌三验证”的JWT令牌续签策略(兼顾安全、性能、及用户状态管理的综合性方案)

前言:最近研究JWT的续签机制,发现虽然JWT已经在业界广泛应用,但续签机制的探讨还是处于一种百家争鸣的状态(有些策略甚至能看出连JWT的基本规范都没学扎实)。所以不才在吸收了一圈各路大神分享的续签策略后加上一些个人的思考,提出这套名叫“长短令牌三验证”的解决策略,希望能为互联网开发生态的完善做出一点自己微薄的贡献。

令牌使用策略概述

名词概念:

顾名思义,本机制下所使用的令牌分为长短两种:长令牌即过期时间较长的refresh_token,专门用于token的续签,同时刷新两种token;短令牌即过期时间较短的access_token,进行常规业务请求时使用。

流程概述:

  1. 登陆
  2. 用户登录成功后,同时获得长短两种令牌,之后长令牌存在客户端暂不使用,仅使用短令牌用于常规业务请求。
  3. 服务端在接收到每个携带短令牌的业务请求时,按照JWT的规则进行用户的身份验证,并从payload中获取用户身份信息,此即“三验证”中的第一种验证。
  4. 上述操作中,客户端如果发现自己的请求因短令牌过期被拒,则使用长令牌(refresh_token)向专用于续签令牌的接口发起请求,申请一对拥有全新过期时间的长短令牌。
  5. 服务端在接收到续签请求时,会进行两步验证:一是根据JWT的算法规则验证长令牌的合法性,此即“三验证”中的第二种验证;如果通过,则将该token按照某一规则进行转变,转变为一个有状态的token,用于比对服务端所存储的token数据,也即“三验证”中的第三种验证。(该步骤是本策略的核心关键,后面会展开详说。)
  6. 以上的两步验证均通过时,才会将此次请求正常返回。后续处理和登陆成功时一致,即生成并返回一对拥有全新过期时间的长短令牌,客户端也同样,拿短令牌访问常规业务接口,用长令牌续签。
  7. 上述操作中的第二、三种验证如果出现任何原因的续签失败,则统一回到1步骤要求用户重新登陆。(视情况也可增加“盗号风险提醒”功能,后详)

补充:

  1. 短令牌的过期时间为“自动续签”的最短时间,小于该时间则令牌有效,大于该时间则客户端自动使用长令牌进行令牌续签,以上操作对用户无感;
  2. 长令牌的过期时间为“用户重新登陆”的最短时间,在该时间内只要有任何访问服务的操作,则客户端均会在需要时进行自动续签,用户无感。一旦超过该时间才会需要用户重新登陆。
  3. 常规的业务请求使用短token,验证时只用秘钥和jwt算法验证合法性;刷新token的请求使用长token,进行两步验证:一是jwt算法验证,二是转换成有状态的token后进行字符串匹配验证

长令牌续签时的两道验证机制

使用短令牌进行身份验证的过程属于JWT基础,这里略过,只说续签:
续签服务的第一道JWT验证机制好理解,和业务请求时的JWT身份验证基本一致,即按照JWT的算法规则验证该token是否合法。

秘钥可以和短令牌一致也可以不一致,个人建议设计成不一致,多少能提升一些安全性。不过如果你的续签接口是和其他业务接口混用的,设计成一致的也无伤大雅,以免同一个服务还要搞两套不同秘钥的JWT验证策,没必要增加这种复杂度。

重点是接下来要说的第三种验证,即“有状态”的token验证。

如何“转变”为有状态token

首先要介绍的是在第三种验证中提及的“转变”为有状态token。本质上是一个你自定义的规则,即要求你自定义一个方式,将格式为JWT的refresh_token转变为一个仅作为匹配一致性使用的、无意义的字符串,该转变只需要保证结果唯一,无需可逆。你可以对整个refresh_token进行一次md5计算(长度32),也可以直接把jwt token的签名部分截取使用(长度43)。开发阶段可以先使用后者,方便一眼看出对应关系。本文中把这个由长令牌转换来的字符串称为 “有状态token”,以区别于运用jwt规则验证的无状态token。

第三种验证(有状态token验证)机制流程

核心机制:

首先在用户登陆阶段(流程概述的步骤1)做出改动,在生成了长短两个jwt token之外,还需生成一个有状态token,由长令牌按前面所说的规则“转变”而来。之后将长短令牌返回客户端,将有状态token作为键名存在redis中。留待后续的续签服务中使用。
之后在第三种验证时(流程概述的步骤5),只需将请求所携带的长令牌按照你设定的规则转变为为有状态token,再以此为键去redis中查询是否存在对应键即可。如果存在,则表示验证通过,其后的操作则与登录时一致,即生成长短令牌与有状态token,前俩令牌返回客户端,有状态令牌存redis,并在redis中删去此次请求中已使用过的 有状态令牌。

设计思路:

以上操作的核心意义在于给长令牌赋予了只能一次性使用的特性,可以大大提升系统的安全性,弥补JWT token无法提前失效的弊端。即假如自动续签token的请求被黑客抓包,将其中的长令牌重复使用,虽然能顺利通过第二种验证,但随即就会因为其所对应的“有状态token”已经在redis中被删而无法通过第三种验证。
同样由于一次性机制,即使长令牌泄露并被黑客使用,也会被合法客户端及时发现——因为下一次自动续签时,合法客户端的长令牌就会因为一次性机制失效了,继而要求用户重新登录,使得黑客手中的refresh token失效。也方便服务端根据登录、续签时的ip地址等记录判断该用户是否存在被盗号的风险,进一步进行账号冻结或提醒操作。
同时,纯JWT机制下,服务端系统原本无能为力的注销用户、拉黑名单等用户状态管理操作也得到了补全。注销时只需在redis中删去对应的有状态token,拉黑名单则在前者基础上进一步限制登陆即可,此时客户端发来的续签请求都会因为无法通过有状态token的验证而被拒。虽然客户端的短token还能在失效前继续可用,但已经利大于弊,采用JWT机制所失去的用户登录状态管理功能已经能够得到最大限度的挽回。

后续补充:如果连客户端的短token依然短暂有效这点也不愿接受,也有对应策略:即增设一个仅针对claims中用户名字段(比如aud,userid之类)的临时黑名单,缓存过期时间设为用户手上短token的最长过期时间,每次普通业务请求的验证jwt token阶段也比对一下当前token的aud是否在黑名单中。因为这层验证极为频繁,所以比较建议的做法是将这个黑名单同步到每个微服务的本地缓存中以最大限度地节约通信开销。即便如此也是一笔不小的开销,各位同行可以根据自己业务情况自行取舍。

进阶用法:

同一用户多设备的情形下登陆状态的管理

基于上述设计,再稍稍扩展一下,也能做好同一用户多设备的情形下登陆状态的管理:比如限制用户同时在线的设备数目、限制用户在同一类设备上仅能同时在线一个等等。

拿其中最为复杂的“限制用户在同一类设备上仅能同时在线一个”举例,在redis中以用户id为键,再维护一套hash(子map)对照表。表的filed为设备类型,用户登陆时由客户端传来,如果对应filed不存在则直接保存,以设备类型为filed,以有状态token的字符串为值;如果对应filed已存在,则先根据其中存的值,找到对应有状态token的键并删除,再将“设备类型–token”的field–value对存入。此时同类型旧设备上所存的refresh_token即会因为对应有状态token被删而失效,需要用户重新登录。
所以“限制用户同时在线的设备数目”也更简单了,把上面的设计的hash结构改成list结构,用lua脚本确保list元素上限,超过了则连同有状态token的key一并移除,以此让最旧的设备上的refresh token失效。
并且,如果客户端需要加个“管理我的设备”功能,也能一并实现了。用户可以自行移除自己其他设备上的自动登录状态。

可选策略:

系统设计的关键是合适,而非通用,所以根据不同情形和要求可以基于以上设计产生多种附加或删减的设计:

1. 结合通信加密(进一步加强长令牌的安全性)

登陆的请求本来就应当加密。同时为了避免登陆或续签令牌时的返回值被人抓包,这部分也建议和登陆请求一样加密。原本refresh_token就已经做成一次性了,在续签请求时被抓包也不怕,但是获取到新token并返回时还是有点风险,这部分加上加密策略就几乎完美的安全了。

这里简述一下通信加密策略的建议,即对称和不对称加密混用的通信加密策略
每次请求前客户端先生成一个临时秘钥,用该秘钥对称加密消息体(或其中的敏感数据),将该秘钥用不对称加密的公钥加密后,放在请求头中,和加密后的消息体一起发送到服务端。
服务端在获得请求后,从请求头中获取加密后的临时秘钥并用不对称加密的私钥解密得到临时秘钥,然后用临时秘钥对称解密消息体。
返回值如果需要加密,就也和客户端的做法一样,用临时秘钥加密消息体后返回到客户端,临时秘钥也不用带了,因为本来就是客户端发来的,它自己必然还存着一份用以解密。

2. 结合ip地址绑定(进一步加强短令牌的安全性)

加强与设备的绑定肯定能让通信更为安全,但是设备唯一id,比如pc的网卡号和iphone的UDID,得由客户端自行获取并填入请求消息体中,可以造假,没有意义。剩下的只有IP地址了,但是如今的互联网环境是设备可能在多个网络间切换,比如wifi信号不好我就切蜂窝网络,出门换个地儿也就换了wifi,以此导致ip地址可能频繁变化。所以ip的绑定要做也只能绑短令牌access_token(将ip地址加入到payload中),一旦网络切换,短令牌失效而长令牌继续有效,就需要走令牌续签的接口获取新的ip地址下的新短令牌。同时由于我们上面有状态token的机制,refresh_token的验证已经非常安全了,所以短令牌加上ip绑定后也“全上加全”。
当然更安全的代价是服务端资源的更大开销,首先是每次常规的业务请求,在jwt的合法性验证之后,还要拿出客户端的ip地址对比一下和该token的payload中保存的ip地址是否一致;其次是每次切换网络,都要走一遍续签接口,重新生成长短令牌对并更新缓存中的有状态token。这开销和安全性之间的平衡,就由读者自行取舍了。
当然纯PC客户端或者内网办公系统要这么玩还是非常适合的。前者ip地址不会频繁变化,后者并发量也不会有多高。(不过这两种情形,是不是连采用JWT的必要性都有待商榷了?)

3. 异地登录提醒。

redis中存转变后的第三种令牌用的是Key,此时value闲置,可以存用户登陆时的ip地址,这样当下次进行续签请求时可以比对一下ip地址的范围是不是还在同个城市,如有变动可以给用户发个异地登录提醒。
关于value的用法目前只想到这个,能用上总比空着好,要是还有啥个性化需要能把它利用起来的也欢迎在评论中补充或者进群交流。

Q&A,系统设计思路

  1. 为什么要做“转变为有状态token”这一步骤 ,是不是可以直接存长令牌的JWT token?
    答:可以是可以,功能也能走通。但是JWT token更长更占空间都是其次,更重要的是我们系统设计中应该遵循“文要对题”、“形意合一”的原则,保证一目了然的高可读性,以此降低后续维护以及组内沟通时的学习成本。放在当前情境里就是:作为一个有状态token,既然它被当做比对字符串来使用,那它在形式上就应该是个无意义的字符串,类似jsessionid,或者加盐加密后的用户密码。拿自带无状态属性的jwt token当有状态token用,属于文不对题,会给后续的理解与沟通带来不必要的成本和障碍。

  2. 是不是可以随机生成一个字符串作为有状态token,每次续签或登陆返回给客户端三个token?
    答:可以,功能上没问题,三个token的设计本来就是现在这套设计的雏形。因为考虑到refresh token(JWT)和有状态token永远绑定在一起使用,而且后者原本只是比对字符串使用,用啥机制生成都行,完全不妨碍它们合并成一个。首先越简洁的设计越不容易出错;其次在客户端层面完全隐藏了第三道验证的存在,也可一定程度上增加被破解的难度。

  3. 长短令牌的必要性,是不是能合成一个,并使用在服务端判断是否临近过期的机制来实现自动续签?
    答:长短令牌的策略就我搜到的资料来看已经是挺公认的做法了,这里老生常谈一下:以仅使用一个令牌为前提:常规业务请求时,令牌使用频繁且重复使用,泄露风险非常高,所以过期时间越短泄露后的危害就越小;同时令牌一旦过期就需要用户重登录,手动登录越频繁用户体验越差,从这个角度考虑又是过期时间越长用户体验越好。所以长短令牌结合使用的优越性就体现在这里:短令牌频繁使用,靠短间隔的更新增加安全性;长令牌使用频率低天然安全,用长间隔过期保证用户重登录的体验。

  4. 既然最终结果由第三种验证(有状态token的比对)来决定,那么第二种验证策略,即在续签请求中首先以JWT规则验证长令牌是否合法的步骤,是不是可以去掉?
    答:不要去掉,虽然最终结果由第三种验证决定,但第二种验证是一个在性能优化方面非常具有性价比的环节。这道验证的存在类似布隆过滤器,先以低代价的本机运算方式将请求过滤一遍,排除掉那些连JWT token的合法性都没做到的请求,之后再进行远程访问,从而有效节约网络通信和远程缓存服务器的开销。

  5. 每次短令牌过期,都要重新生成短和长token,是否过度消耗性能?
    答:我这文选用的方案比较偏重安全,所以略微牺牲一点性能,换来更好的安全性;如果觉得混合加密也够安全了且希望更节约一些开销,也可以这么改:短token过期时用混合加密长token向服务器申请新的短token,服务器判定长token是否临近过期,临近则同时发放长短token并替换有状态token,否则就仅发放短token。
    其实区别就在于长令牌是每次都无脑换新还是临近过期才换新,无脑换的安全性高一些,逻辑简单粗暴些,代价是性能开销略大;临近换则反之。实际工作中可以初版用无脑换的方案,等稳定后视需要再改临近换。毕竟前者逻辑更简单,开发起来更稳,而后者本身就是在前者的基础上再追加一层逻辑判断。

特征总结

  1. 短token业务用,长token续签用。普通业务时短token用jwt规则验一次,续签时长token不光用jwt规则验一次,还要转变成有状态token再比对服务端的token缓存再验一次。
  2. 有状态token使用一次后即刷新替换,以此保证其安全性。通过一次性机制有效防止非法客户端使用其截获的refresh_token在一定期间内“无限续杯”。同时也可以此获得在服务端管理用户在线、登出的功能,弥补纯无状态的jwt令牌只能“坐待过期”的不足。

后续改进:利用nbf属性批量颁发短令牌

JWT的规则自带一个nbf属性,该属性可以控制jwt开始生效的时间。那么基于前面的设计就可以做出一个更为高效的改动:
每次登录或续签时,在颁发一个长token之外,批量颁发短token,这批短token的有效时间头尾相接,即后一token在前一token临近失效时才生效,保证同一时间只有一个短令牌有效。这样在控制短token泄露危害的同时还可以大大延长续签周期。
不过本策略会导致客户端的逻辑会变得更为复杂,主要是体现在客户端和服务器的时间不同步时:一长一短的情形时无脑用短+被拒直接refresh即可,但多短的情形下,你可能需要加上“转换服务端时间差”的逻辑,以及“短token被拒时继续尝试下一个短token”的逻辑。(有些app在使用时会检测你设备的时钟是否和服务器一致,猜测就是用了这个。

原文地址

“长短令牌三验证”的JWT令牌续签策略

联系作者

站内信或评论吧,csdn的帖子现在不允许留qq

原创不易,认可我的作品就请点个赞吧↘ 谢谢

“长短令牌三验证”的JWT令牌续签策略(兼顾安全、性能的综合性方案)相关推荐

  1. vue12Jwt详解+JWT组成+JWT的验证过程+JWT令牌刷新思路+代码

    目录 1. JWT是什么 2. 为什么使用JWT 3. JWT的工作原理: 4. JWT组成 JWT结构原理图: JWT实际结构: 4.1 Header 4.2 Payload(负荷) 4.3 sig ...

  2. Spring Security OAuth2.0认证授权三:使用JWT令牌

    历史文章 [Spring Security OAuth2.0认证授权一:框架搭建和认证测试] [Spring Security OAuth2.0认证授权二:搭建资源服务] 前面两篇文章详细讲解了如何基 ...

  3. 从零开始超详细的Spring Security OAuth2.0实现分布式系统授权(注册中心+网关+认证授权服务(JWT令牌验证)+资源调用服务)

    文章目录 一.OAuth2.0 1.介绍 2.例子 3.执行流程 二.Spring Cloud Security OAuth2 1.环境介绍 2.认证流程 三.整合分布式项目 1.技术方案 2.项目结 ...

  4. jwt令牌(过滤器)

    JWT过滤器,阻拦器 1. JWT是什么 2. 为什么使用JWT 3. JWT的工作原理 4. JWT组成 4.1 Header 4.2 Payload(载荷) 4.3 signature 5. JW ...

  5. jwt令牌_JWT –生成和验证令牌–示例

    jwt令牌 JWT提供了一种非常有趣的方式来表示可以验证和信任的应用程序之间的声明. 我的目标是展示一个小的样本,它使用出色的Nimbus JOSE + JWT库来生成和验证令牌. 总览 进行介绍的最 ...

  6. Angular之jwt令牌身份验证

    Angular之jwt令牌身份验证 demo https://gitee.com/powersky/jwt 介绍 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种 ...

  7. 使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)——第1部分

    目录 介绍 JWT(JSON Web令牌) ASP.NET Core中的JWToken配置 用户模型类 创建令牌 第1步 第2步 第4步 令牌存储 中间件 自定义中间件app.Use() 中间件app ...

  8. Angular 4:使用JWT令牌进行用户身份验证

    介绍 本文是关于ASP系列文章的第四篇.网络核心身份: ASP.NET Core标识:启动asp.net CoreNET Core标识:建立一个web项目和标识数据库NET Core标识:用户注册,登 ...

  9. ASP.NET Core 基于角色的 JWT 令牌

    原文:https://bit.ly/3vYljq3 作者:Rick Strahl 翻译:精致码农-王亮 声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的.其中可能会去除一些本人实在不知 ...

最新文章

  1. 使用register_chrdev注册字符设备
  2. 谈谈对IOC及DI的理解与思考
  3. vue 保存时清空iuput_vue清空input file
  4. 音频光端机简单故障处理
  5. Vue--- 一点车项目
  6. 你的模型真的陷入局部最优点了吗?
  7. 低代码平台真的能拯救程序员的996吗?
  8. Why String is Immutable or Final in Java
  9. c语言常用单词表格,C语言常用单词
  10. 【读书笔记】100个Switf必备tips
  11. dubbo安装和使用
  12. VS2019/MFC学习笔记之一(创建对话框工程并实现简单加法运算)
  13. LoRa网关市场现状及未来发展趋势
  14. 神经系统的肿瘤有哪些,脑神经肿瘤最常见的是
  15. 推荐系统三十六式:矩阵分解 总结
  16. Windows 7无法启用网络发现的处理办法
  17. 程序员windows基础操作系列文章目录
  18. 面经分享:网友问我,怎样才能在谷歌匹兹堡办公室里写代码?下篇
  19. mc服务器切换模式显示英文字母,我的世界更改模式的指令是什么_MC切换模式指令方法新版...
  20. 计算机vb里代码里的双引号,在VB中使用字符串中的左双引号

热门文章

  1. 为什么样本点平均值会经过线性回归直线?
  2. 产品经理制,互联网公司发扬光大的
  3. 免费布隆过滤器在线计算
  4. 50年来,推动AI革命的背后8大统计学思想!
  5. 正大国际期货:如何摆脱炒黄金期货被套单?
  6. Vivado和modelsim联合仿真出现问题
  7. 如何修复图片中的瑕疵,怎么快速去水印
  8. 分类算法之K近邻和朴素贝叶斯
  9. 端午节用24种编程语言给各位码友们送上祝福
  10. AVM环视算法 3D效果 全方位视图 韩国VADAS 让你感受不一样的360全景