前端双token无感刷新详解

前端双token无感刷新详解

前端双token无感刷新详解_第1张图片

前端双token无感刷新详解

  • 前端双token无感刷新详解
    • 1.双token的使用场景
    • 2.什么是token
    • 3.如何无感刷新token(图文+代码)

1.双token的使用场景

众所周知,Token作为用户获取受保护资源的凭证,必须设置一个过期时间,否则一次登录便可永久使用,认证功能就失去了意义。但是矛盾在于:过期时间设置得太长,用户数据的安全性将大打折扣;过期时间设置得太短,用户就必须每隔一段时间重新登录,以获取新的凭证,这会极大挫伤用户的积极性。

1.假如你正在访问某个平台,沉浸式的使用了一小时后却突然弹出一个token过期,重定向到了登录页…

2.假如你正在某网站里填写你的个人信息或者内容繁多的表单选项,填到一半的时候token过期了…提示需要重新登录,结果登录回来之后又要重新填写表单信息…T~T

3.假如你正在激情的抢票、蹲卡点秒杀活动!结果刚要抢购却因token过期被重定向到登录页…

前端双token无感刷新详解_第2张图片

是不是代入起来就已经很头疼了?那如何避免这种情况发生呢,接下来我们就引入今天的主角——无感刷新token

2.什么是token

在介绍双token无感刷新之前,我们先简单介绍一下什么是token吧!

token是一种用户标识,表示用户身份,类似于我们的身份证件。
Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌。

3.如何无感刷新token(图文+代码)

token过期了之后,我们应该如何做到让用户无感知的自动刷新token呢?

目前比较常见的有三种方法:

  • 写个定时器,时间一到就刷新getToken接口来获取新token(极其不推荐):消耗性能,不断的去发送网络请求,不建议这种做法
  • 后端返回一个过期时间,前端每次发送请求都去判断token是否过期,如果过期就调用getToken接口来获取新token(不推荐):需要后端额外提供一个过期时间的字段;使用了本地时间判断,若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败。
  • 在请求响应拦截器中拦截,判断token 返回过期后,调用刷新token接口。(推荐⭐⭐⭐):无性能损耗

在登录成功后,后端会返回两个Token,一个是Access Token(即短token),一个是Refresh Token(即长token)。前端需要将这两个Token保存到本地存储中,例如localStoragesessionStorage中,以便在需要时使用。

当需要访问API时,前端将从本地存储中获取Access Token,并将其放入请求头中发送到后端。如果Access Token过期了,后端会返回一个错误响应,并提示前端进行刷新Token的操作。

前端可以使用下面的代码实现刷新Token的操作:

import axios from "axios";
import { getToken, setToken, setRefreshToken, getRefreshToken } from "./token";
import { ElMessage } from "element-plus";
import router from "@/router";
import { refreshToken } from "./refreshtoken";
const service = axios.create({
  baseURL: "http://localhost:8087",
  headers: {
    Authorization: `Bearer ${getToken()}`,
  },
});
// 添加请求拦截器
service.interceptors.request.use(
  function (config) {
    if (config.url === "/refresh_token") {
      config.headers["Authorization"] = `Bearer ${getRefreshToken()}`;
    }
    // 在发送请求之前做些什么
    return config;
  },
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
service.interceptors.response.use(async (res) => {
  // 1.第一次登录了以后 后台在header里面返回了短token 那么要先接收token存储到localstorage里面
  if (res.headers.authorization) {
    const token = res.headers.authorization.replace("Bearer ", "");
    // 设置短token
    setToken(token);
    service.defaults.headers.Authorization = `Bearer ${token}`;
  }
  // 2. 第一次登录了以后 后台在header里面返回了长token 那么要先接受长token 存储到localstorage里面
  if (res.headers.refreshtoken) {
    const refreshtoken = res.headers.refreshtoken.replace("Bearer ", "");
    // 设置长token
    setRefreshToken(refreshtoken);
  }
  // 3 假设当后端返回401的时候 代表token失效 时间到了 这个时候正常处理就是跳到登录页面
  // 但是在实际的业务场景的时候 用户体验非常不好

  if (res.data.code === 401) {
    // ElMessage({
    //   message: "token失效",
    //   type: "warning",
    // });
    // router.push("/login");
    // 这个地方就是短token失效了 提交表单 不跳到登录页面 因为这样用户体验很不好
    // 请求刚刚定义的一个接口
    // 这个时候 提交表单的接口 还没提交 停在这里了 现在要干啥?请求
    // 干啥了? 请求了changtoken携带过去 刷新token的接口
    const success = await refreshToken();
    if (success) {
      res.config.headers.Authorization = `Bearer ${getToken()}`;
      const result = await service.request(res.config);
      return result;
    }
  }

  return res.data;
});

function request(options) {
  options.method = options.method || "get";
  // 关于get请求参数的调整
  if (options.method.toLowerCase() === "get") {
    options.params = options.data;
  }

  return service(options);
}

export default request;

对应的token.jsrefreshtoken.js代码:

模拟网卡的时候,响应数据还没返回就重复刷新的情况:(本质就是借助Promise。将请求存进队列中后,同时返回一个Promise,让这个Promise一直处于Pending状态(即不调用resolve),此时这个请求就会一直等啊等,只要我们不执行resolve,这个请求就会一直在等待。当刷新请求的接口返回来后,我们再调用resolve,逐个重试。)

前端双token无感刷新详解_第3张图片

前端双token无感刷新详解_第4张图片

token存进localstorage里,同时为了防止多次刷新

使用 TokenRefresh Token 的时序图如下:

1)登录

前端双token无感刷新详解_第5张图片

2)业务请求
前端双token无感刷新详解_第6张图片

3)Token 过期,刷新 Token
前端双token无感刷新详解_第7张图片

现在,我们就已经成功实现了长短token的无感刷新啦!

一定要理解文章开头这张图的流程哦


前端双token无感刷新详解_第8张图片

你可能感兴趣的:(前端,状态模式,javascript,面试,java,html)