Nuxt.js框架前端token处理

1,281 阅读3分钟

token 失效方案

  1. 在请求发起前拦截:判断 token 的有效时间是否已经过期,若已过期,则将请求挂起,先刷新 token 后再继续请求
  • 优点:节省请求,省流量
  • 缺点:需要后端额外提供一个 token 过期时间的字段;若使用了本地时间判断,当本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败
  • 方法:使用 axios.interceptors.request.use()处理请求前拦截
  1. 在响应返回后拦截:先发起请求,接口返回过期后,先刷新 token,再进行一次重试
  • 优点:不需额外的 token 过期字段,不需判断时间
  • 缺点:会消耗多一次请求,耗流量
  • 方法:使用 axios.interceptors.response.use()处理请求后拦截
  1. 如何防止多次刷新 token
  • 问题:如果 refreshToken 接口还没返回,此时再有一个过期的请求进来,就会再一次执行 refreshToken,这就会导致多次执行刷新 token 的接口
  • 解决:在 封装的请求文件request.js 中用一个 flag 来标记当前是否正在刷新 token 的状态,如果正在刷新则不再调用刷新 token 的接口
  1. 同时发起两个或以上的请求时,其他接口如何重试
  • 问题:多个接口几乎同时发起和返回,第一个接口会进入刷新 token 后重试的流程,而之后的接口需要先存起来,然后等刷新 token 后再重试
  • 当第二个过期的请求进来,token 正在刷新,先将这个请求存到一个数组队列中,让其处于等待中,一直等到刷新 token 后再逐个重试清空请求队列
  • 借助 Promise,让请求处于等待中
    • 将请求存进队列中后,同时返回一个Promise,让这个 Promise 一直处于 Pending 状态,只要不执行 resolve,这个请求就会一直在等待。当刷新请求的接口返回后,再调用 resolve,逐个重试。

token 失效处理

在 layouts/default.vue 中的 mounted 阶段处理,重新刷新页面或者打开 APP 初次加载时会执行一次,内部路由切换时,不会再次执行,不会太影响页面性能。

  • 是否存在 token
    • 存在时请求 is-login 接口,通过接口返回判断 token 是否失效
      • 未失效,走正常流程
      • 失效时,通过 uuid 请求无身份信息的 token,并且清除 localStorage 缓存信息
    • 不存在时,通过 uuid 获取无身份信息的 token
// layouts/default.vue
<template>
  <div>
    <header-component />
    <nuxt />
    <menu-component />
  </div>
</template>

<script>
  export default {
    data() {
      return {};
    },
    mounted() {
      // 设置token
      this.setToken(() => {
        this.showContent = true;
      });
    },
    methods: {
      setToken(callback) {
        const token = this.$getCookie("token");
        if (token) {
          this.$axios.post("/api/is-login").then((res) => {
            if (res.code === 2) {
              // token失效
              this.getUuid(async (uuid) => {
                const res = await this.getToken(uuid);
                if (res.code === 0) {
                  this.$setStorage("loginState", false);
                  this.$setCookie("token", res.response.token, 365);
                  callback();
                }
              });
            } else {
              // token正常
              callback();
            }
          });
        } else {
          // 没有token
          this.getUuid(async (uuid) => {
            const res = await this.getToken(uuid);
            if (res.code === 0) {
              this.$setCookie("token", res.response.token, 365);
              callback();
            }
          });
        }
      },
      // 获取uuid
      getUuid(callback) {
        const appUuid = this.$getCookie("uuid");
        if (appUuid) {
          callback(appUuid);
        } else {
          const Fingerprint2 = require("fingerprintjs2");
          Fingerprint2.getV18({}, (result) => {
            this.$setCookie("uuid", result, 365);
            callback(result);
          });
        }
      },
    },
  };
</script>

<style lang="scss" scoped></style>

权限控制中间件

middleware/authorities.js文件:权限控制中间件

  1. 登陆页和活动页不需要登陆,可以直接进
  2. 需要强制登录的页面,判断是否存在 token,不存在时跳转到首页,存在时正常登录
// 获取客户端token
function getToken() {
  return Cookies.get(TokenKey);
}

// 获取服务端token
function getTokenInServer(req) {
  let serviceCookie = "";
  if (req && req.ctx && req.ctx.cookies) {
    serviceCookie = req.ctx.cookies.get(TokenKey);
  }
  return serviceCookie;
}

export default function ({ req, route, redirect, $axios }) {
  const isLogin = route.name && route.name.indexOf("login") === 0; // 登录页不需验证
  const isActivities = route.name && route.name.indexOf("activities") === 0; // 活动页不强制登录
  const path = route.fullPath.split("?")[1]
    ? "?" + route.fullPath.split("?")[1]
    : "";
  const redirectURL = "/" + path;
  const token = process.server ? getTokenInServer(req) : getToken();
  // 注意要区分客户端和服务端,参数和处理会不一样
  if (process.server) {
    // 服务端渲染
    if (!isLogin && !isActivities && !token) {
      return redirect(redirectURL);
    }
    $axios.defaults.headers.common.Cookie = req.headers.cookie;
    $axios.defaults.headers.common["user-agent"] = req.headers["user-agent"];
  }

  if (process.client) {
    // 客户端渲染
    if (!isLogin && !isActivities && !token) {
      return redirect(redirectURL);
    }
  }
}