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

这篇具有很好参考价值的文章主要介绍了记录--前端无感知刷新token & 超时自动退出。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

一、token的作用

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

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

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

每次请求资源服务器时都会在请求头中添加 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 存入两个字段:

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

当有操作事件时,将当前时间戳存入 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 & 超时自动退出文章来源地址https://www.toymoban.com/news/detail-776842.html

到了这里,关于记录--前端无感知刷新token & 超时自动退出的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信小程序自动刷新token,无感刷新token

            小程序登录开发通常是调用wx.login获取code,然后发送到后台,后台请求微信拿到用户openId,然后根据openId查询用户,有就走登录流程然后返回token,没有则创建用户之后走登录流程然后返回token,也就是都需要返回一个有时效性的token给小程序端,来保持登录状态,

    2024年02月12日
    浏览(28)
  • 无感刷新:Vue前端实现Token的无缝刷新机制

    在现代Web应用程序中,用户身份验证和授权通常使用令牌(Token)机制来实现。然而,由于Token的过期时间限制,用户在长时间使用应用程序时可能需要重新登录。为了提供更好的用户体验,我们可以通过实现Token的无感刷新机制来避免用户在使用过程中的中断。本文将探讨如

    2024年02月10日
    浏览(30)
  • 前端刷新token,判断token是否过期(jwt鉴权)

    4.1 什么是 JWT JWT 是 Auth0 提出的通过 对 JSON 进行加密签名来实现授权验证的方案; 就是登录成功后将相关用户信息组成 JSON 对象,然后对这个对象进行某种方式的加密,返回给客户端; 客户端在下次请求时带上这个 Token; 服务端再收到请求时校验 token 合法性,其实也就是在

    2024年02月03日
    浏览(40)
  • 前端登录退出:处理Token问题(获取、缓存、失效处理)以及代码实现

    Token是服务端生成的一串字符串,当用户第一次登陆成功后,服务器会生成一个token,并将其返回给客户端。 当用户再次向服务器请求数据时,只需要携带着token请求数据即可,无需再次登陆用户名和密码 目的 通过token做一层数据拦截,可以减少数据库请求次数,减缓服务器

    2024年02月02日
    浏览(26)
  • 前端实现token的无感刷新--VUE

    token刷新的方案    方案一:后端返回过期时间,前端判断token过期时间,去调用刷新token的接口    缺点:需要后端提供一个token过期时间的字段;使用本地时间判断,若本地时间被修改,本地时间比服务器时间慢,拦截会失败。    方案二:写个定时器,定时刷新token接口

    2024年02月19日
    浏览(29)
  • VUE前端实现token的无感刷新

    说实话,这个其实没啥好讲的,要说有复杂度的话,也主要是在后端。 实现token无感刷新对于前端来说是一项十分常用的技术,其本质都是为了优化用户体验,当token过期时不需要用户调回登录页重新登录,而是当token失效时,进行拦截,发送刷新token的请求,获取最新的tok

    2024年02月05日
    浏览(37)
  • 记录-使用双token实现无感刷新,前后端详细代码

    近期写的一个项目使用双token实现无感刷新。最后做了一些总结,本文详细介绍了实现流程,前后端详细代码。前端使用了Vue3+Vite,主要是axios封装,服务端使用了koa2做了一个简单的服务器模拟。 jwt:JSON Web Token。是一种认证协议,一般用来校验请求的身份信息和身份权限。

    2023年04月24日
    浏览(29)
  • 前端项目部署自动检测更新后通知用户刷新页面(前端实现,技术框架vue、js、webpack)——方案一:编译项目时动态生成一个记录版本号的文件

    当我们重新部署前端项目的时候,如果用户一直停留在页面上并未刷新使用,会存在功能使用差异性的问题,因此,当前端部署项目后,需要提醒用户有去重新加载页面。 vue、js、webpack 编译项目时动态生成一个记录版本号的文件 轮询(20s、自己设定时间)这个文件,判断版

    2024年02月02日
    浏览(46)
  • VUE前端实现token的无感刷新,即refresh_token

    通常,对于一些需要记录用户行为的系统,在进行网络请求的时候都会要求传递一下登录的token。不过,为了接口数据的安全,服务器的token一般不会设置太长,根据需要一般是1-7天的样子,token过期后就需要重新登录。不过,频繁的登录会造成体验不好的问题,因此,需要体

    2024年02月09日
    浏览(29)
  • uniapp 前端定时刷新token,接口排队等待,promise 接口封装

           此项目为小程序。小程序完成第一版token刷新设计思路是:根据接口调用返回的errorCode来判断当前用户的token和refreshToken是否过期。根据不同的errorCode,前端去调用接口完成token的刷新或者跳转到登录页面重新登录。        由于小程序的用户功能权限可以在后台管理

    2024年02月09日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包