JWT 刷新后运行 AJAX 请求

问题描述 投票:0回答:2

我有一个现有的单页应用程序,我已将其转换为 JWT 而不是 PHP 会话。它运行良好,但我想弄清楚如何在 AJAX 请求之前刷新 JWT(如有必要)。

一点背景:用户登录后,我存储一个 JWT(10 分钟到期)和一个刷新令牌(2 小时到期)。我有以下函数,我想在对 API 的每次 AJAX 调用之前运行。该函数检查 JWT 是否已过期,如果已过期,它会通过刷新令牌检索新的 JWT。显然,在 API 中,我在允许数据发回之前检查了 JWT 和刷新令牌的有效性。这部分工作完美。

    function checkTokenExp(){
     var validToken = false;   // DEFAULT TO EXPIRED
     var apiClaims = localStorage.getItem('apiToken').split('.');
     var api = JSON.parse(atob(apiClaims[1]));
     var apiExpiration = new Date(api['exp'] * 1000);
     var now = new Date();
     if (apiExpiration < now) {
       // API TOKEN EXPIRED - CHECK REFRESH TOKEN
       var refreshClaims = localStorage.getItem('refreshToken').split('.');
       var refresh = JSON.parse(atob(refreshClaims[1]));
       var refreshExpiration = new Date(refresh['exp'] * 1000);
       if (refreshExpiration > now) {
         //  REFRESH TOKEN NOT EXPIRED - NEED NEW API TOKEN
         $.ajax({
           type: "GET",
           url: 'api/session/token',
           contentType: 'application/json',
           beforeSend: function (xhr) {
             xhr.setRequestHeader("Authorization", 'Bearer '+localStorage.getItem('refreshToken'));
           }
         }).done(function (data) {
           var data = jQuery.parseJSON(data);
           if (data.status == 'success'){
             localStorage.setItem('apiToken', data.apiToken);
           }
           validToken = true;
         });
       }else{
         //  REFRESH TOKEN EXPIRED - FORCE LOG OUT
         $("#SessionExpiredModal").modal('show');
       }
     }else{
       //  API TOKEN NOT EXPIRED
       validToken = true;
     }
     return validToken;
   }

我遇到麻烦的地方是下面的代码(以及以后的所有其他 AJAX 调用)。如您所见,我正在尝试在此 AJAX 调用之前运行上面的函数并验证我的 JWT(如果它无效,则请求并存储一个新的),然后使用新的 JWT 运行 AJAX 调用。一切正常,除了 AJAX 返回 401 未经授权,因为它仍在发送以前的 JWT(显然已过期)。因此,似乎在上述功能完成并存储新的 JWT 之前发送了 AJAX。但我知道我的功能运行正常并且 JWT 已更新,因为我第二次单击按钮时它运行正常并且我从 API 得到了正确的回复。

$('#BtnTest').on('click', function(){
     $.ajax({
      type: "GET",
      url: 'api/random',
      contentType: 'application/json',
      beforeSend: function (xhr) {
        checkTokenExp();  // SHOULD BE SAVING NEW TOKEN
        xhr.setRequestHeader("Authorization", 'Bearer '+ localStorage.getItem('apiToken'));  // SHOULD BE SENDING NEW TOKEN
      }
    }).done(function (response) {
      alert(response);
    })
   });

我希望这是有道理的,并提前感谢您的帮助。另外,有没有一种简单的方法可以让我的函数在每次 ajax 调用之前运行,而不必在每个 AJAX 语句中对其进行编码。

javascript jquery ajax token jwt
2个回答
0
投票

您需要向

checkTokenExp()
添加回调,以便在刷新 API 令牌后完成 API 调用。

现在发生了什么:

  1. 你准备打电话给
    api/random
  2. beforeSend
    执行
    checkTokenExp()
  3. 如果令牌过期,它会请求另一个异步
  4. api/random
    的实际调用可能在服务器用新令牌回复第3步之前

看看 jQuery 的 ajaxPrefilter

你可以尝试这样的事情:

var currentRequests = {};

$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
    // Skip URLs that don't need a token
    if( options.url == 'your api/session/token URL' ) {
        console.log("No ajax prefilter on api/session/token");
        return;
    }

    successCb = function() {
        currentRequests[ options.url ] = jqXHR;
    };
    errorCb = function() {
        console.error("Could not refresh token, aborting Ajax call to " + options.url);
        currentRequests[ options.url ].abort();
    };
    checkTokenExp(successCb, errorCb);
});



function checkTokenExp(successCb, errorCb){
    // ...
    if (refreshExpiration > now) {
        //  REFRESH TOKEN NOT EXPIRED - NEED NEW API TOKEN
        $.ajax({
            type: "GET",
            url: 'api/session/token',
            contentType: 'application/json',
        }).done(function (data) {
            var data = jQuery.parseJSON(data);
            if (data.status == 'success'){
                localStorage.setItem('apiToken', data.apiToken);
                successCb(); // the new token is set, you can make ajax calls
            }
            // TODO: log error here, don't do validToken = true
        }).fail(errorCb(data));
    }else{
        //  REFRESH TOKEN EXPIRED - FORCE LOG OUT
        $("#SessionExpiredModal").modal('show');
    }
    }else{
        //  API TOKEN NOT EXPIRED
        successCb();
    }
}

0
投票

在 JQuery 中,使用 Ajax Prefilter

function setUpAjaxPrefilter() {
if (!ajaxPrefiltered) {
    ajaxPrefiltered = true; // once
    const origOptsList = [];

    $.ajaxPrefilter(function onPrefilter(opts, ajaxOpts, jqXHR) {
        if (opts.authRefresh) {
            return; // don't do it again
        }
        const dfd = $.Deferred(); 
        // if the non refresh request was done just resolve our deferred
        jqXHR.done(dfd.resolve); 
        // if the request failed, handle

        jqXHR.fail(function onAjaxFail(...args) {
            const { status, url } = jqXHR;
            // keep list of failed requests while refresh token request still outstanding
            origOptsList.push(ajaxOpts);

            window.XHR = {
                ...jqXHR,
                ...opts,
            }; 
            // keep copy of XHR in window global for logging purpose
            const { browserSession } = APPLICATION;

            if (!url && !refreshing && browserSession && status === 401) {
                refreshing = true;
                return browserSession
                    .refresh()
                    .then((response) => {
                        refreshing = false;
                        const { access_token: accessToken } = response;

                        // retry failed requests
                        const retries = [];
                        while (origOptsList.length && accessToken) {
                            const origOpts = origOptsList.shift();
                            const newOpts = {
                                ...origOpts,
                                authRefresh: true, // prevent infinite loop in case of bad token
                                headers: { Authorization: `Bearer ${accessToken}` },
                            };

                            retries.push($.ajax(newOpts).then(dfd.resolve, dfd.reject));
                        }
                        return Promise.allSettled(retries);
                    })
                    .catch((error) => {
                        refreshing = false;

                        jqXHR.status = error.status || 401;
                        jqXHR.statusText = error.message || 'token expired';
                        dfd.rejectWith(jqXHR, [...args]);
                    });
            } else {
                if (jqXHR.status === 200) {
                    return dfd.resolve(args);
                    // status cancel happens when beforeSend cancels $.ajax request
                } else if (jqXHR.statusText && !jqXHR.statusText.includes('cancel')) {
                    // eslint-disable-next-line no-console
                    console.error(jqXHR, jqXHR.responseText);
                }
                return dfd.rejectWith(jqXHR, [...args]);
            }
        });
    });
}

}

© www.soinside.com 2019 - 2024. All rights reserved.