当前位置: 首页 > news >正文

【我不熟悉的javascript】02. 使用token和refreshToken的管理用户登录状态

每日鸡汤:但凡你狠心一点,偷偷流泪的人就不是你

目录

前言

一、token 是什么    

1. 使用JWT(json web token)

2. 为什么用token验证?

二、使用refreshToken

1. 刷新token

2. 实战,在axios中使用refreshToken

总结


前言

一个简单的带有用户登录系统网页,token用来验证用户,refreshToken则是在用户token过期之后刷新用户token,进而保持登录状态的字段。


一、token 是什么    

1. 使用JWT(json web token)

我们常说的token,在我们前端看来,就是后台接口返回给我们的一串base64的字符串,我们只需要使用就可以了。

但是你知道这个字符串代表什么嘛?这就是传说中的JWT ( json web token),使用jwt token,我们经常可以简称为使用token做验证,因为jwt应用广泛,而且安全性比较高。

关于token如何生成,以及各个部分的作用,可以自行学习

JSON Web Token 入门教程 - 阮一峰的网络日志https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html总之我们必须要知道的几点是:

  1. token 保存了用户的信息和用户唯一的签名 ,由后台的登录接口返回给我们前端
  2. 我们前端需要保存token信息,在后续的请求中带在http请求的头部,一般是Authorization字段
  3. token有有效期,而且比较短,过期了需要重新申请

我们随意找一个使用token验证网站的请求来看一下:

在使用token进行验证的时候,需要注意的是:最好是token存在localstorage,而不是cookie中,因为cookie会在每次请求的时候带上,没必要,那我们不如用cookie做验证了

2. 为什么用token验证?

面试必考题,为什么使用token验证,而不是使用cookie和session

题外话,这里面的session,是值得后端的服务中的session,和我们前端的sessionstorage没有半毛钱关系,不要被名字忽悠了,有后端开发经验的同学应该知道,后端可以创建session连接,用来保存当前连接的用户信息等。

所以我们一般所说的cookie验证,其实是cookie/session的缩写,也就是【客户端client  / 服务端sever】对应的技术的缩写。客户端使用cookie,服务端使用session,我们前端同学经常会省略掉对服务器端server用的session的描述。 就说我们这个系统用的是cookie验证。

cookie/session的认证方式存在安全的问题

  1. csrf (暂时不多解释)
  2. xss攻击
  3. 浏览器禁用cookie,则无法认证
  4. session存放用户信息,并且存在服务器上,且不能跨服务器,会增加服务器压力 

而使用token验证就没有这些问题

  1. token认证是无状态的,只需要每次请求携带即可
  2. 服务器不会存用户的信息,没压力,
  3. 服务器只会根据下次请求头的字段进行验证,如果客户端没有携带Authorization,那么很简单,就是验证未通过。

二、使用refreshToken

1. 刷新token

基于安全的考虑,token必须设置有效期,假设token 过期时间1天,refreshToken过期时间需要长一点5天,那么今天登录后,明天token就过期了,这个时候需要使用refreshToken请求刷新token的接口,得到新的token和新的refreshToken。这样在用户角度上看,我的网站貌似一直处于登录状态,很方便!!

同时,refreshToken也会过期,如果连refreshToken也过期了,那就没办法了,只能劳烦用户重新登录了,记住就像食品有保质期一样,任何用于验证的token都需要有有效期,只是时间长短的不同。

为了能够请求接口,并且在token过期之后刷新token,我们需要将token和refreshToken都存在本地,一般是localstorage中就可以。

2. 实战,在axios中使用refreshToken

说了一大堆话,我们还是要关心到底怎么用这个refreshToken,首先肯定是不能让用户看见的暗地里进行的操作,给用户一种我一直处于登录状态的“错觉”。

所以我们一般在请求的拦截器中使用,假设我们使用的是axios这个插件,那么在我们封装的请求的时候设置一些拦截器,肯定是在reponse中设置,因为只有我们把请求发出去了,服务器才能给我们判断我们请求header中携带的token是否过期。注意!token是否过期是服务器判断的,如果过期了一半会返回状态码401  。我们就在这个时候拦住它!!行,服务器老兄,刚才给你的token,你说过期了,那我就刷新一下获取个新的,然后再用新的请求。

这个是axios官方给的response拦截器的模版

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response;
  }, function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
  });

我们只需稍加改造:

import axios from 'axios';

let authorizing = false;
// 定义一个请求队列,方便我们刷新后重新请求
let authQueue = [];

axios.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    // token 过期 401 肯定是走到error这里的
    const { status } = response;
    if (status !== 401) {
      return Promise.reject(error);
    }
    const token = localstorage.getItem('TOKEN'); // 假设token存在localstorage
    if (token) {
      // 如果正在刷新token就返回
      // 并且把当前这个不是刷新token的请求放入请求队列中,用于后续重新请求
      if (authorizing) {
        return new Promise((resolve, reject) => {
          authQueue.push({
            req: error.raw.config,
            resolve,
            reject,
          });
        });
      }
      // 如果不是正在刷新,并且,获取到本地存储的token,和当前请求头携带的一致,就开始刷新
      if (token === error.raw.config.headers.Authorization && !authorizing) {
        // 这是个异步请求的方法  这个方法后面会实现
        startRefreshToken((newToken) => {
          // 这个刷新的方法有一个回掉函数,参数是新的token,用于重新请求
          authorizing = false; // 刷新完毕,修改状态
          if (newToken) {
            // 开始使用新token,重新请求
            authQueue.forEach((item) => {
              item.req.headers = item.req.headers || {};
              item.req.headers.Authorization = newToken;
              axios.request(item.req).then(item.resolve).catch(item.reject);
            });
          } else {
            // 没有新的token, 也就是说本地存的refreshToken也过期了,队列中的每个请求都抛出错误信息
            authQueue.forEach((item) => {
              item.reject('登录过期', error.raw);
            });
          }
          authQueue.length = 0; // 清空请求队列,没错数组可以通过修改长度清空,这个没有不知道的吧
        });
        return new Promise((resolve, reject) => {
          authQueue.push({
            req: error.raw.config,
            resolve,
            reject,
          });
        });
      }

    
      // 如果token 已经更新了,或者 没有正在刷新token ,就重新请求当前的请求

      // 这一步的目的是保留headers上面的其他信息, 同时可以用下一句更新header.Authorization
      error.raw.config.headers = error.raw.config.headers || {};
      error.raw.config.headers.Authorization = token; // 这个是已经更新了的token
      return axios.request(error.raw.config); // 重新请求
    }
    return Promise.reject(error);
  }
);

现在来实现startRefreshToken,刷新token的方法,要求

  1. 请求接口
  2. 请求成功,要把新token和新的refreshToken存在本地,供下次请求使用
  3. 有一个回调函数,供重新请求用,并把新token作为参数传过去

// 是否正在刷新,如果是,就返回,不要重复刷新了
let refreshing = false;
//回调函数的数组,因为请求是源源不断的发的,肯定要都拦截,都要重新请求,所以要暂存一下
let callbacks = [];
async function startRefreshToken(callback) {
  if (callback) {
    callbacks.push(callback); // 暂存当前请求的callback
  }
  if(refreshing) {
    return;
  }
  refreshing = true;
  let newToken = '';
  let errMsg = '';
  try {
    // 获取我们存在本地的refreshToken
    const oldRefreshToken = localStorage.getItem('REFRESH_TOKEN');
    if (oldRefreshToken) {
      const { data } = await axios.post(
        'xxx', // 这个是你们后端的api
        {
          refresh_token: oldRefreshToken,
        },
        {
          headers: {}, // 刷新token的请求headers肯定不要携带Authorization字段
        }
      );
       
      // 这是新返回的token和refreshToken,注意refreshToken也会更新
      const { token, refreshToken } = data

      newToken = token; // 给callback调用

      // 更新本地的存储的值, 这之后还可以用来修改vuex里的值,反正你有新token了,随你干点啥
      localStorage.setItem('TOKEN', token);
      localStorage.setItem('REFRESH_TOKEN', refreshToken);
    }
  } catch (err) {
    errMsg = err.message;
    console.log(errMsg)
  }
  // 执行所有的回调函数
  callbacks.forEach((callback) => {
    // 如果本地存的refreshToken过期了,到这一步  newToken = ''
    callback(newToken); 
  });
  
  // 清空回调函数的数组
  callbacks.length = 0;
  // 重置状态
  refreshing = false;
}

好了,大功告成!!


总结

刷新token是一个必备技能,一定要学会啊,朋友们!!而且好好用心看一遍,真的一点都不难,主要的关键在于如何重新请求已经发送的请求。

就是那个请求队列authQueue感觉很关键,然后就是回调函数需要有。

相关文章:

  • 备战秋招涵盖二十九大技术栈Java面试最新八股文来袭
  • tensorflow张量运算
  • 论文阅读笔记StyTr2: Image Style Transfer with Transformers
  • mybatis面试题及回答
  • 奔腾电力面试题
  • 【leetcode】905. 按奇偶排序数组 (简单)
  • Java--MybatisPlus入门;与Mybatis区别;简单使用(一)
  • #ubuntu# #git# repository git config --global --add safe.directory
  • 【数据结构】——二叉树oj题详解
  • 性能测试:工具篇:Jmeter实时可视化平台搭建
  • 你该用什么的美剧学英语?
  • 面试算法 二叉树的遍历,方法 :迭代 ,前序遍历: 中序遍历: 后序遍历: 层序遍历
  • Matlab常用函数(control)
  • 推荐10款好用的数据可视化工具,赶紧收藏
  • Java刷题面试系列习题(六)
  • [NodeJS] 关于Buffer
  • __proto__ 和 prototype的关系
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • ES6--对象的扩展
  • fetch 从初识到应用
  • JavaScript-Array类型
  • Java教程_软件开发基础
  • JS字符串转数字方法总结
  • Vue2.0 实现互斥
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • Vultr 教程目录
  • 测试如何在敏捷团队中工作?
  • 高程读书笔记 第六章 面向对象程序设计
  • 猴子数据域名防封接口降低小说被封的风险
  • 聊聊flink的TableFactory
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 普通函数和构造函数的区别
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 使用Swoole加速Laravel(正式环境中)
  • 手机app有了短信验证码还有没必要有图片验证码?
  • elasticsearch-head插件安装
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • ​Java并发新构件之Exchanger
  • ​人工智能书单(数学基础篇)
  • $.ajax,axios,fetch三种ajax请求的区别
  • (1)(1.9) MSP (version 4.2)
  • (2)nginx 安装、启停
  • (2015)JS ES6 必知的十个 特性
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (八十八)VFL语言初步 - 实现布局
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (四) Graphivz 颜色选择
  • (一)Neo4j下载安装以及初次使用
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)一些感悟
  • **python多态
  • .NET Compact Framework 3.5 支持 WCF 的子集