はじめに
AngularJS は登場以来、長い間使用されてきました。 これは、シングル ページ アプリケーション (SPA) を開発するための JavaScript フレームワークです。 双方向バインディング、ディレクティブなどの優れた機能がいくつかあります。 この記事では主に Angular ルーティングのセキュリティ戦略を紹介します。 これは、Angular で開発できるクライアント側のセキュリティ フレームワークです。 テストしてみました。 クライアント側のルーティング セキュリティを確保することに加えて、サーバー側のアクセス セキュリティも確保する必要があります。 クライアント セキュリティ ポリシーは、サーバーへの追加アクセスを減らすのに役立ちます。 ただし、誰かがブラウザを騙してサーバーにアクセスした場合、サーバー側のセキュリティ ポリシーで不正アクセスを拒否できる必要があります。 この記事では、クライアント側のセキュリティ ポリシーについてのみ説明します。
1 アプリケーションモジュールレベルでグローバル変数を定義します
アプリケーションの役割を定義します:
var roles = { superUser: 0, admin: 1, user: 2 };
アプリケーションの不正アクセスのルートを定義します:
var routeForUnauthorizedAccess = '/SomeAngularRouteForUnauthorizedAccess';
2 認可サービスを定義します
appModule.factory('authorizationService', function ($resource, $q, $rootScope, $location) { return { // 将权限缓存到 Session,以避免后续请求不停的访问服务器 permissionModel: { permission: {}, isPermissionLoaded: false }, permissionCheck: function (roleCollection) { // 返回一个承诺(promise). var deferred = $q.defer(); // 这里只是在承诺的作用域中保存一个指向上层作用域的指针。 var parentPointer = this; // 检查是否已从服务获取到权限对象(已登录用户的角色列表) if (this.permissionModel.isPermissionLoaded) { // 检查当前用户是否有权限访问当前路由 this.getPermission(this.permissionModel, roleCollection, deferred); } else { // 如果还没权限对象,我们会去服务端获取。 // 'api/permissionService' 是本例子中的 web 服务地址。 $resource('/api/permissionService').get().$promise.then(function (response) { // 当服务器返回之后,我们开始填充权限对象 parentPointer.permissionModel.permission = response; // 将权限对象处理完成的标记设为 true 并保存在 Session, // Session 中的用户,在后续的路由请求中可以重用该权限对象 parentPointer.permissionModel.isPermissionLoaded = true; // 检查当前用户是否有必须角色访问该路由 parentPointer.getPermission(parentPointer.permissionModel, roleCollection, deferred); } ); } return deferred.promise; }, //方法:检查当前用户是否有必须角色访问该路由 //'permissionModel' 保存了从服务端返回的当前用户的角色信息 //'roleCollection' 保存了可访问当前路由的角色列表 //'deferred' 是用来处理承诺的对象 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) { // 如果用户没有必须的权限,我们把用户引导到无权访问页面 $location.path(routeForUnauthorizedAccess); // 由于这个处理会有延时,而这期间页面位置可能发生改变, // 我们会一直监视 $locationChangeSuccess 事件 // 并且当该事件发生的时,就把掉承诺解决掉。 $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', // 路由的 view/template 路径 caseInsensitiveMatch: true, controller: 'superUserController', // 路由的 angular 控制器 resolve: { // 在这我们将使用我们上面的努力成果,调用授权服务 // resolve 是 angular 中一个非常赞的特性,可以确保 // 只有当它下面提到的承诺被处理之后 // 才将控制器(在本例中是 superUserController)应用到路由。 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]); }, } }); });