如何推迟StateChangeStart事件,直到ui-router完成解析?

6
我有一个使用授权逻辑和UI路由的Angular应用程序,以禁止未经授权的用户访问某些状态/视图。我遵循监听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),然后我的resolve加载存储的会话数据,往往确定用户一直被授权。如果我可以在事件触发之前要求执行resolve,那么我就成功了。
有什么想法吗?
顺便说一句,这里有一个类似的未得到答复的SO问题:延迟Angular UI Router $stateChangeStart,直到接收到服务器授权响应

1
这个答案可能会有帮助:https://dev59.com/V3vaa4cB1Zd3GeqPA0Cb#21098923 - TheSharpieOne
4个回答

1
事实证明,我所需要做的就是将配置数据的加载移动到.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);

  });

}]);

0

虽然来晚了,但我认为这会有所帮助。

$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)。


0

我在另一个答案这里中找到了一种很好的异步解决数据的方法,可以在$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;
    }
};

-1

这是客户端安全性,您可以在常规的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]);
                     },
                 }
             })
    });

1
嗯,原始问题要求解决ui-router的问题,而你在回答中使用了ngRoute。我认为这不是作者所寻求的。 - treejanitor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接