数据库模型
简单的用户权限系统中, 涉及到三种模型: 用户、角色、权限。 用户具有角色, 角色拥有权限, 于是用户通过角色便可以拥有权限。基于这样的联系, 用户通过角色便获得了权限。如图。著名的开源系统Redmine就使用了这种简单的权限设计。
User -> RoleList -> PermissionList
服务器端
登录机制
三个接口: 用户登录, 用户登出, 用户是否登录。此处过于基础常见, 不再细说。
权限机制
通过注解管理接口权限, 定义权限注解
[java]
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Permission {
String value(); //接口权限
}
[/java]
需权限限制方法处添加注记:
[java]
@RestController
Class A {
@Permission(value=’create’)
String create() {
… // create logic
}
}
[/java]
通过拦截器实现权限限制逻辑
[java]
public class PermissionInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
…
List<String> userPermissionsList = …; //从request中获取用户权限列表
if(handler.getClass().isAssignableFrom(HandlerMethod.class)){
Permission permissionAnno = ((HandlerMethod) handler).getMethodAnnotation(Permission.class);
// 从注解中获取接口允许的权限列表
String permission = permissionAnno.value();
// 如果用户权限列表于接口权限列表有交集, 放行; 否则返回客户端无权限
if (StringUtils.isBlank(permission) || userPermissionsList.contains(permission)) {
return true;
} else {
Return false;
}
}
return true;
}
[/java]
客户端(AngularJs)
登录机制
主要可以分为以下几块。
本地登录状态存储: 按照生命周期的不同, 可以保存为全局变量(页面级), SessionStorage(会话级), localStorage(本地持久化)。
服务器登录状态验证: 每次发送http请求, 或者切换页面时, 检查服务器端的登录状态。
登录状态更新事件的传播:每次获取到登录状态, 向顶层作用域广播登录状态的更新时间
登录状态改变事件的捕获:判断最新的登录状态, 如果没有登录, 跳转至登录页面。
本地登录信息存储
在AngularJs中 , Service都是单例的。所以我们可以把状态的保存放在Service中。 而根据实际需要, 登录状态以及登录用户信息可以在Service中用成员变量保存, 或者从sessionStorage中读取, 或者从localStorage中读取。
通过这个本地的登录状态, 系统可以判断当前的登录状态, 也可以读取登录用户的信息。
下面的这个例子中, 本地用户登录信息是放在成员变量中, 所以每次打开页面, 这个变量都是空, 系统都会跳转到登录页面。
[javascript]
app.factory(‘BaseAuthService’, function ($rootScope,$q, $http) {
var baseLoginUser = null;
var userName = "";
var userId = "";
return {
getAuth: function () {
return {
baseLoginUser : baseLoginUser,
userName : userName,
userId : userId
}
},
getUserId: function () {
return userId;
},
getUser: function () {
return baseLoginUser;
},
isLogin: function () {
if (null == baseLoginUser) {
return false;
} else {
return true;
}
},
};
});
[/javascript]
登录状态验证
在特定的操作下, 特定的时间点, 前端页面需要到服务器获取用户登录状态。 具体选择什么样的操作, 什么样的时机, 还是需要根据自己系统的特点选择。
如果为了实现简单, 系统也没有大的并发压力, 可以在每一次http请求, 每一次页面流转时, 均进行登录验证。也可以在获取到登录信息的同时, 获取登录信息的过期时间, 前端页面只有在登录信息过期时才试图更新登录信息。
现在只讨论如何在每一次http请求, 每一次页面流转时, 加入登录状态的验证。
- http拦截器
每一次http请求后均检查用户是否登录, 如果检测到未登录, 设置系统状态为未登录。
[javascript]
app.config([‘$httpProvider’, function ($httpProvider){
$httpProvider.interceptors.push(‘normalHttpInterceptor’);
}]);
app.factory(‘normalHttpInterceptor’, [‘$rootScope’, ‘$injector’, function ($rootScope,$injector){
return {
response: function(res){
$rootScope.gHideLoading();
var BaseAuthService = $injector.get(‘BaseAuthService’);
if (!!res.data && res.data.code == "business021") {
// 未登录, 跳转登录页面
BaseAuthService.setAuth(null, null);
}
return res;
},
};
}]);
[/javascript]
捕获页面流转时产生的$stateChangeStart事件,如果成功获取用户的登录状态(已登录或未登录), 设置本地系统状态为相应状态。
[javascript]
app.run(["$rootScope",’$state’, ‘BaseAuthService’, function($rootScope, $state, BaseAuthService){
$rootScope.$on(‘$stateChangeStart’, function(event, toState, toParams, fromState, fromParams) {
if (toState.name == "login") {
return;
}
var fetch = BaseAuthService.fetchAuth();
fetch.then(
function (res) {
if (!!res) {
if (res.success) {
BaseAuthService.setAuth(res.data, res.data.permissions);
} else {
BaseAuthService.setAuth(null, null);
}
} else {
}
},
function (err) {
}
);
});
}]);
[/javascript]
登录状态更新事件的传播
更新本地的登录状态时, 同时广播登录状态更改事件: baseAuthChanged
[javascript]
app.factory(‘BaseAuthService’, function ($rootScope,$q, $http) {
var basePermissionList = null;
var baseLoginUser = null;
var userName = "";
var userId = "";
…..
return {
setAuth: function(baseLoginUse, basePermissionLis) {
baseLoginUser = baseLoginUse;
basePermissionList = basePermissionLis;
userName = null;
userId = null;
if (!!baseLoginUser) {
userName = baseLoginUser.userName;
userId = baseLoginUser.userId;
}
$rootScope.$broadcast(‘baseAuthChanged’);
$rootScope.$broadcast(‘permissionsChanged’);
},
};
});
[/javascript]
登录状态改变事件的捕获
在最外层的Controller中监听事件“baseAuthChanged”,
事件触发时, 如果本地登录状态为未登录, 跳转到登录页面。
[javascript]
$rootScope.$on("baseAuthChanged", function () {
var auth = BaseAuthService.getAuth();
if (!BaseAuthService.isLogin()) {
//$location.path("/login");
$state.go(‘login’);
return;
}
});
[/javascript]
AngularJs中两种事件传播机制
$broadcast的作用是将事件从父级作用域传播至子级作用域,包括自己。格式如下:$broadcast(eventName,args)
$emit的作用是将事件从子级作用域传播至父级作用域,包括自己,直至根作用域。格式如下:$emit(eventName,args)
权限机制
前端权限控制的目的, 仅仅是让元素按照不同的权限进行显示与隐藏。具体实现起来十分简单。
在上述的登录机制中, 后台接口提供的登录用户信息, 不仅仅有id, 用户名等基本信息,还需提供权限列表信息。我们设置本地登录信息的时候, 登录用户的权限列表也就保存在本地页面了。
[javascript]
app.factory(‘BaseAuthService’, function ($rootScope,$q, $http) {
var basePermissionList = null;
var baseLoginUser = null;
var userName = "";
var userId = "";
var fetchAuth = function () {
var deferred = $q.defer();
$http({
method: ‘GET’,
url: ‘rms/1.0/auth/getLoginUser’
}).success(function(response) {
deferred.resolve(response);
}).error(function(response) {
deferred.reject(response);
});
return deferred.promise;
}
return {
setAuth: function(baseLoginUse, basePermissionLis) {
baseLoginUser = baseLoginUse;
basePermissionList = basePermissionLis;
userName = null;
userId = null;
if (!!baseLoginUser) {
userName = baseLoginUser.userName;
userId = baseLoginUser.userId;
}
$rootScope.$broadcast(‘baseAuthChanged’);
$rootScope.$broadcast(‘permissionsChanged’);
},
getAuth: function () {
return {
baseLoginUser : baseLoginUser,
basePermissionList : basePermissionList,
userName : userName,
userId : userId
}
},
getUserId: function () {
return userId;
},
getUser: function () {
return baseLoginUser;
},
getPermissions: function () {
return basePermissionList;
},
hasPermission: function (permission) {
if (null == permission || typeof (permission) == "undefined" || permission == "") {
return true;
}
for (var idx in basePermissionList) {
var userPermission = basePermissionList[idx];
if (userPermission.name == permission) {
return true;
}
}
return false;
},
isLogin: function () {
if (null == baseLoginUser) {
return false;
} else {
return true;
}
},
fetchAuth : fetchAuth
};
});
[/javascript]
在本地拥有了权限列表的前提下, 我们可以通过AngularJs的指令机制, 轻易地将权限应用到页面上的元素中。
<button has-permission=”create”></button>
[javascript]
app.directive(‘hasPermission’, function(BaseAuthService) {
return {
restrict: ‘A’,
link: function(scope, element, attrs) {
var value = attrs.hasPermission.trim();
var notPermissionFlag = value[0] === ‘!’;
if(notPermissionFlag) {
value = value.slice(1).trim();
}
function toggleVisibilityBasedOnPermission() {
var hasPermission = BaseAuthService.hasPermission(value);
if(hasPermission && !notPermissionFlag || !hasPermission && notPermissionFlag)
element.show();
else
element.hide();
}
toggleVisibilityBasedOnPermission();
scope.$on(‘permissionsChanged’, toggleVisibilityBasedOnPermission);
}
};
});
[/javascript]