实现思路
- 用户登录向服务端发送账号密码信息,登录失败返回客户端重新填写并发送用户信息;登录成功服务端生成accessToken和refreshToken并返给客户端,客户端将token存本地。
- 当客户端想服务端发起请求时,在请求头携带accessToken发送给服务端,服务端验证accessToken是否过期,若未过期则正常请求数据;若过期,服务端通过code码将accessToken失效信息返回给客户端。
- 客户端在响应拦截器中添加拦截判断,若返回accessToken失效信息,则在请求头携带refreshToken ,重新发起请求,获取新的accessToken。
- 服务端验证 refreshToken 是否失效。若未过期,则重新生成accessToken返给客户端;若过期,服务端通过code码将refreshToken 失效信息返回给客户端。
- 客户端在响应拦截器中添加拦截判断,若返回refreshToken 失效信息,则提示用户需要重新登录,获取新的双token。
code码?
“code 码” 通常指的是服务端向客户端返回的特定状态码或错误码,用于表示不同的操作或状态。这个 “code 码” 可以是一个数字或字符串,其具体含义会根据服务端和客户端之间的协议或约定而定。通常,不同的 “code 码” 对应不同的操作或情况,以便客户端能够根据这些 “code 码” 来执行不同的处理逻辑。
通常,开发者会定义一套自己的状态码或错误码,以便更好地管理客户端与服务端之间的通信和处理不同的情况。例如:
- 200:表示成功,请求已成功处理。
- 401:表示未授权,通常用于 AccessToken 失效的情况,客户端需要重新登-录或刷新 Token。
- 403:表示禁止访问,通常用于 RefreshToken 失效的情况,客户端需要重新登录获取新的 Token。
- 500:表示服务器内部错误,请求未成功处理。
相应拦截器
相应拦截器(Response Interceptor)是前端开发中进场用到的一种机制,用于拦截和处理从服务器返回的HTTP响应。相应拦截器通常用于以下几个主要目的:
-
处理响应数据: 响应拦截器允许你在数据传递到应用程序之前对响应数据进行处理。这包括解析响应数据、格式化数据或对数据进行任何必要的转换。
-
全局错误处理: 响应拦截器可以用来全局处理错误。如果服务器返回了一个错误响应(例如 404 Not Found 或 500 Internal Server Error),响应拦截器可以捕获这些错误并采取适当的措施,如显示错误消息或执行其他操作。
-
Token 刷新: 在前文提到的身份验证和令牌管理方案中,响应拦截器可以用于检查令牌的有效性,如果令牌过期,它可以自动请求新的令牌,并重新发送之前的请求。
-
数据缓存: 响应拦截器还可以用于缓存响应数据,以提高应用程序的性能和响应速度
响应拦截器通常与请求拦截器一起使用,前者用于处理服务器的响应,后者用于在发送请求之前对请求进行处理,例如添加身份验证令牌或请求头。
相应拦截器代码实例:
import axios from 'axios';
const instance = axios.create({
baseURL: 'https://api.example.com',
});
instance.interceptors.response.use(
(response) => {
const responseData = response.data;
return responseData;
},
(error) => {
return Promise.reject(error);
}
);
export default instance;
为什么用双token登录
-
一句话概括就是提高系统安全性、降低令牌滥用的风险、改善用户体验
提高安全性: 双令牌机制将身份验证令牌(AccessToken)和令牌刷新令牌(RefreshToken)分开存储和使用。AccessToken通常具有较短的有效期,而RefreshToken具有更长的有效期。这可以降低潜在攻击者利用AccessToken的机会,因为AccessToken过期后需要使用RefreshToken来获取新的AccessToken。这提高了系统的安全性。
-
减少令牌滥用的风险: AccessToken通常用于访问资源,而RefreshToken用于获取新的AccessToken。将这两个令牌分开有助于降低AccessToken被滥用的风险。即使AccessToken被泄露,攻击者仍然需要RefreshToken才能获取新的AccessToken,而RefreshToken通常需要更高的安全性保护。
-
减少频繁的登录需求: AccessToken的较短有效期意味着用户需要更频繁地重新登录。但使用RefreshToken,用户可以在不频繁输入用户名和密码的情况下获取新的AccessToken,从而提高了用户体验。
-
支持长时间登录: RefreshToken的较长有效期允许用户保持登录状态,而无需频繁重新登录。这对于应用程序需要长时间会话的情况(如电子邮件或社交媒体应用)非常有用。
-
粒度控制: 双令牌机制允许应用程序对AccessToken和RefreshToken的有效期进行不同的配置。AccessToken可以设置为较短的有效期,以提高安全性,而RefreshToken可以设置为较长的有效期,以提高用户体验。
-
降低对资源服务器的负载: 由于AccessToken的有效期较短,资源服务器(通常是后端服务器)需要验证AccessToken的有效性。使用RefreshToken可以减少对资源服务器的请求次数,因为AccessToken过期后不需要每次都请求资源服务器进行验证,只需使用RefreshToken获取新的AccessToken即可。
双token
Access Token:用于获取访问资源或执行操作的授权,有效期短。客户端发送请求时,在请求头携带此accessToken。
Refresh Token:用来验证用户的身份,刷新accessToken,有效期长。当accessToken过期时,向服务端传递refreshToken来刷新accessToken。

短期令牌(Short-Term Token):
-
有效期较短: 短期令牌的特点是其有效期相对较短,通常只有几分钟到数小时不等。
-
用途: 短期令牌通常用于执行短期任务,如一次性访问资源或进行敏感操作。它们通常用于一次性的身份验证和授权,随后会自动失效。
-
安全性: 由于有效期短暂,短期令牌通常具有较高的安全性,因为它们很快就会自动过期,从而降低了滥用的风险。
示例: 一次性登录令牌、短期会话令牌等。
长期令牌(Long-Term Token):
-
有效期较长: 长期令牌的特点是其有效期相对较长,通常可以维持几天、几周甚至更长的时间。
-
用途: 长期令牌通常用于维持用户的持久登录状态,以便在较长时间内免除用户频繁登录的需求。它们通常用于记住登录状态,例如保持登录的用户会话。
-
安全性: 由于有效期较长,长期令牌可能会具有一定的安全风险。因此,需要采取额外的安全措施来保护长期令牌,如定期刷新令牌或将其存储在安全的地方。
示例: 持久登录令牌、刷新令牌等。
代码实现双token
setToken.js
export function setToken (tokenKey, token){
return localStorage.setItem(tokenKey,token)
}
export function getToken (tokenKey){
return localStorage.getItem(tokenKey)
}
export function removeToken(tokenKey){
return localStorage.removeItem(tokenKey)
}
request.js
import axios from 'axios';
import router from './router';
import { setToken, getToken, removeToken } from './setToken.js';
const request = axios.create({
baseURL: 'http://****',
timeout: 10000,
contentType: 'application/json',
});
let refreshToken = getToken('refreshToken') || "";
let isrefreshToken = false;
if (!getToken("refreshToken")) {
isrefreshToken = false;
if (!getToken('refreshToken')) {
isrefreshToken = true;
}
}
request.interceptors.request.use((config) => {
let token = getToken('accessToken');
if (token) {
if (!isrefreshToken) {
config.headers['x-token'] = getToken('accessToken') || '';
}
}
if (refreshToken) {
if (isrefreshToken) {
config.headers['x-token'] = getToken('refreshToken');
}
}
return config;
}),
(error) => {
return Promise.reject(error);
};
request.interceptors.response.use((response) => {
console.log('响应状态码', response.data.code);
let code = response.data.code;
if (!refreshToken && getToken('refreshToken') != null) {
refreshToken = getToken('refreshToken');
return request(response.config);
}
if (code == 401 || code == 1021) {
refreshToken = getToken('refreshToken');
isrefreshToken = true;
return request(response.config);
}
if (code == 1024) {
setToken('accessToken', response.data.data);
isrefreshToken = false;
return request(response.config);
} else if (code == 1023) {
removeToken('refreshToken');
removeToken('accessToken');
router.push('/login');
isrefreshToken = true;
alert('登录已超期,请重新登录');
}
return response;
}),
(error) => {
return Promise.reject(error);
};
export default request;