什么是JWT

JWT是全称是JSON WEB TOKEN,是一个开放标准,用于将各方数据信息作为JSON格式进行对象传递,可以对数据进行可选的数字加密,可使用RSAECDSA进行公钥/私钥签名。

使用场景

JWT最常见的使用场景就是缓存当前用户登录信息,当用户登录成功之后,拿到JWT,之后用户的每一个请求在请求头携带上Authorization字段来辨别区分请求的用户信息。且不需要额外的资源开销。

相比传统session的区别

比起传统的session认证方案,为了让服务器能识别是哪一个用户发过来的请求,都需要在服务器上保存一份用户的登录信息(通常保存在内存中),再与浏览器的cookie打交道。

  • 安全方面 由于是使用cookie来识别用户信息的,如果cookie被拦截,用户会很容易受到跨站请求伪造的攻击。
  • 负载均衡 当服务器A保存了用户A的数据之后,在下一次用户A服务器A时由于服务器A访问量较大,被转发到服务器B,此时服务器B没有用户A的数据,会导致session失效。
  • 内存开销 随着时间推移,用户的增长,服务器需要保存的用户登录信息也就越来越多的,会导致服务器开销越来越大。

为什么说JWT不需要额外的开销

JWT为三个部分组成,分别是HeaderPayloadSignature,使用.符号分隔。

// 像这样子
xxxxx.yyyyy.zzzzz

标头 header

标头是一个JSON对象,由两个部分组成,分别是令牌是类型(JWT)和签名算法(SHA256RSA

{
  "alg": "HS256",
  "typ": "JWT"
}

负荷 payload

负荷部分也是一个JSON对象,用于存放需要传递的数据,例如用户的信息

{
  "username": "_island",
  "age": 18
}

此外,JWT规定了7个可选官方字段(建议)

属性 说明
iss JWT签发人
exp JWT过期时间
sub JWT面向用户
aud JWT接收方
nbf JWT生效时间
iat JWT签发时间
jti JWT编号

签章 signature

这一部分,是由前面两个部分的签名,防止数据被篡改。在服务器中指定一个密钥,使用标头中指定的签名算法,按照下面的公式生成这签名数据

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

在拿到签名数据之后,把这三个部分的数据拼接起来,每个部分中间使用.来分隔。这样子我们就生成出一个了JWT数据了,接下来返回给客户端储存起来。而且客户端在发起请求时,携带这个JWT在请求头中的Authorization字段,服务器通过解密的方式即可识别出对应的用户信息。

JWT优势和弊端

优势

  • 数据体积小,传输速度快
  • 无需额外资源开销来存放数据
  • 支持跨域验证使用

弊端

  • 生成出来的Token无法撤销,即使重置账号密码之前的Token也是可以使用的(需等待JWT过期)
  • 无法确认用户已经签发了多少个JWT
  • 不支持refreshToken

关于refreshToken

refreshTokenOauth2认证中的一个概念,和accessToken一起生成出来的。

当用户携带的这个accessToken过期时,用户就需要在重新获取新的accessToken,而refreshToken就用来重新获取新的accessToken的凭证。

为什么要有refreshToken

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

抱着这个疑惑我在网上搜寻了一番,

其实这个accessToken的使用期限有点像我们生活中的入住酒店,当我们在入住酒店时,会出示我们的身份证明来登记获取房卡,此时房卡相当于accessToken,可以访问对应的房间,当你的房卡过期之后就无法再开启房门了,此时就需要再到前台更新一下房卡,才能正常进入,这个过程也就相当于refreshToken

accessToken使用率相比refreshToken频繁很多,如果按上面所说如果accessToken给定一个较长的有效时间,就会出现不可控的权限泄露风险。

使用refreshToken可以提高安全性

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

总体来说有了refreshToken可以降低accessToken被盗的风险

关于JWT无感刷新TOKEN方案(结合axios)

业务需求

在用户登录应用后,服务器会返回一组数据,其中就包含了accessTokenrefreshToken,每个accessToken都有一个固定的有效期,如果携带一个过期的token向服务器请求时,服务器会返回401的状态码来告诉用户此token过期了,此时就需要用到登录时返回的refreshToken调用刷新Token的接口(Refresh)来更新下新的token再发送请求即可。

话不多说,先上代码

工具

axios作为最热门的http请求库之一,我们本篇文章就借助它的错误响应拦截器来实现token无感刷新功能。

具体实现

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

利用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<ResponseDataType>) => {
  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<ResponseDataType>) {
    /* 
    将上面 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<ResponseDataType>) => {
  const statusCode = error.response?.status;

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

  return Promise.reject(error);
};


鸣谢:
https://mp.weixin.qq.com/s/Pt3ySxBWCmNswYsKIebPgQ

无感刷新 Token的更多相关文章

  1. Spring Cloud实战 | 最八篇:Spring Cloud +Spring Security OAuth2+ Axios前后端分离模式下无感刷新实现JWT续期

    一. 前言 记得上一篇Spring Cloud的文章关于如何使JWT失效进行了理论结合代码实践的说明,想当然的以为那篇会是基于Spring Cloud统一认证架构系列的最终篇.但关于JWT另外还有一个 ...

  2. 基于OAuth2.0的token无感知刷新

    目前手头的vue项目关于权限一块有一个需求,其实架构师很早就要求我做了,但是由于这个紧急程度不是很高,最近临近项目上线,我才想起,于是赶紧补上这个功能.这个项目是基于OAuth2.0认证,需要在每个请 ...

  3. SpringSecurity+Oauth2+Jwt实现toekn认证和刷新token

    简单描述:最近在处理鉴权这一块的东西,需求就是用户登录需要获取token,然后携带token访问接口,token认证成功接口才能返回正确的数据,如果访问接口时候token过期,就采用刷新token刷新 ...

  4. ASP.NET Core 3.1使用JWT认证Token授权 以及刷新Token

    传统Session所暴露的问题 Session: 用户每次在计算机身份认证之后,在服务器内存中会存放一个session,在客户端会保存一个cookie,以便在下次用户请求时进行身份核验.但是这样就暴露 ...

  5. .Net中使用无闪刷新控件时提示框不显示

    今天做提示框的时候一直不显示,让我郁闷好久,晚上吃饭的时候问了同事一下,他给了一个思路, 他说可能是因为由于页面中的无闪刷新导致的结果:百度了一下真找到了解决方法 在页面中存在无闪刷新控件的时候提示框 ...

  6. WPF MVVM模式下的无阻塞刷新探讨

    很多时候我们需要做一个工作,在一个方法体里面,读取大数据绑定到UI界面,由于长时间的读取,读取独占了线程域,导致界面一直处于假死状态.例如,当应用程序开始读取Web资源时,读取的时效是由网络链路的速度 ...

  7. ArcEngine 图层无闪烁刷新

    使用AE的同行经常会遇到这样的问题,图层刷新.目前常用的有以下几种方法: 1.完全刷新 MapControl.Refresh(); 2.局部刷新 MapControl.Refresh(esriView ...

  8. flask刷新token

    我们在做前后端分离的项目中,最常用的都是使用token认证. 登录后将用户信息,过期时间以及私钥一起加密生成token,但是比较头疼的就是token过期刷新的问题,因为用户在登录后,如果在使用过程中, ...

  9. 十二、存token获取token刷新token发送header头

    //测试token //获取token function setToken(data){ var storage = window.localStorage; if(!storage){ alert( ...

  10. kaggle 欺诈信用卡预测——不平衡训练样本的处理方法 综合结论就是:随机森林+过采样(直接复制或者smote后,黑白比例1:3 or 1:1)效果比较好!记得在smote前一定要先做标准化!!!其实随机森林对特征是否标准化无感,但是svm和LR就非常非常关键了

    先看数据: 特征如下: Time Number of seconds elapsed between each transaction (over two days) numeric V1 No de ...

随机推荐

  1. 10月25日内容总结——正则表达式相关知识与re模块

    目录 一.正则表达式前戏 二.正则表达式内容介绍 1.字符组 2.特殊符号 3.量词 4.贪婪匹配与非贪婪匹配 贪婪匹配 非贪婪匹配 5.转义符 6.正则表达式实战建议与一些例子 建议 例子 三.re ...

  2. Nginx04 反向代理和负载均衡

    1 反向代理介绍 https://www.cnblogs.com/jthr/p/16827214.html 2 负载均衡介绍 https://www.cnblogs.com/jthr/p/168273 ...

  3. MySQL的简单安装配置

    一.简单了解MySQL 1.在了解MySQL之前因该了解的东西 数据库(Database)指长期存储在计算机内的.有组织的.可共享的数据集合.数据库实际上就是一个文件集合,是一个存储数据的仓库,本质就 ...

  4. ASP.NET Core开发者指南(2022版路线图)

    ASP.NET Core开发者指南 2022年 ASP.NET Core 开发者指南: 在下面,您可以看到一个图,说明可以采用的路径以及要成为ASP.NET Core开发人员所想要学习的库.我将此图作 ...

  5. cannot load "mso.dll" vs2008 web开发问题

    已成功解决办法: ①将VS 2008安装包WCUWebDesignerCoreWebDesignerCore.exe提取并重新安装: ②将C:Program Files/Common Files/Mi ...

  6. AIFF和AIFF-C音频交换文件格式的简单介绍

    正文 AIFF,全称 Audio Interchange File Format,可简写为 Audio IFF 或 AIFF,是苹果公司推出的一种音频文件格式. AIFF-C,是 AIFF 的扩充,C ...

  7. CSS常用属性(2)

    (4) position(定位) fixed 一般用来写网页顶端的固定导航条,或者两侧的菜单. <!--对于块级标签来说加上position:fixed之后,该div就不会占一整行,一般需要手动 ...

  8. JavaScript字符串的常用方法

    一.操作方法 我们也可将字符串常用的操作方法归纳为增.删.改.查,需要知道字符串的特点是一旦创建了,就不可变 增 这里增的意思并不是说直接增添内容,而是创建字符串的一个副本,再进行操作 除了常用+以及 ...

  9. JZOJ 2020.02.01【NOIP提高组】模拟A 组

    2020.02.01[NOIP提高组]模拟A 组 二月份第一场比赛 闲话 惨烈啊! 50+30+0=80分 一题都没A 唉 最高150? \(zzh\) 暴虐A组 总结: 若干新东西 \(T1\) 我 ...

  10. 【雅礼联考DAY02】Revolution

    #include<cstdio> #include<queue> using namespace std; const int N = 210 , M = 210000 , I ...