【我不熟悉的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感觉很关键,然后就是回调函数需要有。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 `rest_framework_jwt` 进行 token 续期需要执行以下步骤: 1. 在 `settings.py` 中添加 `JWT_ALLOW_REFRESH` 配置项: ```python JWT_AUTH = { # ... 其他配置项 ... 'JWT_ALLOW_REFRESH': True, 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), } ``` 其中,`JWT_ALLOW_REFRESH` 配置项表示是否允许使用 refresh token 进行 token 续期,`JWT_REFRESH_EXPIRATION_DELTA` 配置项表示 refresh token 的有效期,这里设置为 7 天。 2. 在 `urls.py` 中添加 `refresh_jwt_token` 视图的路由: ```python from rest_framework_jwt.views import refresh_jwt_token urlpatterns = [ # ... 其他路由 ... path('api/token/refresh/', refresh_jwt_token), ] ``` 3. 在前端代码中,当 token 过期后,使用 refresh token 进行 token 续期。示例代码如下: ```javascript const refreshToken = localStorage.getItem('refreshToken'); if (refreshToken) { fetch('/api/token/refresh/', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refresh: refreshToken }), }) .then((response) => response.json()) .then((data) => { localStorage.setItem('accessToken', data.access); localStorage.setItem('refreshToken', data.refresh); }) .catch((error) => { console.error('Failed to refresh token', error); }); } else { console.error('No refresh token found'); } ``` 在上面的代码中,我们首先从本地存储中获取 refresh token,然后使用 `fetch` 发送 POST 请求到 `/api/token/refresh/` 视图,将 refresh token 作为请求体的 `refresh` 参数。在成功获取到新的 access token 和 refresh token 后,我们将它们保存到本地存储中。 希望这可以帮助你理解如何使用 `rest_framework_jwt` 进行 token 续期。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值