双 token 机制

关于**“双 token 机制”**指的是使用户的登录状态在活跃期间保持有效的一种安全机制。

一般需要用户登录的系统为了校验用户的身份或登陆状态,会在用户登录时由服务器生成一个存储了或指向用户信息的 token,返回给客户端存储,这个 token 称为 access_token。

在请求其它接口的时候将 access_token 发送给服务器(通过 Header 或 Cookie)。

服务器根据 access_token 获取到用户信息,作为鉴权的依据。

而为了避免 token 被用户以外的人获取恶意使用,一般会预估一个用户最大的活跃时间,作为 access_token 的有效期,一旦过期,请求就鉴权失败,客户端有义务通知用户(例如强制登出、提示重新登录等)。

但是当 access_token 过期时,用户仍在活跃,突然提示登录这样的体验实在不是很友好。

在不取消 access_token 有效期的前提下,开发者们想到了 双 token 的方法。

就是服务器再生成一个 token,专门用于刷新(或更新) access_token,刷新的方式可以是延长 access_token 的有效期,也可以是生成并返回一个新的 access_token。这个用于刷新的 token 称为 refresh_token。

客户端需要改善的是在需要的时机,主动向服务器请求刷新 access_token,实现用户在活跃期间登录过期时自动重新登录的效果,大大提高用户体验。

通过 refresh_token 可以获取的内容可以包括:

  • 客户端来源
  • access_token
  • 用户信息
  • 其它

服务器可以将 refresh_token 与 access_token 一起返回给客户端客户端,作为客户端发送刷新请求的参数,实现更多安全机制,也可以不提供给客户端,它的最终目的是刷新 access_token。

现在实现了用户活跃期间保持登录状态,但是为了安全还是要为 refresh_token 设置一个有效期,当 refresh_token 也过期时,就无法刷新 access_token,从而真的登录过期,客户端通知用户。

设置 refresh_token 有效期的目的是保证用户不再活跃一定时间后,重置用户的登陆状态,实现单个 token 有效期的相同安全机制。

至于刷新 access_token 时是否同步刷新 refresh_token 就依实际需求为主了。

refresh_token 的有效期必须大于 access_token 的有效期,一般是后者的两倍。

axios 封装

基于 axios 的双 token 机制前端请求封装处理:

import axios from 'axios'// API 状态码
const SUCCESS = '200' // 成功
const FORBIDDEN = '403' // token 过期// axios实例
const service = axios.create()// 请求队列
const requestQueue = {// 请求列表,存储刷新 token 期间发起的请求list: [],// 添加请求add(config, resolve) {// failed = true 表示 token 获取失败,用于清理队列时决议请求的 Promise,避免内存堆积this.list.push((failed) => {if (failed) {resolve()} else {// 更新 tokenconfig.headers.access_token = storage.getItem('access_token')// 重新请求resolve(service(config))}})},// 执行队列// failed = true 表示 token 获取失败,用于清理队列时决议请求的 Promise,避免内存堆积execute(failed) {let fn// eslint-disable-next-linewhile (fn = this.list.shift()) {fn(failed)}},// 清空队列clear() {this.execute(true)},
}// 请求拦截
service.interceptors.request.use(config => {const accessToken = storage.getItem('access_token')// 如果未登录,直接请求if (!accessToken) {return config}// 首部添加 tokenconfig.headers.access_token = accessToken// 判断是否是刷新 token 操作if (config.url.includes('refreshToken')) {return config}return handleByRefreshStatus(config, 'request')},error => {return Promise.reject(error)},
)// 响应拦截
service.interceptors.response.use(response => {// 判断是否是刷新 token 操作if (response.config.url.includes(refreshToken)) {return response}const res = response.data// token 过期if (res.code === FORBIDDEN) {return handleByRefreshStatus(response.config)}// 请求失败(服务器响应级别)if (res.code !== SUCCESS) {return Promise.reject(res)} else {return res}},error => {// 请求失败(异常级别)return Promise.reject(error)},
)// 根据刷新状态处理请求
function handleByRefreshStatus(config, type = 'response') {return new Promise((resolve) => {const status = localStorage.getItem('refresh_token_status')if (type === 'request') {// 判断是否正在刷新 tokenif (!status) {resolve(config)} else {requestQueue.add(config, resolve)}} else {// 将请求添加到队列中,待刷新完成后执行队列requestQueue.add(config, resolve)// 判断 token 刷新状态if (!status) {// 设置刷新状态localStorage.setItem('refresh_token_status', 1)// 获取新 tokenrefreshTokenFetch(localStorage.getItem('access_token')).then(({ data: res }) => {if (!(res && res.data && res.data.access_token)) {throw res}localStorage.setItem('access_token', res.data.access_token)localStorage.removeItem('refresh_token_status')// 执行队列requestQueue.execute()}).catch(err => {console.error('refresh token failed', err)localStorage.removeItem('refresh_token_status')// 清空队列requestQueue.clear()// 登录过期处理loginExpired()})}}})
}// 登录过期处理
function loginExpired() {// 客户端提示用户登录过期的处理方案
}// 刷新 token 请求
function refreshTokenFetch(refresh_token) {return service.post('/refreshToken', {refresh_token,})
}export default service

说明补充

主要逻辑

  • 当请求返回 403(token 过期),根据当前请求重新创建一个相同的请求追加到队列中,发送刷新 token 请求,如果获取成功执行队列中的请求。
  • 当客户端发送请求时,如果如果正在刷新 token,则将请求追加到队列中,等待执行。

localStorage 存储

将 token 等信息存储在 localStorage 是为了在多 Tab 打开应用时能够共享 token 和 刷新状态。避免相互间分别触发刷新请求导致的冲突。

具体以实际需求为准。

清空队列

清空队列的目的是清理内存,而不是仅仅清空队列数组。

axios 发起请求实际是创建了一个 Promise,当触发了刷新 token,当前请求的处理就被存入队列中,等待执行,而请求的处理就是 Promise 的决议。

如果用户登录过期的处理未重置页面状态(例如 location 跳转),队列中的请求也没有被执行完成,Promise 就一直是 pending 状态,这样请求过程产生的闭包就一直存储在内存中。

所以必须将队列中的每个请求进行处理(决议 Promise)。

推荐文章

《前端鉴权的兄弟们:cookie、session、token、jwt、单点登录 - by HenryLulu_几木》 作者详细简洁的介绍了不同鉴权方式的逻辑,关于鉴权,我觉得读完这篇就够了。

个人补充:

  • 双 token 机制目的是保持一定时间(refresh_token 有效期)内的持续登录状态,至于 token 中存的是加密数据或仅仅是一个 sessionID 取决于开发者。
  • 关于单点登录,主要就是通过向 SSO 获取访问目标系统的 ticket,并在访问目标系统的时候通过 URL 参数的方式将 ticket 传递过去进行登录校验。至于如何在获取 ticket 后继续访问目标系统,取决于开发者(如使用 location.href 跳转、或 jsonp)

双 token 保持登录机制前端拦截实践相关推荐

  1. 模块化封装 --- 双ToKen 实现免登录步骤详解

    本文大概配置了下 双token免登录 关于下文提到的store中封装的保存token的方法,请参考本链接: /*** 封装 axios 请求模块*/ import axios from 'axios' ...

  2. JWT之token机制与双token详解

    token机制 何为token ​ token即为令牌,是服务器生成的一串字符串,作为客户端向服务器进行请求的"通行证".在客户端进行初次登陆后由服务器返回,之后的每次请求只需要携 ...

  3. 基于spring security实现vue2前后端分离的双token刷新机制(完整代码详解,含金量拉满!)

    目录 一.前言: 核心功能概要: 通过加密算法创建一个用户: 二.后端 代码详解: 1.代码整体结构: 2.所需依赖: 3.UserDetailServiceImpl拦截用户登陆: 4.所需工具类 4 ...

  4. 后端使用SpringBoot和Jwt工具与Redis数据库+前端Vue Element Admin实现用户携带token的登录功能案例

    登录功能描述: 前端输入对应的用户信息 ,在用户输入邮箱后 点击发送按钮给邮箱发送验证码,拿取到验证填完之后,点击登录按钮给后端发送请求 ,后端接收到请求之后 ,第一步校验验证码,第二步校验用户名和密 ...

  5. 登录双token方案

    登录双token方案 文章目录 登录双token方案 前言 方案设计 前言 当我们需要用户在指定时间内有操作时就可以不用登录(首次登录还是免不了的),而在指定时间内没有任何操作时就需要重新登录的话,那 ...

  6. jsp给前端注入值失败_基于 qiankun 的微前端最佳实践(图文并茂) 应用间通信篇...

    引言 大家好~ 本文是基于 qiankun 的微前端最佳实践系列文章之 应用间通信篇,本文将分享在 qiankun 中如何进行应用间通信. 在开始介绍 qiankun 的应用通信之前,我们需要先了解微 ...

  7. python vue token_Haytham个人博客开发日志 -- Flask+Vue基于token的登录状态与路由管理...

    指路牌 符合一下关键词,这篇博客有可能会对你有帮助 不使用工厂函数的Flask应用 不使用蓝本的Flask应用 Flask跨域配置 基于Token的登录状态管理 Flask+Vue Vue路由拦截 A ...

  8. 关于自定义的登录机制在SAP Spartacus服务器端渲染(SSR)实施过程中遇到的问题

    问题背景 某客户使用了第三方的Authentication service来实现Spartacus用户的登录机制: In our project we have integration with MD ...

  9. springboot+shiro+jwt实现token认证登录

    准备: springboot 2.5.5 jdk 1.8 没有操作刷新token功能,也没有放redis做缓存 1.先贴代码 2.后讲一下验证逻辑 1.导入依赖 <!--shiro-->& ...

最新文章

  1. 利用FFmpeg切割视频
  2. 背包思想计算方案的总数(货币系统)
  3. diff算法_React源码揭秘(三):Diff算法详解
  4. linux下使用ffmpeg命令录屏桌面
  5. NOKIA Update for Windows Phone
  6. 51nod-1640--天气晴朗的魔法(简单最小生成树)
  7. linux使用gpio开一个线程,LINUX的gpio_request_one作用
  8. BZOJ-2194 快速傅立叶之二
  9. 关于腾讯应用管理中心,认领应用
  10. VC++ COleSafeArray VARIANT的使用
  11. 3D视觉关键技术与核心问题包括哪些?
  12. IDEA 方法自动添加注释
  13. 王阳明的心学精髓是什么?
  14. 初中英语知识水平测试软件,美国初中生英文水平测试!百个单词检测,看看你认识几个...
  15. Acwing-4645. 选数异或
  16. java导出excel表格,文件名称汉字话
  17. 块存储、文件存储、对象存储及内容分发CDN
  18. ffmpeg分离视频音频流!
  19. 连接mongodb提示目标计算机拒绝,MongoDB 由于目标计算机积极拒绝,无法连接 2014-07-25T11:00:48.634+0...
  20. 所做一切, 只为验证自己的想法

热门文章

  1. A股历史行情数据 API 接口
  2. 金蝶云星空和聚水潭接口打通对接实战
  3. Bancor之DEX破局
  4. 通信看点:以MACOM视角谈谈5G将如何发展
  5. 多线程的相关知识点(下)
  6. 前端初学者的Ant Design Pro V6总结(上)
  7. 【design pattern】结构型模式之—装饰者模式(Decorator)
  8. python自然语言处理实战 | NLP中用到的机器学习算法学习笔记
  9. Similar command is: ‘lz’ bash: ls: command not found...
  10. shell编程——Shell的字符串拼接