使用JWT的正确姿势分析好久没有正确使用jwt了。意识到这个问题后,我将自己最真实的思考和总结分享给大家,欢迎讨论现状。先说下我之前的使用方法:登录成功后,将userId放入payload中生成jwt(8小时有效),然后将jwt发送给前端,前端每次都存储访问API需要在header中携带jwt。后端先解析jwt。得到userId后,数据库查询用户的权限列表(user-role-permission)。获取到用户的权限列表后,将其与当前界面需要的权限进行匹配。数据返回成功,失败返回401jwt标准。首先,让我们检查一下标准是什么。首先参考jwt.io上关于使用场景的描述:AuthorizationAuthorizationInformationExchangeInformationexchange对于以上信息,我个人的理解是两个方面:允许用户访问路由、服务和资源。在我的例子中,它是接口所需的权限。我也可以使用SSO登录。我不需要能够确定当前用户的身份。在我的例子中,它是userId。目前使用方法如下缺陷:每次调用接口都需要数据库查询权限(user-role-permission),浪费资源。登录成功8小时后,即使用户一直在不停的使用系统,jwt还是会失败,需要重新登录。第一点说起来简单,把权限列表放到payload里,解析后直接和接口需要的权限对比。第二点是把有效期延长到一周,一个月?但是还是会出现jwt失效的情况,需要重新登录。时间设置太长也不安全,因为jwt本身是无状态的,改了权限怎么办?需要很长时间才能见效吗?,看来jwt要刷新了。对应的优化点在这里:将权限列表放入payload中,这样用户就不用每次都去数据库查询,方便用户刷新jwtrefreshjwt解决方案这里参考stackoverflow上的讨论:jwt-refresh-token-flowJWT(JSONWebToken)automaticprogressionofexpiration然后我确定了我的刷新流程:登录成功后发放两个token:accessToken有效期为1小时,refreshToken有效期为1天。accessToken过期后返回401,前端通过refreshToken获取新的accessToken和新的refreshToken。RefreshTokenisinvalid返回403后需要重新登录。也就是说,登录成功后,可以在refreshToken的有效期内继续操作,有效期会延长,不会在使用过程中突然需要重新登录。这两个有效期可以自行调整。这里我考虑的是accessToken不能太长,否则调整权限后有效期会太短。后端调整增加一个刷新token的接口,大部分逻辑和登录一样,验证refreshToken后,返回新的accessToken和新的refreshToken前端调整的主要难点在前端部分,前端-端刷新逻辑:登录成功后在前端存储accessToken和refreshToken,后续每次调用API都需要携带accessToken。用户继续操作后台一个小时,返回401,此时accessToken失效,缓存该阶段的所有请求。使用refreshToken获取新的accessTokennewrefreshToken使用新的accessToken重新发起刚才缓存的所有请求。一天后,用户再次操作,后台返回401,此时accessToken失效。该阶段的所有请求都使用refreshToken进行缓存和获取,后端返回403。跳转到登录页面,重新登录。这里需要考虑的是并发请求。需要缓存accessToken过期后这段时间的所有请求,获取有效的accessToken后继续所有未完成的请求。目前,我正在使用axios。使用拦截器,这里贴出部分核心代码://响应拦截器axios.interceptors.response.use(response=>{constdata=response.data;//没有代码但是httpstatus200表示外部请求成功if(!data.code&&response.status===200)returndata;//根据返回的code值(以及后台的私有协议)做不同的处理switch(data.code){case200:returndata;default:}//如果返回码不正确,并且已经登录,则会抛出错误throwdata;},err=>{//这里是当http状态码不是200或304时的错误处理if(错误&&错误.response){switch(err.response.status){case400:err.message='请求错误';休息;case401://accesstoken错误if(router.currentRoute.path==='/login'){break;}//判断是否有refreshTokenconstroot=useRootStore();如果(!root.refreshToken){注销();休息;}//进入refreshtoken流程//本次请求的所有配置信息,包括url、method、data、header等信息constconfig=err?.config;constrequestPromise=newPromise(resolve=>{addRequestList(()=>{//注意这里的createRequest函数是在resolve开始执行的时候执行的,返回一个NewPromise,这个newPromise会替换接口调用的Promise解决(创建请求(配置));});});refreshTokenRequest();//这个很重要,因为这次请求是401,必须返回给调用接口方法返回一个新的请求returnrequestPromise;case403://这里的403表示刷新令牌失败,登录已过期,需要重新登录//10秒后清除所有缓存的请求setTimeout(()=>{clearTempRequestList();},10000);登出();休息;默认值:}}returnPromise.reject(err);});刷新部分逻辑代码:importaxiosfrom'axios';importhttpfrom'./index';import{useRootStore}from'@/store/root';//临时请求函数列表consttempRequestList=[];//发起刷新令牌标志,防止重复刷新请求letisRefreshing=false;//1min内刷新了tokenflag//为了防止并发刷新请求,tempRequestList已经清空,还有请求返回403,导致重复刷新letrefreshTokenWithin1Minute=false;constrefreshTokenRequest=()=>{if(isRefreshing){返回;}if(refreshTokenWithin1Minute){for(constrequestoftempRequestList){request();}tempRequestList.length=0;返回;}isRefreshing=true;refreshTokenWithin1Minute=true;constroot=useRootStore();//使用刷新令牌请求新的访问令牌和刷新令牌constparams={refreshToken:root.refreshToken};http.post('/api/v1/refresh-token',params).then(({data})=>{root.updateAccessToken(data.token);root.updateRefreshToken(data.refreshToken);root.updateUserId(data.userId);for(constrequestoftempRequestList){请求();}//1分钟后清除标志setTimeout(()=>{refreshTokenWithin1Minute=false;},60000);tempRequestList.length=0;isRefreshing=false;});};constaddRequestList=request=>{tempRequestList.push(request);};constclearTempRequestList=()=>{tempRequestList.length=0;};constcreateRequest=config=>{//这里必须更新header中的AccessTokenconstroot=useRootStore();config.headers['Authorization']='Bearer'+root。访问令牌;returnaxios(config);};export{refreshTokenRequest,createRequest,addRequestList,clearTempRequestList};源码提供了前后端的源码,您可以使用源码调整两个token的有效期进行测试。后端部分的源码在这里前端部分的源码这里还有在线体验地址
