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


    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
  • 相关阅读:
    组合式API
    win11打开方式没有始终,例如pptx应该用power point打开
    江西省职业院校技能大赛中职组网络安全竞赛之Linux操作系统渗透测试
    windows mysql安装卸载,多版本mysql方案
    github的博客搭建以及标签的自动化
    js获取字符串,逗号前后的字符
    AI - 内容推荐算法
    【Flowable】FlowableUI使用以及在IDEA使用flowable插件(二)
    Java集合(二):Map集合与Collections工具类
    OrcaTerm AI
  • 原文地址:https://blog.csdn.net/qq_45641978/article/details/133883090