• 无感刷新token


    无感刷新

    无感刷新Token技术是一种用于实现持久登录体验的关键技术,它通过在用户登录后自动刷新Token,以延长用户的登录状态,避免频繁要求用户重新登录。

    实现

    使用access_token(短效token)和refresh_token(长效token),当请求拦截判断属于access_token过期时,使用refresh_token获取一组新的token,并且将所有无效请求入队列,等拿到有效token时依次出队列重新请求。

    后端

    使用nestjs写了几个测试用的接口

    controller.ts

    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 失效,请重新登录');
          }
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    其中login生成 access_token 和 refresh_token

    前端请求封装

    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;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
  • 相关阅读:
    基于NET蛋糕销售网站 毕业设计-附源码090918
    nodejs DEBUG=*
    mac版Idea快捷键
    计算机网络——物理层-信道的极限容量(奈奎斯特公式、香农公式)
    java程序员必会-远程debug
    Modelsim的仿真之路(结束篇之波形比较)
    【python基础】函数-值传递
    地震勘探原理部分问题解答
    【项目管理】PM vs PMO 18点区别
    【从零开始学习 SystemVerilog】6.5、SystemVerilog 接口—— Clocking Blocks(上)
  • 原文地址:https://blog.csdn.net/lzl980111/article/details/134310793