第一次接触的时候,有没有这样的疑惑,为什么需要refreshToken,而不是从服务端获取一个更长甚至永久的accessToken?带着这个疑问,我在网上搜索了一下。其实这个accessToken的使用寿命有点像我们生活中住酒店。入住酒店时,我们会出示身份证件进行登记,领取房卡。这时房卡就相当于accessToken,可以进入对应的房间。当你的房卡过期后,你就不能再开门了。这时候需要到前台更新房卡才能正常入住。这个过程也相当于refreshToken。accessToken的使用频率比refreshToken高很多。综上所述,如果赋予accessToken更长的有效时间,将存在不可控的权限泄露风险。使用refreshToken可以提高安全性。当用户访问网站时,accessToken被窃取。此时攻击者可以使用accessToken访问权限内的功能。如果accessToken设置了2小时的较短有效期,那么攻击者最多可以使用窃取的accessToken2小时,除非通过refreshToken刷新accessToken才能正常访问。设置accessToken的有效期是永久性的。用户修改密码后,之前的accessToken仍然有效。一般来说,refreshToken可以降低accessToken被盗的风险。JWT无感刷新TOKEN方案(结合axios)的业务需求是在用户登录应用后,服务器会返回一组数据,包括accessToken和refreshToken。每个accessToken都有固定的有效期。如果向服务端请求过期的token,服务端会返回401的状态码,告诉用户token已经过期了,这时候需要使用登录时返回的refreshToken调用刷新Token接口(Refresh)更新新令牌,然后发送请求。话不多说,先从最流行的http请求库之一的代码工具axios说起。在本文中,我们将使用其错误响应拦截器来实现令牌刷新功能。具体来说,这次基于axios-bz代码片段封装响应拦截器,可以直接在你的项目中配置使用。????在业务代码获取接口数据之前,使用interceptors.response检查当前携带的accessToken是否无效,状态码为401。下面是关于interceptors.response中exception阶段的处理内容。当响应码为401时,响应拦截器会转到第二个回调函数onRejected。下面这段代码可能大家读起来不是很流畅。下面我直接把整个代码贴出来,每段代码都是分开的。添加了相应的注释//最大重传次数constMAX_ERROR_COUNT=5;//当前重传次数letcurrentCount=0;//缓存请求队列constqueue:((t:string)=>any)[]=[];//是否刷新当前状态letisRefresh=false;exportdefaultasync(error:AxiosError)=>{conststatusCode=error.response?.status;constclearAuth=()=>{console.log('身份已过期,请重新登录');window.location.replace('/login');//清除数据sessionStorage.clear();returnPromise.reject(错误);};//为了省去冗余代码,这里只展示处理状态码为401的情况if(statusCode===401){//accessToken无效//判断本地缓存中是否有refreshTokenconstrefreshToken=sessionStorage.get('刷新')??null;if(!refreshToken){clearAuth();}//提取请求的配置const{config}=error;//判断是否刷新失败,状态码为401,再次进入错误拦截器if(config.url?.includes('refresh')){clearAuth();}//判断当前是否处于刷新状态(防止多个请求多次调用刷新接口)if(!isRefresh){//设置当前状态刷新isRefresh=true;//如果重传次数超过,直接退出if(currentCount>MAX_ERROR_COUNT){clearAuth();}//增加重试次数currentCount+=1;try{const{data:{access},}=awaitUserAuthApi.refreshToken(refreshToken);//请求成功,缓存新的accessTokensessionStorage。设置(“令牌”,访问);//重置重传次数currentCount=0;//遍历队列,重新发起请求queue.forEach((cb)=>cb(access));//返回请求数据returnApiInstance.request(error.config);}catch{//刷新token失败,直接退出console.log('请重新登录');sessionStorage.clear();window.location.replace('/登录');返回Promise.reject(错误);}finally{//重置状态isRefresh=false;}}else{//当前正在尝试刷新token,首先返回一个promise阻塞请求并推进请求列表returnnewPromise((resolve)=>{//缓存网络请求,并执行queue.push((newToken:string)=>{Reflect.set(config.headers!,'authorization',newToken);//@ts-ignoreresolve(ApiInstance.request>(config));});});}}返回Promise.reject(error);};复制代码并提取上面关于调用refreshtoken的代码代码中分离出一个refreshToken函数来单独处理这种情况,有利于提高代码的可读性和可维护性,让代码看起来不会很臃肿//refreshToken.tsexportdefaultasyncfunctionrefreshToken(error:AxiosError){/*把上面if(statusCode===401)中的代码贴上去,这里不再赘述代码仓库地址:https://github.com/QC2168/axios-bz/blob/main/interceptors/hooks/refreshToken.ts*/}复制代码上面的逻辑提取出来后,现在看拦截器中的代码就很简单了。以后如果要调整相关逻辑,直接在refreshToken.ts文件中调整即可error.response?.status;//为了省去冗余代码,只处理状态码为401的情况if(statusCode===401){refreshToken()}returnPromise.reject(error);};