【问题标题】:How to Defer a StateChangeStart event until after a ui-router resolve executes如何将 StateChangeStart 事件推迟到 ui-router 解析执行后
【发布时间】:2014-11-06 03:02:24
【问题描述】:

我有一个 Angular 应用程序,它使用授权逻辑和 ui-router 来禁止未经授权的用户访问某些状态/视图。我遵循监听 stateChange 事件的标准方法,这会触发我的授权逻辑。这一切都很好,直到可怕的页面重新加载。

我将会话数据(包括授权状态)存储在本地存储中,以便在重新加载页面时,我可以使用 ui-router 中的父状态在尝试更改视图之前首先从本地存储解析/获取授权状态。下面是我的app父状态对象的配置:

$stateProvider.
state('app', {
  url: '/app',
  abstract: true,
  controller: 'appCtrl',
  data: {
    authorizedRoles: [USER_ROLES.all]
  },
  templateUrl: 'partials/app.html',
  resolve: {

    //Try to restore from the previous session before loading any of the app child states
    RestoredSession: ['SessionService',
             function(SessionService){
                return SessionService.restoreSession();
              }]
  }
})

...various app. child states

这是我的 onStateChange 监听器:

//listen for a ui.router $stateChangeStart event and test the new path to see if the currentUser
//is authorized to view that page

.run(         ['$rootScope', 'AUTH_EVENTS', 'SessionService', 
  function ($rootScope,   AUTH_EVENTS,   SessionService) {

  $rootScope.$on('$stateChangeStart', function (event, next) {
    var authorizedRoles = next.data.authorizedRoles;
    //If the requested page allows guest access, then continue to stateChange
    if (authorizedRoles.indexOf('guest') !== -1 || authorizedRoles.indexOf('*') !== -1) return;

    //If the requested page requires authorization, check login and auth privileges
    if (!SessionService.isAuthorized(authorizedRoles)) {

      event.preventDefault();

      if (SessionService.existingSession()) {

        // user is not allowed
        $rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
        console.log("User attempted to access page for which he is not authorized");

      } else {

        // user is not logged in
        $rootScope.$broadcast(AUTH_EVENTS.notLoggedIn);
        console.log("User attempted to access page when he is not logged in");

      }
    }
  });

}]);

我的问题是stateChangeStart 事件在app resolve 之前触发,因此侦听器停止状态更改(通过event.preventDefault),然后我的解析加载存储的会话数据,这通常确定用户一直被授权。如果我可以要求在事件触发之前执行解析,那么我会很高兴。

有什么想法吗???

顺便说一句,这里有一个类似的未回答的 SO 问题:Defer Angular UI Router $stateChangeStart until server authorization response receieved

【问题讨论】:

标签: angularjs angular-ui-router


【解决方案1】:

来晚了一点,但我认为这会有所帮助。

$on 方法为监听器返回一个注销函数。这允许在侦听器中进行自定义处理之前取消事件。

var setInterceptedListener = function($scope) {
    var removeListener = $rootScope.$on('$stateChangeStart',
        function (event, toState, toParams, fromState, fromParams) {   
            // cancel state change
            event.preventDefault();

            // mock prompt for user input
            Prompt.continue('Continue?').then(function(result) {
                // if yes then deregister the listener in order to proceed.
                if (result == 'yes') {
                    removeListener();
                    $state.go(toState, toParams);
                }
            });
        });

        // deregister on scope teardown
        $scope.$on("$destroy", removeListener);
    };

要使用它,只需将此方法添加到服务并调用 setInterceptedListener($scope)。

【讨论】:

    【解决方案2】:

    我在另一个答案here 中找到了在$stateChangeStart 期间异步解析数据的好方法。代码如下:

    rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState) {
    
        if (dataService.isInitialized()) {
            proceedAsUsual();
        } 
        else {
    
            event.preventDefault();
    
            dataService.intialize().success(function () {
                    $state.go(toState, toParams);
            });
        }
    });
    

    然后你可以只记得你的数据已经在服务中以你喜欢的方式初始化了,例如:

    function dataService() {
    
        var initialized = false;
    
        return {
            initialize: initialize,
            isInitialized: isInitialized
        }
    
        function intialize() {
    
            return $http.get(...)
                        .success(function(response) {
                                initialized=true;
                        });
    
        }
    
        function isInitialized() {
            return initialized;
        }
    };
    

    【讨论】:

      【解决方案3】:

      事实证明,我需要做的只是将配置数据的加载移动到 .run() 块,而不是尝试在 parent app 状态的 resolve 中进行。

      //listen for a ui.router $stateChangeStart event and test the new path to see if the currentUser
      //is authorized to view that page
      .run(         ['$rootScope', 'AUTH_EVENTS','SessionService', 'localStorageService',
        function ($rootScope,   AUTH_EVENTS,  SessionService,   localStorageService) 
        {
        $rootScope.$on('$stateChangeStart', function (event, next) {
      
          //function to check to see if the currentUser has one of the required roles to authorize the next state.
          var checkAuthorization = function(authorizedRoles){
      
               //If the requested page allows guest access, then continue to stateChange
               if (authorizedRoles.indexOf('guest') !== -1 || authorizedRoles.indexOf('*') !== -1) return;
               //If the requested page requires authorization, check login and auth privileges
               if (!SessionService.isAuthorized(authorizedRoles)) {
                 event.preventDefault();
                 if (SessionService.existingSession()) {
                   // user is not allowed
                   $rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
                   console.log("User attempted to access page for which he is not authorized");
                 } else {
                   // user is not logged in
                   $rootScope.$broadcast(AUTH_EVENTS.notLoggedIn);
                   console.log("User attempted to access page when he is not logged in");
                 }
               }
             };
      
          //Before calling checkAuthorization(), test to see if the state change was triggered by a reload
          //If so, load config data before triggering the `checkAuthorization()` function.
          if (SessionService.freshLoad === true  || typeof SessionService.freshLoad === 'undefined'){
            SessionService.freshLoad = false;
            var storedUser = localStorageService.get('currentUser');
      
            //If we have a stored user but no existing session, then we know that we have stored
            //user data to reload before the checkAuthorization() function.
            if (typeof storedUser !== "undefined" && storedUser !== null && !SessionService.existingSession()) {
              SessionService.restoreSession();
            }
          }
      
        checkAuthorization(next.data.authorizedRoles);
      
        });
      
      }]);
      

      【讨论】:

        【解决方案4】:

        这是您可以在常规 Angular 版本中实现的客户端安全性。我已经尝试并测试了这一点。 (请在这里找到我的文章:- http://www.codeproject.com/Tips/811782/AngularJS-Routing-Security )。 除了客户端路由安全之外,您还需要保护服务器端的访问。客户端安全有助于避免额外往返服务器。但是,如果有人欺骗了浏览器,那么服务器服务器端安全应该能够拒绝未经授权的访问。

        希望这会有所帮助!

        第 1 步:在 app-module 中定义全局变量

        -为应用程序定义角色

          var roles = {
                superUser: 0,
                admin: 1,
                user: 2
            };
        

        -为应用程序定义未经授权访问的路由

         var routeForUnauthorizedAccess = '/SomeAngularRouteForUnauthorizedAccess';
        

        第 2 步:定义授权服务

        appModule.factory('authorizationService', function ($resource, $q, $rootScope, $location) {
            return {
            // We would cache the permission for the session, to avoid roundtrip to server for subsequent requests
            permissionModel: { permission: {}, isPermissionLoaded: false  },
        
            permissionCheck: function (roleCollection) {
            // we will return a promise .
                    var deferred = $q.defer();
        
            //this is just to keep a pointer to parent scope from within promise scope.
                    var parentPointer = this;
        
            //Checking if permisison object(list of roles for logged in user) is already filled from service
                    if (this.permissionModel.isPermissionLoaded) {
        
            //Check if the current user has required role to access the route
                            this.getPermission(this.permissionModel, roleCollection, deferred);
        } else {
            //if permission is not obtained yet, we will get it from  server.
            // 'api/permissionService' is the path of server web service , used for this example.
        
                            $resource('/api/permissionService').get().$promise.then(function (response) {
            //when server service responds then we will fill the permission object
                            parentPointer.permissionModel.permission = response;
        
            //Indicator is set to true that permission object is filled and can be re-used for subsequent route request for the session of the user
                            parentPointer.permissionModel.isPermissionLoaded = true;
        
            //Check if the current user has required role to access the route
                            parentPointer.getPermission(parentPointer.permissionModel, roleCollection, deferred);
        }
                        );
        }
                    return deferred.promise;
        },
        
                //Method to check if the current user has required role to access the route
                //'permissionModel' has permission information obtained from server for current user
                //'roleCollection' is the list of roles which are authorized to access route
                //'deferred' is the object through which we shall resolve promise
            getPermission: function (permissionModel, roleCollection, deferred) {
                var ifPermissionPassed = false;
        
                angular.forEach(roleCollection, function (role) {
                    switch (role) {
                        case roles.superUser:
                            if (permissionModel.permission.isSuperUser) {
                                ifPermissionPassed = true;
                            }
                            break;
                        case roles.admin:
                            if (permissionModel.permission.isAdministrator) {
                                ifPermissionPassed = true;
                            }
                            break;
                        case roles.user:
                            if (permissionModel.permission.isUser) {
                                ifPermissionPassed = true;
                            }
                            break;
                        default:
                            ifPermissionPassed = false;
                    }
                });
                if (!ifPermissionPassed) {
                    //If user does not have required access, we will route the user to unauthorized access page
                    $location.path(routeForUnauthorizedAccess);
                    //As there could be some delay when location change event happens, we will keep a watch on $locationChangeSuccess event
                    // and would resolve promise when this event occurs.
                    $rootScope.$on('$locationChangeSuccess', function (next, current) {
                        deferred.resolve();
                    });
                } else {
                    deferred.resolve();
                }
            }
        
        };
        });
        

        第 3 步:在路由中使用安全性:让我们使用迄今为止完成的所有难点来保护路由

        var appModule = angular.module("appModule", ['ngRoute', 'ngResource'])
            .config(function ($routeProvider, $locationProvider) {
                $routeProvider
                    .when('/superUserSpecificRoute', {
                        templateUrl: '/templates/superUser.html',//path of the view/template of route
                        caseInsensitiveMatch: true,
                        controller: 'superUserController',//angular controller which would be used for the route
                        resolve: {//Here we would use all the hardwork we have done above and make call to the authorization Service 
                            //resolve is a great feature in angular, which ensures that a route controller(in this case superUserController ) is invoked for a route only after the promises mentioned under it are resolved.
                            permission: function(authorizationService, $route) {
                                return authorizationService.permissionCheck([roles.superUser]);
                            },
                        }
                    })
                .when('/userSpecificRoute', {
                    templateUrl: '/templates/user.html',
                    caseInsensitiveMatch: true,
                    controller: 'userController',
                    resolve: {
                        permission: function (authorizationService, $route) {
                            return authorizationService.permissionCheck([roles.user]);
                        },
                    }
                   })
                     .when('/adminSpecificRoute', {
                         templateUrl: '/templates/admin.html',
                         caseInsensitiveMatch: true,
                         controller: 'adminController',
                         resolve: {
                             permission: function(authorizationService, $route) {
                                 return authorizationService.permissionCheck([roles.admin]);
                             },
                         }
                     })
                     .when('/adminSuperUserSpecificRoute', {
                         templateUrl: '/templates/adminSuperUser.html',
                         caseInsensitiveMatch: true,
                         controller: 'adminSuperUserController',
                         resolve: {
                             permission: function(authorizationService, $route) {
                                 return authorizationService.permissionCheck([roles.admin,roles.superUser]);
                             },
                         }
                     })
            });
        

        【讨论】:

        • 嗯,最初的问题要求为ui-router 提供解决方案,而您在答案中使用了ngRoute。我不认为这是作者想要的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-25
        • 2014-09-23
        • 2015-06-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多