6K Star 11.8K Fork 4K

GVPdotNET China / Furion

 / 详情

自动刷新令牌,当403正好是刷新令牌时间,返回的是401

已完成
创建于  
2022-10-21 11:54

💢 特别说明:如果 Issue 没有严格按照模板编写且未提供测试源码下载或 Git 测试仓库地址,则视为无效 Issue,将无法得到答复。

Furion 版本号

哪个版本号?


.NET SDK 版本号

  • .NET5
  • .NET6
  • .NET7

Web 项目类型

  • WebApi
  • Mvc
  • Razor Pages
  • Blazor Server
  • MinApp

操作系统和版本

  • Windows(版本)
  • Linux(版本)
  • MacOS(版本)
  • 其他(版本)

代码环境

  • 开发环境(Development)
  • 生产环境(Production)
  • 测试环境(Tests/单元测试/集成测试 )

描述你的问题

自动刷新令牌,当403正好是刷新令牌时间,返回的是401.

异常堆栈信息

异常堆栈是什么?


测试项目代码

⚠⚠ 必须提供完整可运行且包含错误的 Git 仓库 DEMO,DEMO 提供最简单的错误逻辑代码,否则将无法得到答复。⚠⚠

您的代码下载地址?


数据库信息

  • Sqlite
  • SqlServer
  • Mysql
  • Oracle
  • PGSql
  • Firebird
  • Cosmos

期待结果

自动刷新令牌,当403正好是刷新令牌时间,返回的依旧是403,且header头携带 最新的令牌和刷新令牌

评论 (18)

毛豆 创建了任务

show me code 啦。

我参与的客户项目有33个,都交付上线了,全部都是自动刷新token的方式,目前没有遇到过这样的情况,所以希望提供例子。请提供例子和图文证明问题,不能只发一个标题哈

而且参考我这个:https://gitee.com/dotnetchina/Furion/blob/v4/clients/axios_vue_react/axios-utils.ts

输入图片说明

输入图片说明

输入图片说明

输入图片说明

譬如 我申请下来的是:
令牌:2022年10月21日 09:00:00
过期时间 :2022年10月21日 09:01:00
刷新令牌过期时间:2022年10月21日 09:02:00

当时间推移到2022年10月21日 09:01:30 时 此时令牌已经过期,所以携带了刷新令牌,但是访问的API是受限的,403
此时我的想法是,服务端就应该返回403 且携带新的令牌和刷新令牌
但真实返回的却是401

好的,我先试图理解你说的。。。不行我们就qq聊~

好的,谢谢!

谢谢大佬之前的解答
我再补充一下:如果访问的接口是正常返回200状态,此情况不会发生!

可以提供一个例子上来,测试了一个小时都没测试出问题,太浪费时间了。

测试可以无限续期:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5IiwiZSI6ImE2MUVPVHVUSEczYnp4clRBVkgwazJ3VC1oLWhfUnhveEFqTm5ReEQzX28iLCJzIjo0NywibCI6NiwiayI6ImlPakUyTiIsImlhdCI6MTY2NjMzNjc0MCwibmJmIjoxNjY2MzM2NzQwLCJleHAiOjE2NjYzMzY4NjAsImlzcyI6ImRvdG5ldGNoaW5hIiwiYXVkIjoicG93ZXJieSBGdXJpb24ifQ.zmEdsUehBV_KQzqJUa689iPtQTiKN8a_D794Nvkokgs

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEsIlVzZXJOYW1lIjoiYWRtaW4iLCJpYXQiOjE2NjYzMzY3NDAsIm5iZiI6MTY2NjMzNjc0MCwiZXhwIjoxNjY2MzM2ODAwLCJpc3MiOiJkb3RuZXRjaGluYSIsImF1ZCI6InBvd2VyYnkgRnVyaW9uIn0.a61EOTuTHG3bzxrTAVH0k2wT-h-h_RxoxAjNnQxD3_o

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5IiwiZSI6ImE2MUVPVHVUSEczYnp4clRBVkgwazJ3VC1oLWhfUnhveEFqTm5ReEQzX28iLCJzIjo0NywibCI6NiwiayI6ImlPakUyTiIsImlhdCI6MTY2NjMzNjc0MCwibmJmIjoxNjY2MzM2NzQwLCJleHAiOjE2NjYzMzY4NjAsImlzcyI6ImRvdG5ldGNoaW5hIiwiYXVkIjoicG93ZXJieSBGdXJpb24ifQ.zmEdsUehBV_KQzqJUa689iPtQTiKN8a_D794Nvkokgs

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEsIlVzZXJOYW1lIjoiYWRtaW4iLCJpYXQiOjE2NjYzMzY3NDAsIm5iZiI6MTY2NjMzNjc0MCwiZXhwIjoxNjY2MzM2ODAwLCJpc3MiOiJkb3RuZXRjaGluYSIsImF1ZCI6InBvd2VyYnkgRnVyaW9uIn0.a61EOTuTHG3bzxrTAVH0k2wT-h-h_RxoxAjNnQxD3_o

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5IiwiZSI6ImZSQ3lYVzBkejJUd3pnbzlvTFQxSlpEZjRRbUo4UGQ4VXJGN2lfLW8tR0EiLCJzIjoyNywibCI6MTIsImsiOiJsSWpvaVlXUnRhVzQiLCJpYXQiOjE2NjYzMzY4MzIsIm5iZiI6MTY2NjMzNjgzMiwiZXhwIjoxNjY2MzM2OTUyLCJpc3MiOiJkb3RuZXRjaGluYSIsImF1ZCI6InBvd2VyYnkgRnVyaW9uIn0.tPzQ2J1r60cDy47selYfPSwzQvQAztD4iDtcDfjAlP8

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEsIlVzZXJOYW1lIjoiYWRtaW4iLCJleHAiOjE2NjYzMzY4OTIsIm5iZiI6MTY2NjMzNjgzMiwiaWF0IjoxNjY2MzM2ODMyLCJpc3MiOiJkb3RuZXRjaGluYSIsImF1ZCI6InBvd2VyYnkgRnVyaW9uIn0.fRCyXW0dz2Twzgo9oLT1JZDf4QmJ8Pd8UrF7i_-o-GA

输入图片说明

输入图片说明

我个人认为是你的前端出问题了,没有处理好。

你可以通过 postman 的方式测试。

输入图片说明
令牌未过期 访问没有权限访问的资源

输入图片说明
令牌已过期,访问没有权限访问的资源,但是携带了刷新令牌.服务端也返回了新的令牌和刷新令牌,但是状态码为401

200情况下,是正常的!

提供代码吧,不磨了,我也没时间对着你的截图一个代码一个代码的敲,做这些无用功

https://gitee.com/yjtyejintian/ademo

输入图片说明

1先登录获取令牌
2等待1分30秒左右(令牌过期时间为1分钟,刷新令牌为2分钟)
3点击访问 api403 按钮 状态码为401 ,此时它其实是携带了刷新令牌的!

收到,今晚或者明天早上就测试你这个。

毛豆 修改了描述
毛豆 修改了描述

直接将时间戳转换成时间显示更直观些

已修正:9c7f9ef

明天发布新版本。

测试结果:测试通过

输入图片说明


另外 index.html 内容也存在问题。

index.html 内容纠正:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <button class="loginbtn">登录</button>
    <button class="api200">访问api 200</button>
    <button class="api403">访问api 403</button>

    <div>
        <ul class="msg">
        </ul>
    </div>
    <script src="./axios.js"></script>
    <script>
        const loginbtn = document.querySelector('.loginbtn');
        const api200 = document.querySelector('.api200');
        const api403 = document.querySelector('.api403');
        const msgul = document.querySelector('.msg');

        const loginurl = 'http://localhost:5000/login';
        const api200url = 'http://localhost:5000/test1';
        const api403url = 'http://localhost:5000/test2';

        const accessTokenKey = "access-token";
        const refreshAccessTokenKey = `x-${accessTokenKey}`;

        const throwError = (message) => {
            throw new Error(message);
        };

        axios.interceptors.request.use(
            (conf) => {
                // 获取本地的 token
                const accessToken = window.localStorage.getItem(accessTokenKey);
                if (accessToken) {
                    // 将 token 添加到请求报文头中
                    conf.headers["Authorization"] = `Bearer ${accessToken}`;

                    // 判断 accessToken 是否过期
                    const jwt = decryptJWT(accessToken);
                    const exp = getJWTDate(jwt.exp);
                    if (new Date() >= exp) {
                        // 获取刷新 token
                        const refreshAccessToken = window.localStorage.getItem(
                            refreshAccessTokenKey
                        );
                        // 携带刷新 token
                        if (refreshAccessToken) {
                            conf.headers["X-Authorization"] = `Bearer ${refreshAccessToken}`;
                        }
                    }
                }
                // 这里编写请求拦截代码 =========================================
                return conf;
            },
            (error) => {
                // 处理请求错误
                if (error.request) {
                }
                // 这里编写请求错误代码
                return Promise.reject(error);
            }
        );

        // axios 响应拦截
        axios.interceptors.response.use(
            (res) => {
                // 检查并存储授权信息
                checkAndStoreAuthentication(res);

                // 处理规范化结果错误
                const serve = res.data;
                console.log(serve);
                if (serve && serve.hasOwnProperty("errors") && serve.errors) {
                    // 处理规范化 401 授权问题
                    if (serve.errors === "401 Unauthorized") {
                        clearAccessTokens();
                    }

                    throwError(
                        !serve.errors
                            ? "Request Error."
                            : typeof serve.errors === "string"
                                ? serve.errors
                                : JSON.stringify(serve.errors)
                    );
                    return;
                }

                // 这里编写响应拦截代码 =========================================

                return res;
            },
            (error) => {
                // 处理响应错误
                if (error.response) {
                    // 获取响应对象并解析状态码
                    const res = error.response;
                    const status = res.status;

                    // 检查并存储授权信息
                    checkAndStoreAuthentication(res);

                    // 检查 401 权限
                    if (status === 401) {
                        clearAccessTokens();
                    }
                }

                // 这里编写响应错误代码

                return Promise.reject(error);
            }
        );

        const clearAccessTokens = () => {
            window.localStorage.removeItem(accessTokenKey);
            window.localStorage.removeItem(refreshAccessTokenKey);

            // 刷新浏览器
            // window.location.reload();
            // 这里可以添加清除更多 Key =========================================
        };

        function checkAndStoreAuthentication(res) {
            // 读取响应报文头 token 信息
            var accessToken = res.headers[accessTokenKey];
            var refreshAccessToken = res.headers[refreshAccessTokenKey];

            // 判断是否是无效 token
            if (accessToken === "invalid_token") {
                clearAccessTokens();
            }
            // 判断是否存在刷新 token,如果存在则存储在本地
            else if (
                refreshAccessToken &&
                accessToken &&
                accessToken !== "invalid_token"
            ) {
                window.localStorage.setItem(accessTokenKey, accessToken);
                window.localStorage.setItem(refreshAccessTokenKey, refreshAccessToken);
            }
        }

        function decryptJWT(token) {
            token = token.replace(/_/g, "/").replace(/-/g, "+");
            var json = decodeURIComponent(escape(window.atob(token.split(".")[1])));
            return JSON.parse(json);
        }

        /**
         * 将 JWT 时间戳转换成 Date
         * @description 主要针对 `exp`,`iat`,`nbf`
         * @param timestamp 时间戳
         * @returns Date 对象
         */
        function getJWTDate(timestamp) {
            return new Date(timestamp * 1000);
        }

        loginbtn.addEventListener('click', () => {
            axios.post(loginurl, { username: 'zhangsan', password: '123456' }).then(res => {
                appenMsg('获取token完成');
            })
        })

        api200.addEventListener('click', () => {
            axios.get(api200url).then(res => {
                appenMsg(res.status);
            }).catch(err => {
                appenMsg(err.message)
            })
        })

        api403.addEventListener('click', () => {
            axios.get(api403url).then(res => {
                appenMsg(res.status);
            }).catch(err => {
                appenMsg(err.message)
            })
        })

        function appenMsg(msg) {
            const li = document.createElement('li');
            li.innerHTML = msg;
            msgul.appendChild(li);
        }
    </script>
</body>
</html>
百小僧 里程碑设置为4.6.8

下个版本发布,先关闭issue,如果新版本更新后还有问题再打开,明天发布。

Furion v4.6.8 版本已发布,升级到该版本,并替换你的 index.html 即可:

替换 index.html 为以下代码 点击展开
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <button class="loginbtn">登录</button>
    <button class="api200">访问api 200</button>
    <button class="api403">访问api 403</button>

    <div>
        <ul class="msg">
        </ul>
    </div>
    <script src="./axios.js"></script>
    <script>
        const loginbtn = document.querySelector('.loginbtn');
        const api200 = document.querySelector('.api200');
        const api403 = document.querySelector('.api403');
        const msgul = document.querySelector('.msg');

        const loginurl = 'http://localhost:5000/login';
        const api200url = 'http://localhost:5000/test1';
        const api403url = 'http://localhost:5000/test2';

        const accessTokenKey = "access-token";
        const refreshAccessTokenKey = `x-${accessTokenKey}`;

        const throwError = (message) => {
            throw new Error(message);
        };

        axios.interceptors.request.use(
            (conf) => {
                // 获取本地的 token
                const accessToken = window.localStorage.getItem(accessTokenKey);
                if (accessToken) {
                    // 将 token 添加到请求报文头中
                    conf.headers["Authorization"] = `Bearer ${accessToken}`;

                    // 判断 accessToken 是否过期
                    const jwt = decryptJWT(accessToken);
                    const exp = getJWTDate(jwt.exp);
                    if (new Date() >= exp) {
                        // 获取刷新 token
                        const refreshAccessToken = window.localStorage.getItem(
                            refreshAccessTokenKey
                        );
                        // 携带刷新 token
                        if (refreshAccessToken) {
                            conf.headers["X-Authorization"] = `Bearer ${refreshAccessToken}`;
                        }
                    }
                }
                // 这里编写请求拦截代码 =========================================
                return conf;
            },
            (error) => {
                // 处理请求错误
                if (error.request) {
                }
                // 这里编写请求错误代码
                return Promise.reject(error);
            }
        );

        // axios 响应拦截
        axios.interceptors.response.use(
            (res) => {
                // 检查并存储授权信息
                checkAndStoreAuthentication(res);

                // 处理规范化结果错误
                const serve = res.data;
                console.log(serve);
                if (serve && serve.hasOwnProperty("errors") && serve.errors) {
                    // 处理规范化 401 授权问题
                    if (serve.errors === "401 Unauthorized") {
                        clearAccessTokens();
                    }

                    throwError(
                        !serve.errors
                            ? "Request Error."
                            : typeof serve.errors === "string"
                                ? serve.errors
                                : JSON.stringify(serve.errors)
                    );
                    return;
                }

                // 这里编写响应拦截代码 =========================================

                return res;
            },
            (error) => {
                // 处理响应错误
                if (error.response) {
                    // 获取响应对象并解析状态码
                    const res = error.response;
                    const status = res.status;

                    // 检查并存储授权信息
                    checkAndStoreAuthentication(res);

                    // 检查 401 权限
                    if (status === 401) {
                        clearAccessTokens();
                    }
                }

                // 这里编写响应错误代码

                return Promise.reject(error);
            }
        );

        const clearAccessTokens = () => {
            window.localStorage.removeItem(accessTokenKey);
            window.localStorage.removeItem(refreshAccessTokenKey);

            // 刷新浏览器
            // window.location.reload();
            // 这里可以添加清除更多 Key =========================================
        };

        function checkAndStoreAuthentication(res) {
            // 读取响应报文头 token 信息
            var accessToken = res.headers[accessTokenKey];
            var refreshAccessToken = res.headers[refreshAccessTokenKey];

            // 判断是否是无效 token
            if (accessToken === "invalid_token") {
                clearAccessTokens();
            }
            // 判断是否存在刷新 token,如果存在则存储在本地
            else if (
                refreshAccessToken &&
                accessToken &&
                accessToken !== "invalid_token"
            ) {
                window.localStorage.setItem(accessTokenKey, accessToken);
                window.localStorage.setItem(refreshAccessTokenKey, refreshAccessToken);
            }
        }

        function decryptJWT(token) {
            token = token.replace(/_/g, "/").replace(/-/g, "+");
            var json = decodeURIComponent(escape(window.atob(token.split(".")[1])));
            return JSON.parse(json);
        }

        /**
         * 将 JWT 时间戳转换成 Date
         * @description 主要针对 `exp`,`iat`,`nbf`
         * @param timestamp 时间戳
         * @returns Date 对象
         */
        function getJWTDate(timestamp) {
            return new Date(timestamp * 1000);
        }

        loginbtn.addEventListener('click', () => {
            axios.post(loginurl, { username: 'zhangsan', password: '123456' }).then(res => {
                appenMsg('获取token完成');
            })
        })

        api200.addEventListener('click', () => {
            axios.get(api200url).then(res => {
                appenMsg(res.status);
            }).catch(err => {
                appenMsg(err.message)
            })
        })

        api403.addEventListener('click', () => {
            axios.get(api403url).then(res => {
                appenMsg(res.status);
            }).catch(err => {
                appenMsg(err.message)
            })
        })

        function appenMsg(msg) {
            const li = document.createElement('li');
            li.innerHTML = msg + "~~ ~~" + (new Date().toLocaleString());
            msgul.appendChild(li);
        }
    </script>
</body>
</html>

登录 后才可以发表评论

状态
负责人
里程碑
Pull Requests
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
开始日期   -   截止日期
-
置顶选项
优先级
参与者(3)
974299 monksoul 1578937227 5139039 yjtyejintian 1584241537 61753 zuohuaijun 1686997111
C#
1
https://gitee.com/dotnetchina/Furion.git
git@gitee.com:dotnetchina/Furion.git
dotnetchina
Furion
Furion

搜索帮助