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

一、需求背景

对于一些需要记录用户行为的系统,在进行网络请求的时候都会要求传递一下登录的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访问受保护资源。

在这里插入图片描述

六、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返回数据。
十、效果展示

登录页面
在这里插入图片描述

首页
在这里插入图片描述

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

我的博客只写前端博文,点击我去看更多喜欢的前端博文,欢迎大家一起讨论学习!【https://blog.csdn.net/qq_29101285?spm=1011.2266.3001.5343】
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 请问您的问题是如何在Vue3使用axios拦截器来添加token吗?如果是这样的话,可以通过以下代码来实现: 首先,在main.js引入axios和设置拦截器: import axios from 'axios'; // 设置baseUrl axios.defaults.baseURL = 'http://localhost:8000/'; // 设置拦截器 axios.interceptors.request.use( config => { const token = localStorage.getItem('token'); if (token) { config.headers.authorization = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); } ); 然后,您可以在代码使用axios请求,并且该请求会自动带上token: // 发起一个有token的GET请求 axios.get('/api/user').then(response => { console.log(response); }).catch(error => { console.log(error); }); ### 回答2: 在Vue3使用axios拦截器来为每个HTTP请求设置token是一种常见实践。Axios拦截器可以帮助我们在请求发送之前或响应返回之前拦截,并在拦截器添加必要的数据,如token。为了在Vue3使用Axios拦截器为每个HTTP请求设置token,下面的内容将提供一些具体方法。 首先,我们需要安装AxiosVue3,安装方法为: ```vue3 # 安装vue3 npm i vue@next -S # 安装axios npm i axios -S ``` 然后,我们需要在Vue3应用创建一个Axios实例,代码如下: ```vue3 import axios from 'axios'; const api = axios.create({ baseURL: 'http://localhost:3000', }); export default api; ``` 在应用程序,我们可以在需要发送HTTP请求的组件导入并使用此api实例。 接下来,我们需要在Axios实例创建一个拦截器拦截器拦截HTTP请求并在其添加必要的headers。在这里,我们添加一个头部,它包含一个名为“Authorization”的令牌。代码如下: ```vue3 api.interceptors.request.use((config) => { config.headers.Authorization = 'Bearer ' + localStorage.getItem('token'); return config; }); ``` 通过使用这个拦截器,我们可以在每个HTTP请求自动添加一个名为“Authorization”的头部,它包含了从localStorage获取的令牌。 在这里,我们还要考虑到token可能会过期,因此我们还可以添加一个拦截器检查HTTP响应。如果响应包含401状态码,我们会强制用户重新登录以获取新的token。代码如下: ```vue3 api.interceptors.response.use( response => response, error => { if (error.response.status === 401) { localStorage.removeItem('token'); router.push('/login'); } return Promise.reject(error); }); ``` 在这个拦截器,我们首先检查响应是否包含401状态码。如果是,我们清除localStoragetoken并强制用户重新登录。 总之,Vue3使用Axios拦截器为每个HTTP请求设置token是一种常见实践,并且可以非常轻松地实现。借助Axios拦截器,我们可以自动为每个HTTP请求设置token,并在token过期时强制用户重新登录。 ### 回答3: 在Vue3使用Axios拦截器使用token进行身份验证是一种非常常见的做法。以下是详细的步骤: 第一步:安装AxiosVue-Router 安装AxiosVue-Router: ``` npm install axios vue-router --save ``` 该命令会将axiosvue-router安装到项目。 第二步:创建Axios实例和拦截器 创建一个名为axios.js的新文件,并在其添加以下代码: ```javascript import axios from "axios"; import router from "@/router"; const axiosInstance = axios.create({ baseURL: process.env.VUE_APP_API_BASE_URL }); axiosInstance.interceptors.request.use(config => { const token = localStorage.getItem("token"); if (token) { config.headers["Authorization"] = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); }); axiosInstance.interceptors.response.use(response => { return response; }, error => { if (error.response.status === 401) { router.push("/login"); } return Promise.reject(error); }); export default axiosInstance; ``` 在这里,我们创建了一个Axios实例,并在请求添加了一个拦截器来检查本地存储是否有token。如果存在,则将其添加到请求头。此外,我们还添加了一个拦截器来处理401未授权错误响应,并重定向到登录页面。 第三步:在Vue组件使用Axios实例 确保我们在请求数据时始终使用我们创建的Axios实例。为此,我们需要在组件导入该实例: ```javascript import axiosInstance from "@/axios"; ``` 我们现在可以像这样在组件使用Axios: ```javascript axiosInstance.get("/users") .then(response => { console.log(response); }) .catch(error => { console.log(error); }); ``` 现在,我们已经成功地设置了Axios拦截器使用token进行身份验证。我们可以在Axios实例添加其他拦截器,以满足特定的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值