处理用户Token

66 阅读3分钟

1. 关于Token

token:又叫令牌,服务器授权过的一个令牌。客户端每次想找服务器要数据,拿着这个令牌,服务器才会给, 我们需要把它存储到一个公共的位置,方便随时取用。

项目中的接口除了登录之外大多数都需要提供 token 才有访问权限。后端接口要求我们将 token 放到请求头 Header 中并以指定格式发送。

使用容器存储 Token 的思路:

  • 登录成功,将 Token 存储到 Vuex 容器中   1. 获取方便 2.响应式

  • 还需要把 Token 放到本地存储  1. 持久化

2. 使用请求拦截器统一添加

1.axios

import axios from "axios"
import store from "@/store"
const request = axios.create({
    baseURL:'http://geek.itheima.net'  //配置根地址
})
/ 实现有权限的接口设置
request.interceptors.request.use(function (config){
    // Do something before request is sent
    // config :本次请求的配置对象
    // config.headers.Authorization = store.state.user.token
    // 'Request failed with status code 405'  缺少判断,有token才设值请求头
    const {user}  = store.state
    if(user && user.token){
        //报错 401  massage :"token超时或者未传token"
        // `Bearer + ${user.token}` 错误拼接
        config.headers.Authorization =`Bearer ${user.token}`
    }
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
})

2.ajax

// ajaxPrefilter 方法会在发送ajax请求之前调用
// 在每个请求发送之前且被$.ajax()处理之前,处理自定义的Ajax选项或修改已存在的选项
    $.ajaxPrefilter(function(options){
        // console.log(options)
        options.url = "http://big-event-api-t.itheima.net" + options.url
        // 实现有权限的接口设置
        if(options.url.indexOf('/my/') !==-1){
            // console.log(123)
            options.headers= {
                Authorization: localStorage.getItem('token') || ''
            }
        }        
        // 控制用户访问权限 用户退出登录以后,不允许在进入后台主页面
        options.complete=function(res){
            // console.log(res)
            if(res.responseJSON.status ==1 && res.responseJSON.message=='身份认证失败!') {
                localStorage.removeItem('token')
                location.href="/login.html"
            }              
        }        
    })

3. 优化封装本地存储操作的模块

export const setItem = (key,value)=>{
    // 判断是否是引用数据类型
     if(typeof value === "object"){
        value = JSON.stringify(value)
        }
    window.localStorage.setItem(key,value)
}
export const getItem = key =>{
    const data =  window.localStorage.getItem(key)
    // 判断是否是基本数据类型, 基本数据类型反序列化JSON.parse()会报错,通过.catch捕获直接返回data
      try{        return JSON.parse(data)
    }catch(err){
        return data
    }}
export const removeItem = key =>{
    window.localStorage.removeItem(key)
}

4.关于Token过期问题

登录成功之后后端会返回两个 Token:

  • token:访问令牌,有效期2小时

  • refresh_token:刷新令牌,有效期14天,用于访问令牌过期之后重新获取新的访问令牌

概述:服务器生成token的过程中,会有两个时间,一个是token失效时间,一个是token刷新时间,刷新时间肯定比失效时间长,当用户的 token 过期时,你可以拿着过期的token去换取新的token,来保持用户的登陆状态,当然你这个过期token的过期时间必须在刷新时间之内,如果超出了刷新时间,那么返回的依旧是 401。

处理流程:

  1. 在axios的拦截器中加入token刷新逻辑

  2. 当用户token过期时,去向服务器请求新的 token

  3. 把旧的token替换为新的token

  4. 然后继续用户当前的请求

在请求的响应拦截器中统一处理 token 过期:

/**
 * 封装 axios 请求模块
 */
import axios from "axios";
import jsonBig from "json-bigint";
import store from "@/store";
import router from "@/router";

// axios.create 方法:复制一个 axios
const request = axios.create({
  baseURL: "http://ttapi.research.itcast.cn/" // 基础路径
});

/**
 * 配置处理后端返回数据中超出 js 安全整数范围问题
 */
request.defaults.transformResponse = [
  function(data) {
    try {
      return jsonBig.parse(data);
    } catch (err) {
      return {};
    }
  }
];

// 请求拦截器
request.interceptors.request.use(
  function(config) {
    const user = store.state.user;
    if (user) {
      config.headers.Authorization = `Bearer ${user.token}`;
    }
    // Do something before request is sent
    return config;
  },
  function(error) {
    // Do something with request error
    return Promise.reject(error);
  }
);

// 响应拦截器
request.interceptors.response.use(
  // 响应成功进入第1个函数
  // 该函数的参数是响应对象
  function(response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response;
  },
  // 响应失败进入第2个函数,该函数的参数是错误对象
  async function(error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    // 如果响应码是 401 ,则请求获取新的 token

    // 响应拦截器中的 error 就是那个响应的错误对象
    console.dir(error);
    if (error.response && error.response.status === 401) {
      // 校验是否有 refresh_token
      const user = store.state.user;

      if (!user || !user.refresh_token) {
        router.push("/login");

        // 代码不要往后执行了
        return;
      }

      // 如果有refresh_token,则请求获取新的 token
      try {
        const res = await axios({
          method: "PUT",
          url: "http://ttapi.research.itcast.cn/app/v1_0/authorizations",
          headers: {
            Authorization: `Bearer ${user.refresh_token}`
          }
        });

        // 如果获取成功,则把新的 token 更新到容器中
        console.log("刷新 token  成功", res);
        store.commit("setUser", {
          token: res.data.data.token, // 最新获取的可用 token
          refresh_token: user.refresh_token // 还是原来的 refresh_token
        });

        // 把之前失败的用户请求继续发出去
        // config 是一个对象,其中包含本次失败请求相关的那些配置信息,例如 url、method 都有
        // return 把 request 的请求结果继续返回给发请求的具体位置
        return request(error.config);
      } catch (err) {
        // 如果获取失败,直接跳转 登录页
        console.log("请求刷线 token 失败", err);
        router.push("/login");
      }
    }

    return Promise.reject(error);
  }
);

export default request;

数据持久化

掌握:使用 pinia-plugin-persistedstate 实现pinia仓库状态持久化,且完成测试

  1. Install with your favorite package manager:

    • pnpm : pnpm i pinia-plugin-persistedstate
    • npm : npm i pinia-plugin-persistedstate
    • yarn : yarn add pinia-plugin-persistedstate
  2. Add the plugin to pinia:

    import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

    const pinia = createPinia() pinia.use(piniaPluginPersistedstate)