AngularJs - 在等待新令牌时不跳过请求

时间:2014-04-30 08:47:41

标签: javascript angularjs

我已经实现了身份验证系统,从角度1.0.8升级到1.2.x后, 系统不像以前那样工作。当用户登录时获取令牌。令牌过期时 调用新令牌的刷新功能。在服务器上成功创建了新令牌,它是 存储到数据库。但是客户端没有获得这个新令牌,所以它再次请求一个新令牌, 并一次又一次地退出。服务器端(MVC Web Api)工作正常,所以问题必须 在客户端。问题必须在重试队列上。下面我贴了相关的代码和 两个版本的应用程序(1.0.8和1.2.x)的控制台跟踪。 我现在正在努力奋斗这几天,我无法弄明白。

在下面的链接中,有5个相关的代码块:

  • interceptor.js(用于拦截请求,两个版本)
  • retryQueue.js(管理重试请求队列)
  • security.js(管理重试队列项的处理程序并从api获取新令牌)
  • httpHeaders.js(设置标题)
  • tokenHandler.js(处理Cookie中的令牌)

代码:http://pastebin.com/Jy2mzLgj

应用程序的控制台跟踪角度1.0.8:http://pastebin.com/aL0VkwdN

和角度1.2.x:http://pastebin.com/WFEuC6WB

interceptor.js(angular 1.2.x version)

angular.module('security.interceptor', ['security.retryQueue'])
.factory('securityInterceptor', ['$injector', 'securityRetryQueue', '$q',
     function ($injector, queue, $q) {
        return {
            response: function(originalResponse) {
                return originalResponse;
            },
            responseError: function (originalResponse) {
                var exception;
                if (originalResponse.headers){
                    exception = originalResponse.headers('x-eva-api-exception');
                }
                if (originalResponse.status === 401 && 
                   (exception === 'token_not_found' || 
                    exception === 'token_expired')){
                    queue.pushRetryFn(exception, function retryRequest() {
                        return $injector.get('$http')(originalResponse.config);
                    });
                }
                return $q.reject(originalResponse);
            }
        };
     }])
     .config(['$httpProvider', function($httpProvider) {
         $httpProvider.interceptors.push('securityInterceptor');
     }]);

retryQueue.js

angular.module('security.retryQueue', [])
.factory('securityRetryQueue', ['$q', '$log', function($q, $log) {
    var retryQueue = [];
var service = {
        onItemAddedCallbacks: [],
        hasMore: function(){
            return retryQueue.length > 0;
        },
        push: function(retryItem){
            retryQueue.push(retryItem);
            angular.forEach(service.onItemAddedCallbacks, function(cb) {
                try {
                    cb(retryItem);
                } 
                catch(e){
                     $log.error('callback threw an error' + e);
                }
            });
        },
        pushRetryFn: function(reason, retryFn){
            if ( arguments.length === 1) {
                retryFn = reason;
                reason = undefined;
            }
            var deferred = $q.defer();
            var retryItem = {
                reason: reason,
                retry: function() {
                    $q.when(retryFn()).then(function(value) {
                        deferred.resolve(value);
                    }, function(value){
                        deferred.reject(value);
                    });
                },
                cancel: function() {
                    deferred.reject();
                }
            };
            service.push(retryItem);
            return deferred.promise;
        },
        retryAll: function() {
            while(service.hasMore()) {
                retryQueue.shift().retry();
            }
        }
    };
    return service;
}]);

security.js

angular.module('security.service', [
'session.service',
'security.signin',
'security.retryQueue',
'security.tokens',
'ngCookies'
])
.factory('security', ['$location', 'securityRetryQueue', '$q', /* etc. */ function(){
     var skipRequests = false;      
     queue.onItemAddedCallbacks.push(function(retryItem) {
         if (queue.hasMore()) {
             if(skipRequests) {return;}
             skipRequests = true;
             if(retryItem.reason === 'token_expired') {
                 service.refreshToken().then(function(result) {
                     if(result) { queue.retryAll(); }
                     else {service.signout(); }
                     skipRequests = false;
                 });
             } else {
                 skipRequests = false;
                 service.signout();
             }
         }
     });

     var service = {
         showSignin: function() {
             queue.cancelAll();
             redirect('/signin');
         },
         signout: function() {
             if(service.isAuthenticated()){
                 service.currentUser = null;
                 TokenHandler.clear();
                 $cookieStore.remove('current-user');
                 service.showSignin();
             }
         },
         refreshToken: function() {
             var d = $q.defer();
             var token = TokenHandler.getRefreshToken();
             if(!token) { d.resolve(false); }
             var session = new Session({ refreshToken: token });
             session.tokenRefresh(function(result){
                 if(result) { 
                     d.resolve(true); 
                     TokenHandler.set(result);
                 } else {
                     d.resolve(false);
                 }
             });
             return d.promise;
         }
     };

    return service;
}]);

session.service.js

angular.module('session.service', ['ngResource'])

    .factory('Session', ['$resource', '$rootScope', function($resource, $rootScope) {
     var Session = $resource('../api/tokens', {}, {
        create: {method: 'POST'}
    });

    Session.prototype.passwordSignIn = function(ob) {
        return Session.create(angular.extend({
                       grantType: 'password', 
                       clientId: $rootScope.clientId
                }, this), ob);
    };

    Session.prototype.tokenRefresh = function(ob) {
        return Session.create(angular.extend({
                       grantType: 'refresh_token', 
                       clientId: $rootScope.clientId
                    }, this), ob);
    };

    return Session;
}]);

感谢@Zerot的建议和代码示例,我不得不改变拦截器的一部分:

if (originalResponse.status === 401 && 
    (exception === 'token_not_found' || exception === 'token_expired')){
    var defer = $q.defer();

    queue.pushRetryFn(exception, function retryRequest() {
            var activeToken = $cookieStore.get('authorization-token').accessToken;
            var config = originalResponse.config;
            config.headers.Authorization = 'Bearer ' + activeToken;
            return $injector.get('$http')(config)
                    .then(function(res) {
                            defer.resolve(res);
                    }, function(err)
                    {
                            defer.reject(err);
                    });
    });

    return defer.promise;
}

非常感谢, 贾尼

3 个答案:

答案 0 :(得分:2)

拦截器应该总是回复承诺。

因此,在responseError中,您应该更好return $q.reject(originalResponse);而不仅仅是return originalResponse

希望这有帮助

答案 1 :(得分:2)

您是否尝试修复1.2日志中的错误?

  

错误:[ngRepeat:dupes]不允许在转发器中重复。使用'track by'表达式指定唯一键。 Repeater:在client.projects中的项目,Duplicate键:string:e

该错误正好在您需要查看$ httpHeaders设置行的位置。看起来你的session.tokenrefresh不起作用(而且这个代码也从pastebin中丢失,所以我无法检查。)

答案 2 :(得分:0)

我认为你的拦截器在errorResponse方法中返回错误的结果。

出于同样的原因,我遇到了一些"undebuggable" issue。 使用此代码,您可能会遇到一些无限循环流... 希望这会有所帮助。