不得不承认,这个标题异常绕口……
前言
现在用jersey
+spring
+hibernate
已经做了好几套“XXXX管理系统”了,但其实和单机应用无异,因为从来都没有遇到过权限设计的问题。但是上周帮学长做毕业设计时候不得已碰到了这一块。折腾了近5个小时。最终算有个屌丝版的解决方案了。
要求
个人觉得一个可接收的权限设计的最基本要求大概如下:
- 与业务(接口)分离
- 设计简单,安全性有保障
- 浏览器端与应用端方法统一
分析
与业务分离很容易理解,解耦合,就算以后有权限改动也不用改动代码。设计简单没什么好说的。浏览器端与应用端统一,貌似应用端使用session
不是太方便,而且我一直都是非常喜欢Restful
的无状态设计。(我不会说只是我不会用session
罢了)。所以也不选用session
。
实现方案
差不多就是这样了。
代码实现
数据库设计
首先是后端部分,表设计如下:
由于权限是精确到各个API接口部分的,所以有三张表,一张接口,一张角色,一张权限控制。
API又分POST
,DELETE
,GET
,PUT
等,所以API表需要做个细分。初步设想是这样的,在系统运行时,一次性把权限表载入内存,省去了每次读写数据库的开支。然后有改动之后同步即可。
生成Token
登陆模块生成Token较为简单:
- 获取用户帐号密码
- 与数据库对比
- 用户+密码+盐(或者时间,或者随机码)MD5之类的生成密钥(Token)
- 把<密钥,用户>放入缓存,并设置过期时间
- 返回密钥给前台
拦截验证
后台Jersey
的拦截器代码大致如下:
public void filter(ContainerRequestContext requestContext) throws IOException {
/*请求Path模板*/
List<UriTemplate> matchedTemplates = uriInfo.getMatchedTemplates();
StringBuilder path = new StringBuilder();
for (int i = matchedTemplates.size() - 1; i >= 0; i--) {
path.append(matchedTemplates.get(i).getTemplate());
}
/*请求类型*/
String type = requestContext.getMethod();
/*获取token*/
String token = requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (TokenUser == null) {
/*创建<Token,User>缓存*/
TokenUser = new CrunchifyInMemoryCache<>(3600, 300, 3000);
}
/*如果已经登陆*/
if (TokenUser.get(token) != null) {
/*获取用户以及其权限*/
User loginUser = TokenUser.get(token);
String role = loginUser.getPrivilege();
Permission x = new Permission();
if (PermissionCache == null) {
/*创建权限缓存,并从表导入*/
PermissionCache = new CrunchifyInMemoryCache<>(0, 0, 3000);
permissionService.loadIntoCache();
}
/*在缓存内寻找用户的权限*/
Boolean flag = PermissionCache.get(role + "-" + path + "-" + type);
/*如果未找到(未设置权限)或者无权限*/
if (flag == null || flag == false) {
/*打断,返回401*/
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.entity("User cannot access the resource.")
.build());
}else{
/*正常通行*/
}
/*如果未登陆*/
} else {
/*登陆api属于特殊放行的特例*/
if (path.toString().trim().equals("/user/login")) {
/*正常通行*/
} else {
/*打断,返回401*/
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.entity("User cannot access the resource.")
.build());
}
}
}
前端控制
由于对前端不熟,所以只有一个最简单的思路:
-
发送帐号密码,获取Token(必要时也获取用户信息)
-
放入Cookie,设置过期时间
-
ajaxSetup
设置每次的请求头,和统一处理返回码401的情况(跳转到登录页) -
注销时删除所有相关Cookie