Token无感知刷新

前言

前一段时间要做一个小项目,项目的模板也有,从原来的项目上重新拉一个分支然后创建一个新的仓库,原来的项目是没有做token无感知刷新的,而是token过期重新跳转登录页进行登录。这样会导致有时候在进行业务操作时,突然跳转到登录页,然后要从头操作。所以就找后端说了一下使用双token进行token无感知刷新,后端大哥也很配合。

可能有人会说把token时间设置长一点不就行了,这样是解决的眼前的问题,当是不安全,会导致token被滥用

完整代码在后面。

正文

token无感刷新的原理也简单,使用双token,分别为accessTokenrefreshToken,正常都是携带accessToken进行验证。当返回状态码表示token过期时,再携带refreshToken重新获取accessToken,然后重新携带accessToken发起请求。

实现效果

  1. accessToken没有过期
  1. accessToken过期但是refreshToken没有过期
  1. accessTokenrefreshToken都过期

Node后端环境搭建

这里的核心就是使用中间件进行token验证,并将无tokentoken过期的状态码设置为401。

js 复制代码
let Koa = require('koa');
let app = new Koa();
let fs = require('fs')
let Router = require('koa-router')();
const cors = require("@koa/cors")
app.use(cors())
const jwt = require('jsonwebtoken');

//静态web服务//到public目录下找,返回资源链接
const path = require('path')
let koaStatic = require('koa-static');


app.use(koaStatic(path.join(__dirname, 'public')))
var bodyParser = require('koa-bodyparser');
app.use(bodyParser());

//秘钥
const tokenSecret = 'aaaaaaaaa'

/** 生成token*/
const createToken = () => {
  let accessRule = {
    iss: "lzt",
    sub: "lzt",
    aud: 'user',
    exp: Math.floor(Date.now() / 1000) + 10, // 10秒后过期
  }


  let refreshRule = {
    iss: "lzt",
    sub: "lzt",
    aud: 'user',
    isRefresh: true,
    exp: Math.floor(Date.now() / 1000) + 30, // 30秒后过期
  }
  let accessToken = jwt.sign(accessRule, tokenSecret);
  let refreshToken = jwt.sign(refreshRule, tokenSecret);

  return {
    accessToken,
    refreshToken
  }
}

//使用中间件验证token
const verifyToken = (token) => {
  return new Promise((resolve, reject) => {
    jwt.verify(token, tokenSecret, (err, decode) => {
      if (err) {
        reject(err)
      } else {
        resolve(decode)
      }
    })
  })
}
app.use(async (ctx, next) => { 
  if (ctx.url === '/login') {
    await next()
  } else {
    let token = ctx.get('Authorization')
    if (token === '') {
      //设置状态码
      ctx.status = 401

      ctx.body = {
        code: 401,
        msg: '没有token'
      }
    } else {
      try {
        let decode = await verifyToken(token)
        console.log(decode);
        await next()
      } catch (error) {
        ctx.status = 401
        ctx.body = {
          code: 401,
          msg: 'token过期'
        }
      }
    }
  }
})


Router.get('/test', async (ctx) => { 
  console.log(ctx);
  ctx.body = {
    code: 200,
    msg: '测试'
  }
})

Router.post('/refreshToken', async (ctx) => { 
  let tokenObj = createToken()
  ctx.body = {
    code: 200,
    data: {
      accessToken: tokenObj.accessToken,
    }
  }
})

//登录接口
Router.get('/login', async (ctx) => {
  let tokenObj = createToken()
  ctx.body = {
    code: 200,
    data: {
      tokenObj,
    }
  }
})


app
  .use(Router.routes())   	//启动路由
  .use(Router.allowedMethods());
app.listen(3000);

前端代码

主要是对请求进行响应拦截和请求拦截

  • 请求拦截:判断请求路径,为请求头添加对应token
  • 响应拦截:对响应的数据进行统一处理
ts 复制代码
import axios from "axios";
import { AxiosRetry } from './axiosClass'

axios.defaults.baseURL='http://127.0.0.1:3000'

// 添加请求拦截器
axios.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    // 在请求头中添加token
    config.headers.Authorization = localStorage.getItem("accessToken");
    if (config.url == "/refreshToken") {
      config.headers.Authorization = localStorage.getItem("refreshToken");
    }
    return config;
  },
);


/**先到拦截器*/
axios.interceptors.response.use(res => {
  if (res.status != 200) {
    return Promise.reject(res.data);
  }
  return Promise.resolve(res.data)
});

const axiosRetry = new AxiosRetry({
  onSuccess: (res) => {
    let { accessToken } = res.data
    localStorage.setItem("accessToken", accessToken);
  },
  onError: () => {
    console.log('refreshToken过期,需要重新登录');
  },
});


export const request = (url: string) => {
  return axiosRetry.requestWrapper(() => {
    return axios({
      method: "get",
      url: `${url}`,
    })
  });
}

下面是token过期到重新发起请求的主要代码。主要是对token过期状态401进行判断并进行相应的处理

ts 复制代码
import { Axios } from 'axios';
import axios from 'axios';

export class AxiosRetry {
  //相当于一个锁
  private fetchNewTokenPromise: Promise<any> | null = null;
  private onSuccess: (res: any) => any;
  private onError: () => any;

  constructor({
    onSuccess,
    onError,
  }: {
    onSuccess: (res: any) => any;
    onError: () => any;
    }) {
    this.onSuccess = onSuccess;
    this.onError = onError;
  }

  /** 发送请求*/
  requestWrapper<T>(request: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      /** 将请求接口的函数保存*/
      const requestFn = request;
      return request().then((res) => {
        //拦截器处理后的数据
        resolve(res);
      }).catch(err => {
        //token过期或者没有token    
        if (err.response.status === 401) {
          if (!this.fetchNewTokenPromise) {
            this.fetchNewTokenPromise = this.fetchNewToken();
          }
          this.fetchNewTokenPromise.then(() => {
            return requestFn();
          }).then((res) => {
            resolve(res);
            this.fetchNewTokenPromise = null;
          }).catch((err) => {
            reject(err);
            this.fetchNewTokenPromise = null;
          });
        } else {
          reject(err);
        }
      });
    });
  }

  // 获取新的token
  fetchNewToken() {
    return axios({
      method: "post",
      url: `/refreshToken`,
    }).then((res) => {
      this.onSuccess(res)
    }).catch((err) => {
      this.onError();
      //表示refreshToken过期,需要重新登录
      if (err.response.status === 401) {
        return Promise.reject(
          new Error("refreshToken过期,需要重新登录")
        );
      }
      //表示发生了其他错误
      else {
        return Promise.reject(err); 
      }
    })
  }
}

结语

完整代码地址 : function-realization: 实现一些有趣的功能 (gitee.com)

感兴趣的可以去试试。

相关推荐
我是伪码农2 分钟前
Vue 2.3
前端·javascript·vue.js
夜郎king27 分钟前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳35 分钟前
JavaScript 的宏任务和微任务
javascript
夏幻灵1 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星1 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_2 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝2 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions2 小时前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发2 小时前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
程序员猫哥_2 小时前
HTML 生成网页工具推荐:从手写代码到 AI 自动生成网页的进化路径
前端·人工智能·html