權限的設計中比較常見的就是rbac基于角色的通路控制,基本思想是,對系統操作的各種權限不是直接授予具體的使用者,而是在使用者集合與權限集合之間建立一個角色集合。每一種角色對應一組相應的權限。
一旦使用者被配置設定了适當的角色後,該使用者就擁有此角色的所有操作權限。這樣做的好處是,不必在每次建立使用者時都進行配置設定權限的操作,隻要配置設定使用者相應的角色即可,而且角色的權限變更比使用者的權限變更要少得多,這樣将簡化使用者的權限管理,減少系統的開銷。
1. ui處理(根據使用者擁有的權限,判斷頁面上的一些内容是否顯示)
2. 路由處理(當使用者通路一個它沒有權限通路的url時,跳轉到一個錯誤提示的頁面)
3. http請求處理(當我們發送一個資料請求,如果傳回的status是401或者403,則通常重定向到一個錯誤提示的頁面)
如何實作?
首先需要在angular啟動之前就擷取到目前使用者的所有的 permissions,然後比較優雅的方式是通過一個service存放這個映射關系.對于ui處理一個頁面上的内容是否根據權限進行顯示,我們應該通 過一個directive來實作.當處理完這些,我們還需要在添加一個路由時額外為其添加一個"permission"屬性,并為其指派表明擁有哪些權限 的角色可以跳轉這個url,然後通過angular監聽routechangestart事件來進行目前使用者是否擁有此url通路權限的校驗.最後還需要 一個http攔截器監控當一個請求傳回的status是401或者403時,跳轉頁面到一個錯誤提示頁面.大緻上的工作就是這些,看起來有些多,其實一個個來還是挺好處理的.
傳回401,執行loginctrl,傳回403執行permissionctrl。
在angular運作之前擷取到permission的映射關系
angular項目通過ng-app啟動,但是一些情況下我們是希望 angular項目的啟動在我們的控制之中.比如現在這種情況下,我就希望能擷取到目前登入使用者的所有permission映射關系後,再啟動 angular的app.幸運的是angular本身提供了這種方式,也就是angular.bootstrap().

var permissionlist;
angular.element(document).ready(function() {
$.get('/api/userpermission', function(data) {
permissionlist = data;
angular.bootstrap(document, ['app']);
});
});
進一步使用上面的代碼可以将擷取到的映射關系放入一個service作為全局變量來使用.

// app.js
var app = angular.module('myapp', []), permissionlist;
app.run(function(permissions) {
permissions.setpermissions(permissionlist)
// common_service.js
angular.module('myapp')
.factory('permissions', function ($rootscope) {
var permissionlist;
return {
setpermissions: function(permissions) {
permissionlist = permissions;
$rootscope.$broadcast('permissionschanged')
}
};
在取得目前使用者的權限集合後,我們将這個集合存檔到對應的一個service中,然後又做了2件事:
(1) 将permissions存放到factory變量中,使之一直處于記憶體中,實作全局變量的作用,但卻沒有污染命名空間.
(2) 通過$broadcast廣播事件,當權限發生變更的時候.
1如何确定ui元件的依據權限進行顯隐
這裡我們需要自己編寫一個directive,它會依據權限關系來進行顯示或者隐藏元素.

<!-- if the user has edit permission the show a link -->
<div has-permission='edit'>
<a href="/#/courses/{{ id }}/edit"> {{ name }}</a>
</div>
<!-- if the user doesn't have edit permission then show text only (note the "!" before "edit") -->
<div has-permission='!edit'>
{{ name }}
這裡看到了比較理想的情況是通關一個has-permission屬性校驗permission的name,如果目前使用者有則顯示,沒有則隐藏.

angular.module('myapp').directive('haspermission', function(permissions) {
return {
link: function(scope, element, attrs) {
if(!_.isstring(attrs.haspermission))
throw "haspermission value must be a string";
var value = attrs.haspermission.trim();
var notpermissionflag = value[0] === '!';
if(notpermissionflag) {
value = value.slice(1).trim();
function togglevisibilitybasedonpermission() {
var haspermission = permissions.haspermission(value);
if(haspermission && !notpermissionflag || !haspermission && notpermissionflag)
element.show();
else
element.hide();
togglevisibilitybasedonpermission();
scope.$on('permissionschanged', togglevisibilitybasedonpermission);
}
};
擴充一下之前的factory:

},
haspermission: function (permission) {
permission = permission.trim();
return _.some(permissionlist, function(item) {
if(_.isstring(item.name))
return item.name.trim() === permission
});
2路由上的依權限通路
這一部分的實作的思路是這樣: 當我們定義一個路由的時候增加一個permission的屬性,屬性的值就是有哪些權限才能通路目前url.然後通過routechangestart事 件一直監聽url變化.每次變化url的時候,去校驗目前要跳轉的url是否符合條件,然後決定是跳轉成功還是跳轉到錯誤的提示頁面.

app.config(function ($routeprovider) {
$routeprovider
.when('/', {
templateurl: 'views/viewcourses.html',
controller: 'viewcoursesctrl'
})
.when('/unauthorized', {
templateurl: 'views/error.html',
controller: 'errorctrl'
.when('/courses/:id/edit', {
templateurl: 'views/editcourses.html',
controller: 'editcourses',
permission: 'edit'
});
maincontroller.js 或者 indexcontroller.js (總之是父層controller)

app.controller('mainappctrl', function($scope, $location, permissions) {
$scope.$on('$routechangestart', function(scope, next, current) {
var permission = next.$$route.permission;
if(_.isstring(permission) && !permissions.haspermission(permission))
$location.path('/unauthorized');
這裡依然用到了之前寫的haspermission,這些東西都是高度可複用的.這樣就搞定了,在每次view的route跳轉前,在父容器的controller中判斷一些它到底有沒有跳轉的權限即可.
3http請求處理
這個應該相對來說好處理一點,思想的思路也很簡單.因為angular應用推薦的是restful風格的借口,是以對于http協定的使用很清晰.對于請求傳回的status code如果是401或者403則表示沒有權限,就跳轉到對應的錯誤提示頁面即可.
當然我們不可能每個請求都去手動校驗轉發一次,是以肯定需要一個總的filter.代碼如下:

.config(function($httpprovider) {
$httpprovider.responseinterceptors.push('securityinterceptor');
})
.provider('securityinterceptor', function() {
this.$get = function($location, $q) {
return function(promise) {
return promise.then(null, function(response) {
if(response.status === 403 || response.status === 401) {
$location.path('/unauthorized');
}
return $q.reject(response);
};
};