Angularjs 如何将StateChangeStart事件延迟到ui路由器解析执行之后
我有一个angular应用程序,它使用授权逻辑和ui路由器禁止未经授权的用户访问某些状态/视图。我遵循侦听stateChange事件的标准方法,该事件触发我的授权逻辑。这一切都很好,直到可怕的页面重新加载 我将会话数据(包括授权状态)存储在本地存储中,以便在页面重新加载时,我可以使用ui路由器中的父状态,在尝试更改视图之前,首先从本地存储解析/获取授权状态。下面是myAngularjs 如何将StateChangeStart事件延迟到ui路由器解析执行之后,angularjs,angular-ui-router,Angularjs,Angular Ui Router,我有一个angular应用程序,它使用授权逻辑和ui路由器禁止未经授权的用户访问某些状态/视图。我遵循侦听stateChange事件的标准方法,该事件触发我的授权逻辑。这一切都很好,直到可怕的页面重新加载 我将会话数据(包括授权状态)存储在本地存储中,以便在页面重新加载时,我可以使用ui路由器中的父状态,在尝试更改视图之前,首先从本地存储解析/获取授权状态。下面是myapp父状态对象的配置: $stateProvider. state('app', { url: '/app', abst
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
之前触发,这样侦听器停止状态更改(通过事件.preventDefault
),然后我的resolve加载存储的会话数据,这通常会确定用户一直都是经过授权的。如果我可以要求在事件触发之前执行解析,那么我就是黄金
有什么想法吗
顺便说一句,这里有一个类似的SO问题没有得到回答:这是客户端安全性,可以在常规版本中实现。我已经试过并测试过了。(请在这里找到我的文章:- ). 除了客户端路由安全之外,您还需要确保服务器端的访问安全。客户端安全性有助于避免到服务器的额外往返。但是,如果有人欺骗浏览器,那么服务器端安全应该能够拒绝未经授权的访问 希望这有帮助 步骤1:在应用程序模块中定义全局变量 -定义应用程序的角色
var roles = {
superUser: 0,
admin: 1,
user: 2
};
var routeForUnauthorizedAccess = '/SomeAngularRouteForUnauthorizedAccess';
-为未经授权的应用程序访问定义路由
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();
}
}
};
});
第三步:在路由中使用安全性:让我们使用我们迄今为止所做的所有努力来保护路由
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]);
},
}
})
});
事实证明,我所需要做的只是将配置数据的加载移动到
.run()
块,而不是尝试在父应用程序的状态的解析中进行加载
//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);
});
}]);
在另一个答案中,我找到了在$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;
}
};
来晚了一点,但我想这会有帮助的
$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)。此答案可能会有所帮助:嗯,原来的问题是询问ui路由器的解决方案,您在回答中使用了ngRoute
。我认为这不是作者想要的。