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)

感兴趣的可以去试试。

相关推荐
2401_8791036830 分钟前
24.11.10 css
前端·css
ComPDFKit1 小时前
使用 PDF API 合并 PDF 文件
前端·javascript·macos
yqcoder2 小时前
react 中 memo 模块作用
前端·javascript·react.js
谈谈叭2 小时前
Javascript中的深浅拷贝以及实现方法
开发语言·javascript·ecmascript
优雅永不过时·2 小时前
Three.js 原生 实现 react-three-fiber drei 的 磨砂反射的效果
前端·javascript·react.js·webgl·threejs·three
爱编程的鱼3 小时前
javascript用来干嘛的?赋予网站灵魂的语言
开发语言·javascript·ecmascript
神夜大侠5 小时前
VUE 实现公告无缝循环滚动
前端·javascript·vue.js
明辉光焱5 小时前
【Electron】Electron Forge如何支持Element plus?
前端·javascript·vue.js·electron·node.js
柯南二号5 小时前
HarmonyOS ArkTS 下拉列表组件
前端·javascript·数据库·harmonyos·arkts
wyy72935 小时前
v-html 富文本中图片使用element-ui image-viewer组件实现预览,并且阻止滚动条
前端·ui·html