为什么要有refreshToken

简介: 当你第一次接触的时候,你有没有一个这样子的疑惑,为什么需要refreshToken这个东西,而不是服务器端给一个期限较长甚至永久性的accessToken呢?

当你第一次接触的时候,你有没有一个这样子的疑惑,为什么需要refreshToken这个东西,而不是服务器端给一个期限较长甚至永久性的accessToken呢?
抱着这个疑惑我在网上搜寻了一番,
其实这个accessToken的使用期限有点像我们生活中的入住酒店,当我们在入住酒店时,会出示我们的身份证明来登记获取房卡,此时房卡相当于accessToken,可以访问对应的房间,当你的房卡过期之后就无法再开启房门了,此时就需要再到前台更新一下房卡,才能正常进入,这个过程也就相当于refreshToken。
accessToken使用率相比refreshToken频繁很多,如果按上面所说如果accessToken给定一个较长的有效时间,就会出现不可控的权限泄露风险。
使用refreshToken可以提高安全性

用户在访问网站时,accessToken被盗取了,此时攻击者就可以拿这个accessToke访问权限以内的功能了。如果accessToken设置一个短暂的有效期2小时,攻击者能使用被盗取的accessToken的时间最多也就2个小时,除非再通过refreshToken刷新accessToken才能正常访问。

设置accessToken有效期是永久的,用户在更改密码之后,之前的accessToken也是有效的

总体来说有了refreshToken可以降低accessToken被盗的风险
关于JWT无感刷新TOKEN方案(结合axios)
业务需求
在用户登录应用后,服务器会返回一组数据,其中就包含了accessToken和refreshToken,每个accessToken都有一个固定的有效期,如果携带一个过期的token向服务器请求时,服务器会返回401的状态码来告诉用户此token过期了,此时就需要用到登录时返回的refreshToken调用刷新Token的接口(Refresh)来更新下新的token再发送请求即可。
话不多说,先上代码
工具
axios作为最热门的http请求库之一,我们本篇文章就借助它的错误响应拦截器来实现token无感刷新功能。
具体实现

本次基于axios-bz代码片段封装响应拦截器
可直接配置到你的项目中使用 ✈️ ✈️

利用interceptors.response,在业务代码获取到接口数据之前进行状态码401判断当前携带的accessToken是否失效。
下面是关于interceptors.response中异常阶段处理内容。当响应码为401时,响应拦截器会走中第二个回调函数onRejected

下面代码分段可能会让大家阅读起来不是很顺畅,我直接把整份代码贴在下面,且每一段代码之间都添加了对应的注释

// 最大重发次数
const MAX_ERROR_COUNT = 5;
// 当前重发次数
let currentCount = 0;
// 缓存请求队列
const queue: ((t: string) => any)[] = [];
// 当前是否刷新状态
let isRefresh = false;

export default async (error: AxiosError) => {
const statusCode = error.response?.status;
const clearAuth = () => {

console.log('身份过期,请重新登录');
window.location.replace('/login');
// 清空数据
sessionStorage.clear();
return Promise.reject(error);

};
// 为了节省多余的代码,这里仅展示处理状态码为401的情况
if (statusCode === 401) {

// accessToken失效
// 判断本地是否有缓存有refreshToken
const refreshToken = sessionStorage.get('refresh') ?? null;
if (!refreshToken) {
  clearAuth();
}
// 提取请求的配置
const { config } = error;
// 判断是否refresh失败且状态码401,再次进入错误拦截器
if (config.url?.includes('refresh')) {
clearAuth();
}
// 判断当前是否为刷新状态中(防止多个请求导致多次调refresh接口)
if (!isRefresh) {
  // 设置当前状态为刷新中
  isRefresh = true;
  // 如果重发次数超过,直接退出登录
  if (currentCount > MAX_ERROR_COUNT) {
    clearAuth();
  }
  // 增加重试次数
  currentCount += 1;

  try {
    const {
      data: { access },
    } = await UserAuthApi.refreshToken(refreshToken);
    // 请求成功,缓存新的accessToken
    sessionStorage.set('token', access);
    // 重置重发次数
    currentCount = 0;
    // 遍历队列,重新发起请求
    queue.forEach((cb) => cb(access));
    // 返回请求数据
    return ApiInstance.request(error.config);
  } catch {
    // 刷新token失败,直接退出登录
    console.log('请重新登录');
    sessionStorage.clear();
    window.location.replace('/login');
    return Promise.reject(error);
  } finally {
    // 重置状态
    isRefresh = false;
  }
} else {
  // 当前正在尝试刷新token,先返回一个promise阻塞请求并推进请求列表中
  return new Promise((resolve) => {
    // 缓存网络请求,等token刷新后直接执行
    queue.push((newToken: string) => {
      Reflect.set(config.headers!, 'authorization', newToken);
      // @ts-ignore
      resolve(ApiInstance.request<ResponseDataType<any>>(config));
    });
  });
}

}

return Promise.reject(error);
};
复制代码
抽离代码
把上面关于调用刷新token的代码抽离成一个refreshToken函数,单独处理这一情况,这样子做有利于提高代码的可读性和维护性,且让看上去代码不是很臃肿
// refreshToken.ts
export default async function refreshToken(error: AxiosError) {

/* 
将上面 if (statusCode === 401) 中的代码贴进来即可,这里就不重复啦
代码仓库地址: https://github.com/QC2168/axios-bz/blob/main/Interceptors/hooks/refreshToken.ts
*/

}
复制代码
经过上面的逻辑抽离,现在看下拦截器中的代码就很简洁了,后续如果要调整相关逻辑直接在refreshToken.ts文件中调整即可。
import refreshToken from './refreshToken.ts'
export default async (error: AxiosError) => {
const statusCode = error.response?.status;

// 为了节省多余的代码,这里仅展示处理状态码为401的情况
if (statusCode === 401) {

refreshToken()

}

return Promise.reject(error);
};

相关文章
|
4月前
|
存储 JSON 前端开发
SpringBoot 如何实现无感刷新Token
【8月更文挑战第30天】在Web开发中,Token(尤其是JWT)作为一种常见的认证方式,被广泛应用于身份验证和信息加密。然而,Token的有效期问题常常导致用户需要重新登录,从而影响用户体验。为了实现更好的用户体验,SpringBoot可以通过无感刷新Token的机制来解决这一问题。以下将详细介绍SpringBoot如何做到无感刷新Token。
153 2
|
JSON 安全 数据安全/隐私保护
Refresh Token的使用场景以及如何与JWT交互
在这篇文章中,我们将探索由OAuth2定义的Refresh Token的概念。我们将会明白为什么他们会这样做,以及他们如何与其他类型的Token进行比较。我们也将通过一个简单的例子来学习如何使用它们。
18056 0
|
7月前
|
存储 算法 安全
PKCS#1、PKCS#5、PKCS#7、PKCS#8到底是什么?
PKCS#1、PKCS#5、PKCS#7、PKCS#8到底是什么?
959 0
|
7月前
|
缓存 前端开发 测试技术
antd renderFormItem AutoComplete结合防抖导致防抖失效的解决办法
antd renderFormItem AutoComplete结合防抖导致防抖失效的解决办法
108 3
|
数据采集 JavaScript Windows
用nodejs脚本实现网站自动签到功能
有一个网站每天签到可以获取流量,之前每天都是自己打开网页登录然后手动点签到,但是如果连续7天没签到之前获取的所有流量都会清空。类似的需求会有很多,很多网站、应用也都会提供签到获取积分、金币、能量、饲料...其实用node脚本来实现每天自动签到很简单的
390 1
|
存储 消息中间件 NoSQL
如何设置token有效期【5个应用场景分析+双token实现解析】
如何设置token有效期【5个应用场景分析+双token实现解析】
|
存储 SQL 缓存
MySQL 配置文件 my.cnf / my.ini 逐行详解
充分理解 MySQL 配置文件中各个变量的意义对我们有针对性的优化 MySQL 数据库性能有非常大的意义。我们需要根据不同的数据量级,不同的生产环境情况对 MySQL 配置文件进行优化。Windows 和 Linux 下的 MySQL 配置文件的名字和存放位置都是不同的,WIndows 下 MySQL 配置文件是 `my.ini` 存放在 MySQL 安装目录的根目录下;Linux 下 MySQL 配置文件是 `my.cnf` 存放在 `/etc/my.cnf`、`/etc/mysql/my.cnf`。我们也可以通过 `find` 命令进行查找。
30495 2
|
存储 缓存 算法
|
easyexcel Java API
POI及EasyExcel【Java提高】
POI及EasyExcel【Java提高】
547 0
用python实现网站自动签到
用python实现网站自动签到