AngularJS 安全 - Angular 路由的授权






4.83/5 (18投票s)
在 Angular 中保护路由是无法开箱即用的。 本技巧将帮助保护基于 Angular 的应用程序中的路由。
引言
自 AngularJS 推出以来,它已经取得了长足的进步。 它是一个用于单页应用程序 (SPA) 开发的综合 JavaScript 框架。 它有一些很棒的功能,例如双向绑定、指令等。 本主题将重点关注 Angular 安全性。 这是客户端安全性,您可以使用 Angular 实现。 除了客户端路由安全性(我们将在本技巧中讨论)之外,您还需要保护服务器端的访问。 客户端安全性有助于避免在客户端创建用户会话对象后进行额外的往返服务器。 但是,如果有人欺骗浏览器,那么服务器端安全性应该能够拒绝未经授权的访问。 在本技巧中,我将我的讨论限制在客户端安全性。
我们将创建一个授权服务,该服务将从服务器获取有关用户角色的用户授权/角色特定数据。 之后,此用户角色数据将用于用户在其会话期间访问的所有路由。 您会注意到关于 Angular 服务的一个非常有趣的方面是,服务数据在整个用户会话中都会被保留。 在此示例中,为了简单起见,我只考虑了 Windows 身份验证。 为了理解这篇文章,您需要具备 AngularJS、Angular 路由和 promises 的基本知识。 我将逐步向您介绍实施的详细信息。 这不是我在 angularJS 中发明的新东西。 但是,我试图将 Angular 中可用的内容组合在一起,并可以组合起来创建一个可重用的安全代码。 我很乐意接受对这篇文章编写改进的建议。 如果您发现任何不正确或遗漏的内容,请告诉我。 我将根据需要进行更正。 希望您喜欢阅读它!
第 1 步:在 app-module 中定义全局变量
为应用程序定义角色
这些是您希望在应用程序中用于授权的角色。 将它们保持全局是必要的,因为它们将在 App 模块范围和服务范围中使用。
var roles = {
superUser: 0,
admin: 1,
user: 2
};
为应用程序定义未经授权访问的路由
如果您未被授权查看任何页面,则这是您希望用户前往的路由。
我们稍后将在 App 模块路由中使用此变量。
var routeForUnauthorizedAccess = '/SomeAngularRouteForUnauthorizedAccess';
第 2 步:定义授权服务
这是整个授权的核心。 Angular 服务(或工厂)是单例的,并且在给定用户会话的应用程序的不同路由中持久存在。 我们可以利用服务的这个特性来对不同的路由进行授权。 为了创建该服务,您需要在服务器端提供一项服务(例如 Web API),该服务可以为您提供给定用户的一组角色。 从服务器端获取角色后,Angular 服务就可以保留此知识,以便在用户会话期间将其用于所有路由。 我试图通过内联注释来解释服务代码。
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 permission 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 步:在路由中使用安全性
让我们使用我们迄今为止所做的所有辛勤工作来保护路由。 到目前为止,我们创建了一个服务,该服务能够检查用户是否属于特定角色。 现在可以在 Angular 路由中使用此知识。 在我们定义路由的 angular 模块中,我们可以进行这些检查,以查看用户是否有权访问为路由指定的任何角色。 我在这些路由中放置了角色的组合,以确保更好地理解。
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', //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]);
},
}
})
// Route for unauthorized access (When permission is not given to visit a page)
.when(routeForUnauthorizedAccess,
{
templateUrl: '/templates/UnauthorizedAccess.html',
caseInsensitiveMatch: true
})
});
摘要
在本技巧中,我们看到了如何定义基于 Angular JS 的应用程序的各种路由的授权。 作为保护路由的替代方案,您可以选择不向无权访问的用户显示链接(或菜单链接),但是,将访问权限放在应用程序中的路由本身的一个中心位置,即使您错过了隐藏未授权用户的链接(最终成为路由),路由配置也将确保未授权的用户不会被带到该路由。
最后但同样重要的是,我想强调的是,如果用户欺骗了浏览器,则以上所有内容都将无效。 因此,对任何数据的读取/更新进行服务器端检查非常重要。