在刷新身份验证令牌时处理多个未经授权的请求



当对API的调用返回401 "Unauthorized"时,使用JWT进行API身份验证的Angular应用程序会启动登录对话框,让用户输入凭据并获得新的有效JWT。然后应用程序重试失败的未经授权的请求并保持流。

此处列出的代码基于Chris Clarke的此解决方案。

.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(['$q', '$location', '$injector', function ($q, $location, $injector) {
    return {
        'responseError': function(response) {
            // Keep the response waiting until we get new JWT
            var deferred = $q.defer();
            if (response.status === 401 && response.data.error && response.data.error.message.toLowerCase() === "unauthorized") {
                // JWT has expired
                // Open login dialog
                var cslAuth = $injector.get('cslAuth');
                if (cslAuth.isLoggedIn()) {
                    // Logout user, next pending request will not trigger auth dialog
                    cslAuth.logout();
                    $injector.get('ngDialog').openConfirm({
                        template: 'web_app/views/login.html',
                        className: 'ngdialog-theme-default',
                        showClose: false,
                        controller: 'LoginCtrl',
                        cache: false
                    })
                    .then(
                        function(value) {
                            // JWT has been refreshed. Try pending request again
                            var config = response.config;
                            // Inject the new token in the Auth header
                            config.headers.Authentication = cslAuth.getTokenHeader();
                            $injector.get("$http")(config).then(
                                function(response){
                                    deferred.resolve(response);
                                },
                                function(response) {
                                    deferred.reject();
                                }
                            );
                        },
                        function(value) {
                            deferred.reject();
                        }
                    );
                }
            } else {
                return $q.reject(response);
            }
            // Return a promise while waiting for auth refresh
            return deferred.promise;
        }
    }
}])
}])

问题是当有多个请求使用过期的令牌时。第一个返回应该触发登录对话框并获得新的令牌。但是,如何让其他挂起的请求等待新令牌可用?可以设置一个标志来告诉以下所有传入响应正在请求新令牌。可以返回promise,并且所有配置对象都可以存储在Service的数组中。当新令牌可用时,可以重试所有等待的请求。但是,在新令牌可用后返回未经授权的请求会发生什么?它们将触发一个新的登录对话框。

一些人注意到一个额外的:

  • 这个答案提供了一个相关问题的解决方案,但由于这里涉及到一个新的登录,我不知道如何将解决方案适应这种情况。

  • 这不是一个选项来自动更新令牌。令牌将有8小时的到期时间(一个工作会话),并且必须进行新登录。

  • 在配置对象中注入服务(此处为cslAuth$http)是否安全?我的代码正在工作,但我已经阅读了它们目前还不能完全准备好

这段代码从两个方面改进了问题中发布的代码:

  • 保留挂起请求的数组,然后在成功刷新令牌后重试
  • 在401上,它检查所使用的身份验证令牌是否与当前令牌不同,以便在不要求登录的情况下重试请求。这解决了pendingRequests数组中不包含请求的问题

代码:

/ HTTP Interceptors
.config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push(['$q', '$location', '$injector', function ($q, $location, $injector) {
        var pendingRequests = [];
        function retryRequest(deferred, config, cslAuth) {
            config.headers.Authentication = cslAuth.getTokenHeader();
            $injector.get("$http")(config).then(
                function(response){
                    deferred.resolve(response);
                },
                function(response) {
                    deferred.reject();
                }
            );
        }
        return {
            'responseError': function(response) {
                switch (response.status) {
                    case 401: // JWT has expired
                        // To keep the response waiting until we get new JWT
                        var deferred = $q.defer();
                        var cslAuth = $injector.get('cslAuth');
                        // Check if a new token exists. Then retry the request with new token
                        if (response.config.headers.Authentication != cslAuth.getTokenHeader()) {
                            retryRequest(deferred, response.config, cslAuth);
                            // Return a promise while waiting
                            return deferred.promise;
                        }
                        // Open login dialog
                        if (cslAuth.isLoggedIn()) {
                            // Logout user, next pending request will not trigger auth dialog
                            cslAuth.logout();
                            $injector.get('ngDialog').openConfirm({
                                template: 'web_app/views/login-inner.html',
                                className: 'ngdialog-theme-default',
                                showClose: false,
                                controller: 'LoginCtrl'
                            })
                            .then(
                                function(value) {
                                    // JWT has been refreshed. Try pending requests again
                                    for (var i = 0; i < pendingRequests.length; i++) {
                                        retryRequest(pendingRequests[i].deferred, pendingRequests[i].config, cslAuth);
                                    }
                                },
                                function(value) {
                                    pendingRequests[i].deferred.reject();
                                }
                            );
                        }
                        // Return a promise while waiting for auth refresh
                        pendingRequests.push({'deferred': deferred, 'config': response.config});
                        return deferred.promise;
                        break;
                    default: // What happened?
                        return $q.reject(response);
                        break;
                }
            }
        }
    }])
}])

最新更新