这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前端无感知刷新token&超时自动退出

一、token的作用

因为http请求是无状态的,是一次性的,请求之间没有任何关系,服务端无法知道请求者的身份,所以需要鉴权,来验证当前用户是否有访问系统的权限。

以oauth2.0授权码模式为例:

每次请求资源服务器时都会在请求头中添加 Authorization: Bearer access_token 资源服务器会先判断token是否有效,如果无效或过期则响应 401 Unauthorize。此时用户处于操作状态,应该自动刷新token保证用户的行为正常进行。

刷新token:使用refresh_token获取新的access_token,使用新的access_token重新发起失败的请求。

二、无感知刷新token方案

2.1 刷新方案

当请求出现状态码为 401 时表明token失效或过期,拦截响应,刷新token,使用新的token重新发起该请求。

如果刷新token的过程中,还有其他的请求,则应该将其他请求也保存下来,等token刷新完成,按顺序重新发起所有请求。

2.2 原生AJAX请求

2.2.1 http工厂函数

function httpFactory({ method, url, body, headers, readAs, timeout }) {
   const xhr = new XMLHttpRequest()
   xhr.open(method, url)
   xhr.timeout = isNumber(timeout) ? timeout : 1000 * 60

   if(headers){
       forEach(headers, (value, name) => value && xhr.setRequestHeader(name, value))
  }
   
   const HTTPPromise = new Promise((resolve, reject) => {
       xhr.onload = function () {
           let response;

           if (readAs === 'json') {
               try {
                   response = JSONbig.parse(this.responseText || null);
              } catch {
                   response = this.responseText || null;
              }
          } else if (readAs === 'xml') {
               response = this.responseXML
          } else {
               response = this.responseText
          }

           resolve({ status: xhr.status, response, getResponseHeader: (name) => xhr.getResponseHeader(name) })
      }

       xhr.onerror = function () {
           reject(xhr)
      }
       xhr.ontimeout = function () {
           reject({ ...xhr, isTimeout: true })
      }

       beforeSend(xhr)

       body ? xhr.send(body) : xhr.send()

       xhr.onreadystatechange = function () {
           if (xhr.status === 502) {
               reject(xhr)
          }
      }
  })

   // 允许HTTP请求中断
   HTTPPromise.abort = () => xhr.abort()

   return HTTPPromise;
}

2.2.2 无感知刷新token

// 是否正在刷新token的标记
let isRefreshing = false

// 存放因token过期而失败的请求
let requests = []

function httpRequest(config) {
   let abort
   let process = new Promise(async (resolve, reject) => {
       const request = httpFactory({...config, headers: { Authorization: 'Bearer ' + cookie.load('access_token'), ...configs.headers }})
       abort = request.abort
       
       try {                            
           const { status, response, getResponseHeader } = await request

           if(status === 401) {
               try {
                   if (!isRefreshing) {
                       isRefreshing = true
                       
                       // 刷新token
                       await refreshToken()

                       // 按顺序重新发起所有失败的请求
                       const allRequests = [() => resolve(httpRequest(config)), ...requests]
                       allRequests.forEach((cb) => cb())
                  } else {
                       // 正在刷新token,将请求暂存
                       requests = [
                           ...requests,
                          () => resolve(httpRequest(config)),
                      ]
                  }
              } catch(err) {
                   reject(err)
              } finally {
                   isRefreshing = false
                   requests = []
              }
          }                        
      } catch(ex) {
           reject(ex)
      }
  })
   
   process.abort = abort
   return process
}

// 发起请求
httpRequest({ method: 'get', url: 'http://127.0.0.1:8000/api/v1/getlist' })

2.3 Axios 无感知刷新token

// 是否正在刷新token的标记
let isRefreshing = false

let requests: ReadonlyArray<(config: any) => void> = []

// 错误响应拦截
axiosInstance.interceptors.response.use((res) => res, async (err) => {
   if (err.response && err.response.status === 401) {
       try {
           if (!isRefreshing) {
               isRefreshing = true
               // 刷新token
               const { access_token } = await refreshToken()

               if (access_token) {
                   axiosInstance.defaults.headers.common.Authorization = `Bearer ${access_token}`;

                   requests.forEach((cb) => cb(access_token))
                   requests = []

                   return axiosInstance.request({
                       ...err.config,
                       headers: {
                           ...(err.config.headers || {}),
                           Authorization: `Bearer ${access_token}`,
                      },
                  })
              }

               throw err
          }

           return new Promise((resolve) => {
               // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
               requests = [
                   ...requests,
                  (token) => resolve(axiosInstance.request({
                       ...err.config,
                       headers: {
                           ...(err.config.headers || {}),
                           Authorization: `Bearer ${token}`,
                      },
                  })),
              ]
          })
      } catch (e) {
           isRefreshing = false
           throw err
      } finally {
           if (!requests.length) {
               isRefreshing = false
          }
      }
  } else {
       throw err
  }
})

三、长时间无操作超时自动退出

当用户登录之后,长时间不操作应该做自动退出功能,提高用户数据的安全性。

3.1 操作事件

操作事件:用户操作事件主要包含鼠标点击、移动、滚动事件和键盘事件等。

特殊事件:某些耗时的功能,比如上传、下载等。

3.2 方案

用户在登录页面之后,可以复制成多个标签,在某一个标签有操作,其他标签也不应该自动退出。所以需要标签页之间共享操作信息。这里我们使用 localStorage 来实现跨标签页共享数据。

在 localStorage 存入两个字段:

当有操作事件时,将当前时间戳存入 lastActiveTime。

当有特殊事件时,将特殊事件名称存入 activeEvents ,等特殊事件结束后,将该事件移除。

设置定时器,每1分钟获取一次 localStorage 这两个字段,优先判断 activeEvents 是否为空,若不为空则更新 lastActiveTime 为当前时间,若为空,则使用当前时间减去 lastActiveTime 得到的值与规定值(假设为1h)做比较,大于 1h 则退出登录。

3.3 代码实现

const LastTimeKey = 'lastActiveTime'
const activeEventsKey = 'activeEvents'
const debounceWaitTime = 2 * 1000
const IntervalTimeOut = 1 * 60 * 1000

export const updateActivityStatus = debounce(() => {
   localStorage.set(LastTimeKey, new Date().getTime())
}, debounceWaitTime)

/**
* 页面超时未有操作事件退出登录
*/
export function timeout(keepTime = 60) {
   document.addEventListener('mousedown', updateActivityStatus)
   document.addEventListener('mouseover', updateActivityStatus)
   document.addEventListener('wheel', updateActivityStatus)
   document.addEventListener('keydown', updateActivityStatus)

   // 定时器
   let timer;

   const doTimeout = () => {
       timer && clearTimeout(timer)
       localStorage.remove(LastTimeKey)
       document.removeEventListener('mousedown', updateActivityStatus)
       document.removeEventListener('mouseover', updateActivityStatus)
       document.removeEventListener('wheel', updateActivityStatus)
       document.removeEventListener('keydown', updateActivityStatus)

       // 注销token,清空session,回到登录页
       logout()
  }

   /**
    * 重置定时器
    */
   function resetTimer() {
       localStorage.set(LastTimeKey, new Date().getTime())

       if (timer) {
           clearInterval(timer)
      }

       timer = setInterval(() => {
           const isSignin = document.cookie.includes('access_token')
           if (!isSignin) {
               doTimeout()
               return
          }

           const activeEvents = localStorage.get(activeEventsKey)
           if(!isEmpty(activeEvents)) {
               localStorage.set(LastTimeKey, new Date().getTime())
               return
          }
           
           const lastTime = Number(localStorage.get(LastTimeKey))

           if (!lastTime || Number.isNaN(lastTime)) {
               localStorage.set(LastTimeKey, new Date().getTime())
               return
          }

           const now = new Date().getTime()
           const time = now - lastTime

           if (time >= keepTime) {
               doTimeout()
          }
      }, IntervalTimeOut)
  }

   resetTimer()
}

// 上传操作
function upload() {
   const current = JSON.parse(localStorage.get(activeEventsKey))
   localStorage.set(activeEventsKey, [...current, 'upload'])
   ...
   // do upload request
   ...
   const current = JSON.parse(localStorage.get(activeEventsKey))
   localStorage.set(activeEventsKey, Array.isArray(current) ? current.filter((item) => itme !== 'upload'))
}

本文转载于:

https://juejin.cn/post/7320044522910269478

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--前端无感知刷新token & 超时自动退出的更多相关文章

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

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

  2. OAuth2.0与前端无感知token刷新实现

    前言 OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛的应用.Facebook.Twitter和Google等各种在线服务都提供了基于OAuth规范的认证机制. ...

  3. 无感刷新 Token

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

  4. 设置Linux shell超时自动退出

    Linux shell,一般默认情况下是不会超时退出的,但是有的时候我们想要让它在多少分钟后没有操作自动退出终端(听起来有点像windows多少分钟后自动锁屏一样).我们可以通过设置来实现这一功能. ...

  5. Linux登录超时自动退出处理办法

    出于安全方面的考虑,机器常要求配置一个登录时间期限,当闲置超过这一期限就自动退出:但在某些场合我们需要时不时地就使用机器,如果每次都要重新ssh登录那是非常麻烦的 方法一:让当前会话一直处于工作状态 ...

  6. ASP.NET MVC (Umbraco)中如何设置网站超时自动退出

    原文章请参考  https://edgewebware.com/2014/06/automatically-log-out-members-send-login-page-umbraco/ 在网站开发 ...

  7. web页面超时自动退出方法

    思路: 使用 mousemover 事件来监测是否有用户操作页面,写一个定时器间隔特定时间检测是否长时间未操作页面,如果是,退出: 具体时间代码如下(js):var lastTime = new Da ...

  8. mysql 8/oracle 登录失败处理,应配置并启用结束会话、限制非法登录次数和当登录连接超时自动退出等相关措施

    1 mysql 8 先安装密码插件 install plugin CONNECTION_CONTROL soname 'connection_control.so';install plugin CO ...

  9. 登录超时自动退出,计算时间差-b

    // 此方法适用于所有被创建过的controller,且当前controller生命周期存在,如有错误的地方望大神斧正 //  说一下我们的需求和实现原理,需求:在点击home键退出但没有滑飞它,5分 ...

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

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

随机推荐

  1. Series基础

    目录 创建Series对象 1) 创建一个空Series对象 2) ndarray创建Series对象 3) dict创建Series对象 4) 标量创建Series对象 访问Series数据 1) ...

  2. SSD 简介—— NAND 芯片介绍

    制作 存储芯片的制作和其他芯片制作大致相同,从沙子中提取单晶硅制作晶圆再封装芯片. 闪存芯片从架构上分为NOR和NAND NOR Flash的source line把每个cell都并联起来,而NAND ...

  3. .NET周刊【2月第2期 2024-02-11】

    国内文章 C#/.NET该如何自学入门? https://www.cnblogs.com/Can-daydayup/p/18006914 随着DotNetGuide技术社区交流群的扩大,很多新成员希望 ...

  4. openai chatGPT 原理通俗介绍

    引言 近年来,随着深度学习技术的不断发展,自然语言处理(NLP)领域取得了长足的进步.ChatGPT(Generative Pre-trained Transformer)作为一种先进的语言生成模型, ...

  5. Ubuntu18.04 Server安装Harbor

    Harbor是一个开源的本地部署的容器镜像注册中心, 带访问权限控制, 以及管理界面. 作用类似于Java中Maven的私有仓库Nexus, 用于配合Jenkins在部署容器到K8s时提供镜像源, 加 ...

  6. SpringBoot使用git-commit-id-maven-plugin打包

    简介 git-commit-id-maven-plugin 是一个maven 插件,用来在打包的时候将git-commit 信息打进jar中. 这样做的好处是可以将发布的某版本和对应的代码关联起来,方 ...

  7. jenkins配置github秘钥

    1.登录github,打开Settings 2.点击Developer settings 3.点击Personal access tokens-->Generate new token 4.勾选 ...

  8. python-鼠标宏

    按下鼠标左键, 连击 按下鼠标右键, 停止 import win32api import time from pynput.mouse import Button, Controller mouse ...

  9. [App Service for Windows]通过 KUDU 查看 Tomcat 配置信息

    问题描述 在App Service 中选择了Java Tomcat后,如何查看Azure App Service的Tomcat的配置信息呢? 问题解答 可以通过以下的 3个步骤查看: 第一步:登录 K ...

  10. Java 重写 equals + toString 练习

    1 package com.bytezreo.objectclass2; 2 3 /** 4 * 5 * @Description 重写 equals + toString 6 * @author B ...