无感刷新
无感刷新Token技术是一种用于实现持久登录体验的关键技术,它通过在用户登录后自动刷新Token,以延长用户的登录状态,避免频繁要求用户重新登录。
实现
使用access_token(短效token)和refresh_token(长效token),当请求拦截判断属于access_token过期时,使用refresh_token获取一组新的token,并且将所有无效请求入队列,等拿到有效token时依次出队列重新请求。
后端
使用nestjs写了几个测试用的接口
controller.ts
javascript
import { Body, Query, Controller,Request, Get, Post, BadRequestException, Inject, Req, UnauthorizedException } from '@nestjs/common';
import { AppService } from './app.service';
import { UserDto } from './dto/user.dto';
import { JwtService } from "@nestjs/jwt"
const users = [
{ username:"admin", password:"admin" },
{ username:"zhangsan", password:"123" },
]
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Inject(JwtService)
private jwtService:JwtService;
@Get()
getHello(): string {
return this.appService.getHello();
}
@Post('login')
login(@Body() userDto : UserDto){
const user = users.find(item => item.username === userDto.username);
if(!user) {
throw new BadRequestException('用户不存在');
}
if(user.password !== userDto.password) {
throw new BadRequestException("密码错误");
}
const accessToken = this.jwtService.sign({
username:user.username,
}, {
expiresIn: '0.001h'
})
const refreshToken = this.jwtService.sign({
username:user.username,
}, {
expiresIn: '7d'
})
return {
userInfo: {
username: user.username,
},
accessToken: accessToken,
refreshToken: refreshToken
};
}
@Get("aaa")
aaa(@Req() req: Request){
const authorization = req.headers['authorization'];
if(!authorization){
throw new UnauthorizedException("用户未登录")
}
try {
const token = authorization;
const data = this.jwtService.verify(token);
console.log(data);
return 'aaa'
} catch (error) {
throw new UnauthorizedException("token失效,请重新登录")
}
}
@Get("bbb")
bbb(@Req() req: Request){
const authorization = req.headers['authorization'];
if(!authorization){
throw new UnauthorizedException("用户未登录")
}
try {
const token = authorization;
const data = this.jwtService.verify(token);
console.log(data);
return 'bbb'
} catch (error) {
throw new UnauthorizedException("token失效,请重新登录")
}
}
@Get('refresh')
refresh(@Query('token') token: string) {
try{
const data = this.jwtService.verify(token);
const user = users.find(item => item.username === data.username);
const accessToken = this.jwtService.sign({
username: user.username,
}, {
expiresIn: '0.001h'
});
const refreshToken = this.jwtService.sign({
username: user.username
}, {
expiresIn: '7d'
})
return {
accessToken,
refreshToken
};
} catch(e) {
throw new UnauthorizedException('token 失效,请重新登录');
}
}
}
其中login生成 access_token 和 refresh_token
前端请求封装
javascript
import axios from 'axios';
import { userStore } from "@/store/user.js"
const store = userStore();
class RequestQueue {
constructor() {
this.queue = [];
this.isRefresh = false;
}
// 入队
enqueue(value) {
return this.queue.push(value);
}
// 出队
dequeue() {
return this.queue.shift();
}
// 取队头元素
peek() {
return this.queue[0];
}
// 判断队列是否为空
isEmpty() {
return this.queue.length === 0;
}
// 取队列有多少个元素
size() {
return this.queue.length;
}
// 清空队列
clear() {
this.queue = [];
}
setIsRefresh(isRefresh){
return this.isRefresh = isRefresh
}
}
//1. 创建axios对象
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000
});
//2. 请求拦截器
service.interceptors.request.use(config => {
console.log('store拦截信息',store)
if(store.accessToken){
config.headers.Authorization = store.accessToken;
}
return config;
}, error => {
Promise.reject(error);
});
let requests = new RequestQueue; // 请求队列
//3. 响应拦截器
service.interceptors.response.use(response => {
//判断code码
return response.data;
},error => {
if(error.response.status == 401){
// token过期处理
// 请求入队
requests.enqueue(error.response.config);
if(!requests.isRefresh){
requests.setIsRefresh(true)
store.doRefreshToken().then(res=>{
while(!requests.isEmpty()){
let config = requests.dequeue()
config.Authorization = store.accessToken
service.request(config)
}
requests.setIsRefresh(false)
});
}
} else {
// token请求没有过期
return new Promise((resolve,reject)=>{
reject(error)
});
}
});
export default service;