您现在的位置是:首页 > 技术教程 正文

前端刷新token,判断token是否过期(jwt鉴权)

admin 阅读: 2024-03-29
后台-插件-广告管理-内容页头部广告(手机)

4.1 什么是 JWT
JWT 是 Auth0 提出的通过 对 JSON 进行加密签名来实现授权验证的方案;
就是登录成功后将相关用户信息组成 JSON 对象,然后对这个对象进行某种方式的加密,返回给客户端;
客户端在下次请求时带上这个 Token;
服务端再收到请求时校验 token 合法性,其实也就是在校验请求的合法性。
4.2 JWT 的组成
JWT 由三部分组成: Header 头部、 Payload 负载 和 Signature 签名
它是一个很长的字符串,中间用点(.)分隔成三个部分。列如 :

`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c`
  • 1

Header 头部:
在 Header 中通常包含了两部分:

typ:代表 Token 的类型,这里使用的是 JWT 类型;
alg:使用的 Hash 算法,例如 HMAC SHA256 或 RSA.

{ "alg": "HS256", "typ": "JWT" }
  • 1
  • 2
  • 3
  • 4

Payload 负载:
它包含一些声明 Claim (实体的描述,通常是一个 User 信息,还包括一些其他的元数据) ,用来存放实际需要传递的数据,JWT 规定了7个官方字段:

iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号

除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

{ "sub": "1234567890", "name": "John Doe", "admin": true }
  • 1
  • 2
  • 3
  • 4
  • 5

Signature 签名
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
  • 1
  • 2
  • 3
  • 4

4.3 JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

Authorization: Bearer <token>
  • 1

4.4 JWT 的认证流程图
其实 JWT 的认证流程与 Token 的认证流程差不多,只是不需要再单独去查询数据库查找用户用户;简要概括如下:
在这里插入图片描述

4.5 JWT 的优点

不需要在服务端保存会话信息(RESTful API 的原则之一就是无状态),所以易于应用的扩展,即信息不保存在服务端,不会存在 Session 扩展不方便的情况;
JWT 中的 Payload 负载可以存储常用信息,用于信息交换,有效地使用 JWT,可以降低服务端查询数据库的次数

4.6 JWT 的缺点

加密问题: JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
到期问题: 由于服务器不保存 Session 状态,因此无法在使用过程中废止某个 Token,或者更改 Token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

5.判断token是否过期

1、刷新令牌(Refresh Token)

在用户登录时,除了发放一个访问令牌(Access Token)以外,再发放一个刷新令牌(Refrsh Token)。 访问令牌的有效期比较短,刷新令牌的有效期比较长。 当访问令牌过期时,使用刷新令牌向服务器请求新的访问令牌。如果刷新令牌也过期,则跳转回登录界面。 这种方式的优点是可以避免用户频繁登录,但需要妥善保管刷新令牌,因为它的安全性比访问令牌更高。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

一般会根据时间戳判断时间是否超过有效期时间(可借鉴下面的例子进行操作)

login.js中,登录后设置即将过期时间:

import {setTokenOverdueTime} from '../src/api/refreshToken' localStorage.setItem("tokenOverdueTime", setTokenOverdueTime()); 1 2 新建一个refreshToken.js文件,文件内容如下: import Vue from 'vue' //设置token快过期时间,当前时间+20分钟 export function setTokenOverdueTime() { let t = new Date().getTime() + 1200000; let d = new Date(t); let theMonth = d.getMonth() + 1; let theDate = d.getDate(); let theHours = d.getHours(); let theMinutes = d.getMinutes(); if (theMonth < 10) { theMonth = '0' + theMonth } if (theDate < 10) { theDate = '0' + theDate } if (theHours < 10) { theHours = '0' + theHours } if (theMinutes < 10) { theMinutes = '0' + theMinutes } let date = d.getFullYear() + '-' + theMonth + '-' + theDate let time = theHours + ':' + theMinutes let Spare = date + ' ' + time return Spare; } // 判断token是否即将过期 function isTokenExpired() { let curTime = new Date(); //获取即将过期时间 let tokenOverdueTime = localStorage.getItem("tokenOverdueTime"); // 判断当前时间是否大于即将过期时间 if (curTime > new Date(tokenOverdueTime)) { return true } return false; } // 将所有的请求都push到数组中,其实数组是[function(token){}, function(token){},...] function cacheRequestArrHandle(cb) { cacheRequestArr.push(cb); } // 数组中的请求得到新的token之后自执行,用新的token去重新发起请求 function afreshRequest(token) { cacheRequestArr.map(cb => cb(token)); cacheRequestArr = []; } //定义一个空数组,用来缓存请求 let cacheRequestArr = []; // 是否正在刷新的标志 window.isRefreshing = false; export function isRefreshToken(instance, config, store) { // 判断token是否即将过期,且不是请求刷新token的接口 if(isTokenExpired() && config.url !== '/refreshToken'){ // 所有的请求来了,先判断是否正在刷新token, // 如果不是,将刷新token标志置为true并请求刷新token. // 如果是,则先将请求缓存到数组中 // 等到刷新完token后再次重新请求之前缓存的请求接口即可 if (!window.isRefreshing) { window.isRefreshing = true; instance.get('/refreshToken').then(res => { if(res.data.status === 0){ // 更新 store和缓存里的值 localStorage.setItem("userToken", JSON.stringify(res.data.data)); store.dispatch("setUserToken", res.data.data); // 更新即将过期时间 localStorage.setItem("tokenOverdueTime", setTokenOverdueTime()); // 将刷新的token替代老的token config.headers.token = res.data.data; // 刷新token完成后重新请求之前的请求 afreshRequest(res.data.data); } }).finally(() => { window.isRefreshing = false; }) // 下面这段代码一定要写,不然第一个请求的接口带过去的token还是原来的,要将第一个请求也缓存起来 let retry = new Promise((resolve) => { cacheRequestArrHandle((token) => { config.headers.token = token; // token为刷新完成后传入的token // 将请求挂起 resolve(config) }) }) return retry; } else{ let retry = new Promise((resolve) => { cacheRequestArrHandle((token) => { config.headers.token = token; // token为刷新完成后传入的token // 将请求挂起 resolve(config) }) }) return retry; } } else{ return config } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107

设置拦截器:

import store from '../../store'; import {isRefreshToken} from '../src/api/refreshToken' /** * 请求拦截 * interceptors * @param instance * */ export const interceptors = (instance) => { //请求拦截 instance.interceptors.request.use(config => { // 请求头携带token 和env let token = localStorage.getItem("userToken") ? JSON.parse(localStorage.getItem("userToken")) : null; if(token){ config.headers.token = token; } let flag = isRefreshToken(instance, config, store); return flag ? flag : config; }, (error) => { return Promise.reject(error); }) //响应拦截 instance.interceptors.response.use(res => { //返回数据 if(res.data.dev) return res; if (res.data.code !== 0) { return Promise.reject(res); }else { return res; } }, (error) => { return Promise.reject(error); })
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

2、滑动窗口

用户每次使用使用访问令牌时,服务器都会更新访问令牌的过期时间。 这种方式的优点是用户只要频繁访问,就不需要登录,但可能会增加服务器负担。
  • 1
  • 2
  • 3

3、重新登录

当访问令牌过期时,跳转回登录界面,让用户重新登录。这是最简单的一种方式,但可能会影响用户体验。
  • 1
标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索