在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新

2024-06-04 7483阅读

文章目录

      • 一、需求背景
      • 二、token刷新的方案
        • 1、根据过期时间重新获取
        • 2、定时刷新token接口
        • 3、使用了RefreshToken
        • 三、关于RefreshToken
        • 四、Refresh Token的优点
        • 五、Refresh Token的工作原理
        • 六、Refresh Token的使用流程
        • 七、Refresh Token的实现步骤
          • 1、登录成功后保存AccessToken,RefreshToken
          • 2、正常请求业务接口的时候携带AccessToken
          • 3、响应拦截器处理401权限错误
          • 4、防止重复请求refreshToken接口
          • 5、同时多个请求返回401,需要刷新token
          • 八、总结
          • 九、代码上传
            • 1、vue项目部分
            • 2、nodejs服务部分
            • 十、效果展示

              一、需求背景

              对于一些需要记录用户行为的系统,在进行网络请求的时候都会要求传递一下登录的token。不过,为了接口数据的安全,服务器的token一般不会设置太长,根据需要一般是30分钟的样子,token过期后就需要重新登录。不过,频繁的登录会造成体验不好的问题,因此,需要体验好的话,就需要定时去刷新token,并替换之前的token。

              实现token无感刷新对于前端来说是一项十分常用的技术,其本质都是为了优化用户体验,当token过期时不需要用户调回登录页重新登录,而是当token失效时,进行拦截,发送刷新token的请求,获取最新的token进行覆盖,让用户感受不到token已过期。

              二、token刷新的方案

              1、根据过期时间重新获取

              后端返回过期时间,前端判断token过期时间,去调用刷新token的接口。

              缺点:需要后端额外提供一个token过期时间的字段;使用了本地时间判断,若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败。

              2、定时刷新token接口

              根据token过期时间,写一个定时器,定时刷新token接口

              缺点:浪费资源,消耗性能,不建议采用。

              3、使用了RefreshToken

              后端在登录之后会给前端一个RefreshToken字段同AccessToken一并传过来。token失效后利用RefreshToken去延长用户的登录信息。

              三、关于RefreshToken

              RefreshToken 方法是现代 Web 应用中一种常见的身份验证机制,尤其在需要长时间保持用户登录状态的场景下具有重要意义。

              RefreshToken 方法的主要作用是在用户登录后,服务器生成一个 RefreshToken 并将其返回给客户端。客户端在之后的每次请求中都需要携带这个 RefreshToken,以便服务器能够验证用户身份并返回用户所需的数据。

              使用场景包括但不限于:用户在应用中的长时间操作、用户在多个设备上使用应用、用户需要跨域访问应用等。在这些场景下,RefreshToken 方法能够有效地减少用户重复登录的次数,提高用户体验。

              四、Refresh Token的优点

              • 安全性增强:Refresh Token不同于AccessToken,它只在第一次获取和刷新时在网络中传输,因此被盗的风险远小于AccessToken。同时,Refresh Token是加密字符串,并且和token是相关联的,相比获取各种资源的token,refresh token的作用仅仅是获取新的token,因此其作用和安全性要求都大为降低。

              • 减少服务器负担:使用Refresh Token刷新服务端不需要刷新Token的过期时间,一旦Token过期,就反馈给前端,前端使用Refresh Token申请一个全新Token继续使用。这种方案中,服务端只需要在客户端请求更新Token的时候对Refresh Token的有效性进行一次检查,大大减少了更新有效期的操作,也就避免了频繁读写。

              • 提高用户体验:由于Refresh Token的存在,用户在访问令牌过期后不需要重新登录,提高了用户体验。

                五、Refresh Token的工作原理

                • 当AccessToken过期时,客户端使用Refresh Token发起刷新请求。
                • 认证服务器验证Refresh Token的有效性。
                • 如果Refresh Token有效,认证服务器会生成一个新的AccessToken,并返回给客户端。
                • 客户端收到新的AccessToken后,可以继续使用该token访问受保护资源。

                  在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新 第1张

                  六、Refresh Token的使用流程

                  • 首次登录的时候会获取到两个token(AccessToken,RefreshToken)
                  • 持久化保存起来(localStorage方案)
                  • 正常请求业务接口的时候携带AccessToken
                  • 当接口口返回401权限错误时,使用RefreshToken请求接口获取新的AccessToken
                  • 替换原有旧的AccessToken,并保存
                  • 继续未完成的请求,携带AccessToken
                  • RefreshToken也过期了,跳转回登录页面,重新登录

                    七、Refresh Token的实现步骤

                    1、登录成功后保存AccessToken,RefreshToken

                    登录请求登录接口authorization(),这里省略了。

                    比如我们请求登录接口"authorization"成功后,后端返回我们2个字段。

                    data:{
                        code:200,
                        msg:'ok',
                        accessToken:'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImFkbWluIiwiaWF0IjoxMzIyNTc1MDcyOSwiZXhwIjoxNzA2NjMwMzk5fQ.sTLeqLl9lgG4OW40RNXdoZz9NO2bgCOOtnXuErRkXBM',
                        RefreshToken:'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImFkbWluIiwiaWF0IjoxMzIyNTc1MDcyOSwiZXhwIjoxNzA2NjMzMzk5fQ.GF-j_rFEMNwh7H7o4MbM5EFspFC5lQ1zxD85e70nOiM',
                    }
                    

                    保存到localStorage里面

                    localStorage.setItem('accessToken', res.data.data.accessToken);
                    localStorage.setItem('RefreshToken', res.data.data.RefreshToken);
                    
                    2、正常请求业务接口的时候携带AccessToken
                    import axios from 'axios'
                    // 创建axios实例
                    const service = axios.create({
                      timeout: 20000, // 请求超时时间(毫秒)
                    })
                    // 请求拦截器
                    service.interceptors.request.use((config) => {
                    	const accessToken = localStorage.getItem('accessToken');
                    	const RefreshToken = localStorage.getItem('RefreshToken');
                    	if (config.url) {
                    		// 此处为 Refresh Token 专用接口,请求头使用 Refresh Token
                    		if (config.url.indexOf('/refreshToken') >= 0) {
                    			config.headers['token'] = RefreshToken;
                    		} else if (!(config.url.indexOf('/login') !== -1 && config.method === 'post')) {
                    			// 其他接口,请求头使用 Access Token
                    			config.headers['token'] = accessToken;
                    		}
                    		return config;
                    	}
                    }, error => {
                    	return Promise.reject(error);
                    })
                    
                    3、响应拦截器处理401权限错误
                    service.interceptors.response.use(async (response) => {
                    	let res = response.data
                    	// 为了演示,这里仅展示处理状态码为401的情况
                    	if (res.code == '401') {
                    			// 这里是获取新token的接口,方法在这里省略了。
                    			const result = await refreshToken()
                    			// 获取成功
                    			if (result && result.data) {
                    				// 新token
                    				let newToken = result.data
                    				// 保存新的accessToken
                    				localStorage.setItem('accessToken', newToken)
                    				// 替换新accessToken
                    				response.config.headers.token = newToken
                    				// 继续未完成的请求
                    				const resp = await service.request(response.config)
                    				// 返回请求结果
                    				return resp
                    			} else {
                    				// 清除token
                    				localStorage.clear()
                    				// 跳转到登录页
                    				router.replace('/login')
                    			}
                    	}
                    	return res
                    }, error => {
                    	// 返回错误信息
                    	return Promise.reject(error)
                    })
                    
                    4、防止重复请求refreshToken接口

                    为了防止多次刷新token,可以通过一个变量isRefreshing 去控制是否正在请求刷新token的接口。

                    响应拦截器处理,防止同时多次调用刷新token接口。

                    • 这里使用isRefreshing变量,存放是否正在请求
                      // 变量isRefreshing
                      let isRefreshing = false
                      service.interceptors.response.use(async (response) => {
                      	let res = response.data
                      	// 为了演示,这里仅展示处理状态码为401的情况
                      	if (res.code == '401') {
                      		// 控制是否在刷新token的状态
                      		if (!isRefreshing) {
                      			// 修改isRefreshing状态
                      			isRefreshing = true
                      			// 这里是获取新token的接口,方法在这里省略了。
                      			const result = await refreshToken()
                      			// 获取成功
                      			if (result && result.data) {
                      				// 新token
                      				let newToken = result.data
                      				// 保存新的accessToken
                      				localStorage.setItem('accessToken', newToken)
                      				// 替换新accessToken
                      				response.config.headers.token = newToken
                      				// 继续未完成的请求
                      				const resp = await service.request(response.config)
                      				// 重置状态
                      				isRefreshing = false
                      				// 返回请求结果
                      				return resp
                      			} else {
                      				// 清除token
                      				localStorage.clear()
                      				// 重置状态
                      				isRefreshing = false
                      				// 跳转到登录页
                      				router.replace('/login')
                      			}
                      		} 
                      	}
                      	return res
                      }, error => {
                      	// 返回错误信息
                      	return Promise.reject(error)
                      })
                      
                      5、同时多个请求返回401,需要刷新token

                      第一个refreshToken接口还没返回,后面的请求又过来了,防止同时多次调用刷新token接口,先把后面这些请求放在一个数组里面,等到refreshToken接口成功后,我们再逐个重试数组里面的请求。

                      响应拦截器处理,同时多个请求返回401,需要刷新token

                      • 这是使用了requestList存放请求队列
                        // 变量isRefreshing
                        let isRefreshing = false
                        // 后续的请求队列
                        let requestList = []
                        service.interceptors.response.use(async (response) => {
                        	let res = response.data
                        	// 为了演示,这里仅展示处理状态码为401的情况
                        	if (res.code == '401') {
                        		// 控制是否在刷新token的状态
                        		if (!isRefreshing) {
                        			// 修改isRefreshing状态
                        			isRefreshing = true
                        			// 这里是获取新token的接口,方法在这里省略了。
                        			const result = await refreshToken()
                        			// 获取成功
                        			if (result && result.data) {
                        				// 新token
                        				let newToken = result.data
                        				// 保存新的accessToken
                        				localStorage.setItem('accessToken', newToken)
                        				// 替换新accessToken
                        				response.config.headers.token = newToken
                        				// token 刷新后将数组里的请求队列方法重新执行
                        				requestList.forEach((cb) => cb(newToken))
                        				// 重新请求完清空
                        				requestList = []
                        				// 继续未完成的请求
                        				const resp = await service.request(response.config)
                        				// 重置状态
                        				isRefreshing = false
                        				// 返回请求结果
                        				return resp
                        			} else {
                        				// 清除token
                        				localStorage.clear()
                        				// 重置状态
                        				isRefreshing = false
                        				// 跳转到登录页
                        				router.replace('/login')
                        			}
                        		} else {
                        			// 后面的请求走这里排队
                        			// 返回未执行 resolve 的 Promise
                        			return new Promise(resolve => {
                        				// 用函数形式将 resolve 存入,等待获取新token后再执行
                        				requestList.push(newToken => {
                        					response.config.headers.token = newToken
                        					resolve(service(response.config))
                        				})
                        			})
                        		}
                        	}
                        	return res
                        }, error => {
                        	// 返回错误信息
                        	return Promise.reject(error)
                        })
                        

                        八、总结

                        基本的思路是这样的,你也可以根据自己的业务需要,自己修改。

                        • 比如抽离上面的方法或逻辑,单独封装。

                        • 你也可以添加接口失败重连的逻辑。

                        • 你也可以使用数据加密传输,例如sm4等。

                          九、代码上传

                          这里我做了个简单的demo演示,可以到顶部下载。

                          1、vue项目部分

                          下载依赖

                          npm i
                          

                          启动项目

                          npm run serve
                          

                          启动后项目地址为:http://localhost:8080

                          1、先进入登录页面,点击’登录’按钮,请求’login’接口,接口返回accessToken、RefreshToken。

                          2、跳转到首页,正常携带token请求’getTableList’接口,接口返回列表数据。

                          3、下面做了3个按钮来测试接口返回401的状态。

                          • 点击1个按钮,用来测试’test1’接口返回401状态,响应拦截器做了处理自动请求’refreshToken’。

                          • 连续点击2个或3个按钮,用来测试防止重复请求refreshToken接口。第一个refreshToken接口还没返回,后面的请求又过来了,防止同时多次调用刷新token接口,先把后面这些请求放在一个数组里面,等到refreshToken接口成功后,我们再逐个重试数组里面的请求。

                            2、nodejs服务部分

                            做了个简单的nodejs服务,里面写了对应的测试接口。

                            下载依赖

                            npm i
                            

                            启动服务

                            npm run serve
                            

                            启动后服务为:http://localhost:3000

                            • 为了演示接口401状态,test1、test2、test3接口第1次请求返回401,后面就返回200正常状态。

                            • 接口getTableList为普通接口,正常返回数据。

                            • 接口login、refreshToken里面的token为模拟的JWT格式的token。

                            • 接口refreshToken为了演示,里面做了延迟5s返回数据。

                              十、效果展示

                              登录页面

                              在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新 第2张

                              首页

                              在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新 第3张

                              这里是同时点击3个按钮,第1个test1请求返回401后,去请求refreshToken,因为这个接口做了5s延迟,不会立即返回结果,后面test2,test3也都返回401,因为做了判断,所有不会重复去请求refreshToken。等到refreshToken返回结果后,会自动去重新请求请求队列里面的接口。后面陆续返回了test1、test2、test3正常的结果。

                              在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新 第4张


    免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

    目录[+]