记得上一篇Spring Cloud的文章关于如何使JWT失效进行了理论结合代码实践的说明,想固然的觉得那篇会是基于Spring Cloud统一认证架构系列的最终篇。但关于JWT另外还有一个热议的话题是JWT续期?。html
本篇就我的以为比较好的JWT续期方案以及落地和你们分享一下,算是抛转引玉,你们有好的方案欢迎留言哈。前端
后端vue
管理前端ios
微信小程序git
理论背景: 在 有来商城 微服务项目 OAuth2实现微服务的统一认证的背景下,前端调用/oauth/token接口认证,在认证成功会返回两个令牌access_token和refresh_token,出于安全考虑access_token时效相较refresh_token短不少(access_token默认12小时,refresh_token默认30天)。当access_token过时或者将要过时时,须要拿refresh_token去刷新获取新的access_token返回给客户端,可是为了客户良好的体验须要作到无感知刷新。github
方案一:spring
浏览器起一个定时轮询任务,每次在access_token过时以前刷新。axios
方案二:小程序
请求时返回access_token过时的异常时,浏览器发出一次使用refresh_token换取access_token的请求,获取到新的access_token以后,重试因access_token过时而失败的请求。后端
方案比较:
第一种方案实现简单,但在access_token过时以前刷新,那些旧access_token依然可以有效访问,若是使用黑名单的方式限制这些就的access_token无疑是在浪费资源。
第二种方案是在access_token已经失效的状况下才去刷新便不会有上面的问题,可是它会多出来一次请求,并且实现起来考虑的问题相较下比较多,例如在token刷新阶段后面来的请求如何处理,等获取到新的access_token以后怎么从新重试这些请求。
总结:第一种方案实现简单;第二种方案更为严谨,过时续期不会形成已被刷掉的access_token还有效;总之二者都是可行方案,本篇就第二种方案如何经过先后端的配合实现无感知刷新token实现JWT续期展开说明。
直接进入主题,如何经过代码实如今access_token过时时使用refresh_token刷新续期,本篇涉及代码基于Spring Cloud后端youlai-mall 和 Vue前端 youlai-mall-admin,须要的小伙伴能够下载到本地参考下,若是对你有帮助,也但愿给个star,感谢~
后端部分这里惟一工做是在网关youlai-gateway鉴定access_token过时时抛出一个自定义异常提供给前端断定,以下图所示:
小伙伴们在这里也许会有疑问,网关这里如何判断JWT是否已过时?先不急,下文会说明,先看实现以后再说原理。
设置OAuth2客户端支持刷新模式,只有这样才能使用refresh_token刷新换取新的access_token。以及为了方便咱们测试分别设置access_token和refresh_token的过时时间,由于默认的12小时和30天咱们吃不消的;除此以外,还必须知足t(refresh_token) > 60s + t(access_token)的条件, refresh_token的时效大于access_token时效咱们能够理解,那这个60s是怎么回事,别急仍是先看实现,缘由下文会说明。
设置了支持客户端刷新模式以后,在前端添加一个refreshToken方法,调用的接口和登陆认证是同一个接口/oauth/token,只是参数受权方式grant_type的值由password切换到refresh_token,即密码模式切换到刷新模式,这个方法做用是在刷新token以后将新的token写入到localStorage覆盖旧的token。
在判断响应结果是token过时时,执行刷新令牌方法覆盖本地的token。
在刷新期间需作到两点,一是避免重复刷新,二是请求重试,为了知足以上两点添加了两个关键变量:
在第一次access_token过时请求失败时,调用刷新token请求时开启此标识,标识当前正在刷新中,避免后续请求因token失效重复刷新。
当执行刷新token期间时,须要把后来的请求先缓存到等待队列,在刷新token成功时,从新执行等待队列的请求便可。
修改请求响应封装request.js的代码以下,关键部分使用注释说明,完整工程 youlai-mall-admin
let refreshing = false,// 正在刷新标识,避免重复刷新 waitQueue = [] // 请求等待队列 service.interceptors.response.use( response => { const {code, msg, data} = response.data if (code !== '00000') { if (code === 'A0230') { // access_token过时 使用refresh_token刷新换取access_token const config = response.config if (refreshing == false) { refreshing = true const refreshToken = getRefreshToken() return store.dispatch('user/refreshToken', refreshToken).then((token) => { config.headers['Authorization'] = 'Bearer ' + token config.baseURL = '' // 请求重试时,url已包含baseURL waitQueue.forEach(callback => callback(token)) // 已刷新token,全部队列中的请求重试 waitQueue = [] return service(config) }).catch(() => { // refresh_token也过时,直接跳转登陆页面从新登陆 MessageBox.confirm('当前页面已失效,请从新登陆', '确认退出', { confirmButtonText: '从新登陆', cancelButtonText: '取消', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { location.reload() }) }) }).finally(() => { refreshing = false }) } else { // 正在刷新token,返回未执行resolve的Promise,刷新token执行回调 return new Promise((resolve => { waitQueue.push((token) => { config.headers['Authorization'] = 'Bearer ' + token config.baseURL = '' // 请求重试时,url已包含baseURL resolve(service(config)) }) })) } } else { Message({ message: msg || '系统出错', type: 'error', duration: 5 * 1000 }) } } return {code, msg, data} }, error => { return Promise.reject(error) } )
完成上面先后端代码调整以后,接下来进入测试,还记得上面设置access_token时效为1s、refresh_token为120s吧。这里access_token设置为1s,可是时效确是61s,至于缘由下文细说。这里把测试根据时间分为3个阶段:
声明: 问题基于youlai-mall项目使用的nimbus-jose-jwt这个JWT库,依赖spring-security-oauth2-jose这个jar包。
1. 如何断定JWT过时?
JWT的是否过时判断最终落点是在JwtTimestampValidator#validate方法上
2.为何access_token比设定多了60s时效?
开挂?有后台?向天再借60s?
刚开始在不知情的状况下觉得本身哪里配置错了,设置5s过时,等个1min多钟。后来确实没办法决心去调试下源码,最后找到JWT验证过时的方法JwtTimestampValidator#validate
基本上知足 Instant.now(this.clock).minus(this.clockSkew).isAfter(expiry)
就说明JWT过时了
now - 60s > expiry =转换=> now > expiry + 60s
按正常理解当前时间大于过时时间就可断定为过时,但这里却在过时时间加了个时钟偏移60s,活生生的延长了一分钟,至于为何?没找到说明文档,注释也没说明,知道的小伙伴欢迎下方留言~
本篇讲述 youlai-mall 项目中如何经过先后端配合利用双token刷新实现JWT续期的功能需求,后端抛出token过时异常,前端捕获以后调用刷新token请求,成功则完成续期,失败(通常指refresh_token也过时了)则须要从新登陆。在代码的实现过程当中了解到在资源服务器(youlai-gateway)如何判断JWT是否过时、axios如何进行请求重试等一些问题。
最后说一下本身的项目,youlai-mall 集成当前主流开发模式微服务加先后端分离,当前最新主流技术栈 Spring Cloud + Spring Cloud Alibaba + Vue , 以及最流行统一认证受权Spring Cloud Gateway + Spring Security OAuth2 + JWT。因此以为本文对你有所帮助的话给个关注(持续更新中...),或者对该项目感兴趣的小伙伴给个star,也期待你的加入和建议,仍是老样子有问题随时联系我~(微信号:haoxianrui)。
项目名称 | 地址 |
---|---|
后台 | youlai-mall |
管理前端 | youlai-mall-admin |
微信小程序 | youlai-mall-weapp |