• 【我不熟悉的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拦截器的模版

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

    我们只需稍加改造:

    1. import axios from 'axios';
    2. let authorizing = false;
    3. // 定义一个请求队列,方便我们刷新后重新请求
    4. let authQueue = [];
    5. axios.interceptors.response.use(
    6. function (response) {
    7. return response;
    8. },
    9. function (error) {
    10. // token 过期 401 肯定是走到error这里的
    11. const { status } = response;
    12. if (status !== 401) {
    13. return Promise.reject(error);
    14. }
    15. const token = localstorage.getItem('TOKEN'); // 假设token存在localstorage
    16. if (token) {
    17. // 如果正在刷新token就返回
    18. // 并且把当前这个不是刷新token的请求放入请求队列中,用于后续重新请求
    19. if (authorizing) {
    20. return new Promise((resolve, reject) => {
    21. authQueue.push({
    22. req: error.raw.config,
    23. resolve,
    24. reject,
    25. });
    26. });
    27. }
    28. // 如果不是正在刷新,并且,获取到本地存储的token,和当前请求头携带的一致,就开始刷新
    29. if (token === error.raw.config.headers.Authorization && !authorizing) {
    30. // 这是个异步请求的方法 这个方法后面会实现
    31. startRefreshToken((newToken) => {
    32. // 这个刷新的方法有一个回掉函数,参数是新的token,用于重新请求
    33. authorizing = false; // 刷新完毕,修改状态
    34. if (newToken) {
    35. // 开始使用新token,重新请求
    36. authQueue.forEach((item) => {
    37. item.req.headers = item.req.headers || {};
    38. item.req.headers.Authorization = newToken;
    39. axios.request(item.req).then(item.resolve).catch(item.reject);
    40. });
    41. } else {
    42. // 没有新的token, 也就是说本地存的refreshToken也过期了,队列中的每个请求都抛出错误信息
    43. authQueue.forEach((item) => {
    44. item.reject('登录过期', error.raw);
    45. });
    46. }
    47. authQueue.length = 0; // 清空请求队列,没错数组可以通过修改长度清空,这个没有不知道的吧
    48. });
    49. return new Promise((resolve, reject) => {
    50. authQueue.push({
    51. req: error.raw.config,
    52. resolve,
    53. reject,
    54. });
    55. });
    56. }
    57. // 如果token 已经更新了,或者 没有正在刷新token ,就重新请求当前的请求
    58. // 这一步的目的是保留headers上面的其他信息, 同时可以用下一句更新header.Authorization
    59. error.raw.config.headers = error.raw.config.headers || {};
    60. error.raw.config.headers.Authorization = token; // 这个是已经更新了的token
    61. return axios.request(error.raw.config); // 重新请求
    62. }
    63. return Promise.reject(error);
    64. }
    65. );

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

    1. 请求接口
    2. 请求成功,要把新token和新的refreshToken存在本地,供下次请求使用
    3. 有一个回调函数,供重新请求用,并把新token作为参数传过去
    1. // 是否正在刷新,如果是,就返回,不要重复刷新了
    2. let refreshing = false;
    3. //回调函数的数组,因为请求是源源不断的发的,肯定要都拦截,都要重新请求,所以要暂存一下
    4. let callbacks = [];
    5. async function startRefreshToken(callback) {
    6. if (callback) {
    7. callbacks.push(callback); // 暂存当前请求的callback
    8. }
    9. if(refreshing) {
    10. return;
    11. }
    12. refreshing = true;
    13. let newToken = '';
    14. let errMsg = '';
    15. try {
    16. // 获取我们存在本地的refreshToken
    17. const oldRefreshToken = localStorage.getItem('REFRESH_TOKEN');
    18. if (oldRefreshToken) {
    19. const { data } = await axios.post(
    20. 'xxx', // 这个是你们后端的api
    21. {
    22. refresh_token: oldRefreshToken,
    23. },
    24. {
    25. headers: {}, // 刷新token的请求headers肯定不要携带Authorization字段
    26. }
    27. );
    28. // 这是新返回的token和refreshToken,注意refreshToken也会更新
    29. const { token, refreshToken } = data
    30. newToken = token; // 给callback调用
    31. // 更新本地的存储的值, 这之后还可以用来修改vuex里的值,反正你有新token了,随你干点啥
    32. localStorage.setItem('TOKEN', token);
    33. localStorage.setItem('REFRESH_TOKEN', refreshToken);
    34. }
    35. } catch (err) {
    36. errMsg = err.message;
    37. console.log(errMsg)
    38. }
    39. // 执行所有的回调函数
    40. callbacks.forEach((callback) => {
    41. // 如果本地存的refreshToken过期了,到这一步 newToken = ''
    42. callback(newToken);
    43. });
    44. // 清空回调函数的数组
    45. callbacks.length = 0;
    46. // 重置状态
    47. refreshing = false;
    48. }

    好了,大功告成!!


    总结

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

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

  • 相关阅读:
    【云原生】创建harbor私有仓库及使用aliyun个人仓库
    华为主题开发分享-在windows 11操作系统上识别不到P50等华为手机的解决方案
    MySQL事务控制
    jvs-rules(规则引擎)和jvs智能bi(自助式数据分析)9.22更新内容
    力扣(LeetCode)1758. 生成交替二进制字符串的最少操作数(C++)
    SQL注入简介
    docker四种网络模式介绍
    spring aop的几种配置方式
    go入门快速学习笔记二:函数、数组与切片
    【论文阅读】Prototypical Networks for Few-shot Learning
  • 原文地址:https://blog.csdn.net/qq_17335549/article/details/126649702