访问控制策略
访问策略 |
理解 |
DAC(自主型访问控制) |
用户/对象来决定访问权限,信息的所有者设定谁有权限访问信息及操作,基于身份的访问控制,如 UNIX 权限管理 |
MAC(强制性访问控制) |
系统决定访问权限,由操作系统的规则决定,基于规则 |
基于属性证书的访问控制 |
访问权限信息存放在用户属性证书的权限属性中 |
RBAC(Role-Based Access Control) |
基于角色的访问控制,某个职位根据工作需要调整,RBAC模型对组织内部的关系和访问控制给出恰当的描述 |
RBAC 访问控制的思路
- 为用户分配角色和权限(用户、角色和权限数据及其之间关系数据存入数据库)
- 获取当前用户和被访问的资源
- 若用户是管理员,直接放行访问;
- 若资源不需要权限控制,直接放行访问;
- 其他情况则从数据库中查询该用户(先找角色,再找角色对应的权限)是否具有访问该资源的权限,若没有,提醒用户权限不足,若有放行访问。

先把所有 domain 独立的增删改查做完。然后将有关系的 domain 建立关系,并修改页面让其关联。比如 emloyee 与 department 多对一关系,employee 与 role 多对多关系
注意:权限表达式值必须唯一
权限表达式的生成
权限表达式必须唯一,用来区分用户访问的到底是什么资源。权限控制,就是对 Controller 中的处理方法做限制,因为处理方法包含数据库的 CRUD 操作,所以控制器中的一个个处理方法就是一个个的权限,所以数据库中,权限表达式就是所有控制器的一个一个的方法。
permission 表中,name 就是给角色分配时看的,必须见名知意。比如,非VIP用户无法访问付费区内容。
expression 必须为宜,可以用 控制器类名首字母小写:方法名,来设置。
可以使用注解,获取容器对象,从容器对象中获取所有贴 @Controller 注解的 bean,然后获取贴有自己定义的注解的方法,获取权限名称和权限表达式(可以手动拼接,也可以是用注解定义获取),
- 判断该方法是否有我们自定义的注解,
- 判断这个方法的权限表达式是否在数据库中,
- 如果有注解且权限表达式在数据库中查不到,就创建 Permission 对象,添加到数据库中。
权限管理实现
加载权限
自定义权限控制注解
1 2 3 4 5
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequiredPermission { String name(); }
|
Permission 的添加跟其他的添加不太一样,它是通过给 Controller 中的方法贴自定义注解来获取对象的。而不是手动去添加
修改权限管理中的添加按钮样式为加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script type="text/javascript"> $('#reload').click(function () { $.post('/permission/reload', function (data) { if (data.success) {
} else { alert('系统繁忙') } }) }) }) </script>
<button id="reload" type="button" class="btn btn-primary"> <span class="glyphicon glyphicon-refresh" aria-hidden="true"></span>加载 </button>
|
贴注解
在 Controller 需要添加权限管理的方法贴上 @RequestPermission 注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
@RequestMapping("/list") @RequiredPermission(name = "员工列表") public String list(Model model, @ModelAttribute("qe") QueryEmployee qe) { if (qe == null || qe.getCurrentPage() == null) { qe = QueryEmployee.EMPTYQUERY; model.addAttribute("qe", qe); } QueryResult result = employeeService.gets(qe); model.addAttribute("result", result); return "employee/list"; }
|
实现 PermissionController 的 reload 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@RequestMapping("/reload") @ResponseBody public Map<String, Boolean> reload(Model model) { Map<String, Boolean> result = new HashMap<>(); try { permissionService.reload(); result.put("success", true); } catch (Exception e) { e.printStackTrace(); result.put("success", false); } return result; }
|
service reload 方法
在 IPermissionService 中添加 reload 方法,以及实现类,获取 ApplicationContext 对象,为了获取容器中的 bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| @Autowired private ApplicationContext context;
@Override public void reload() { List<Permission> permissions = permissionMapper.selectAll(); Set<String> permissionSet = new HashSet<>(); for (Permission permission : permissions) { permissionSet.add(permission.getExpression()); }
Map<String, Object> beans = context.getBeansWithAnnotation(Controller.class); Set<String> keys = beans.keySet(); for (String key : keys) { System.out.println(beans.get(key)); System.out.println(key); }
Collection<Object> controllers = beans.values(); for (Object controller : controllers) { Method[] methods = controller.getClass().getDeclaredMethods(); for (Method method : methods) { boolean isPermissionMethod = method.isAnnotationPresent(RequiredPermission.class); if (isPermissionMethod) { String methodExpression = StringUtil.getMethodExpression(method); if (permissionSet.contains(methodExpression)) { continue; } Permission permission = new Permission(); permission.setName(method.getAnnotation(RequiredPermission.class).name()); permission.setExpression(methodExpression); permissionMapper.insert(permission); } } } }
|
上面是实现思路,下面是使用时的代码
PermissionServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Override public void reload() { List<Permission> permissions = permissionMapper.selectAll(); Set<String> permissionSet = new HashSet<>(); for (Permission permission : permissions) { permissionSet.add(permission.getExpression()); }
Map<String, Object> beans = context.getBeansWithAnnotation(Controller.class); Collection<Object> controllers = beans.values(); for (Object controller : controllers) { Method[] methods = controller.getClass().getDeclaredMethods(); for (Method method : methods) { boolean isPermissionMethod = method.isAnnotationPresent(RequiredPermission.class); if (isPermissionMethod) { String methodExpression = StringUtil.getMethodExpression(method); if (permissionSet.contains(methodExpression)) { continue; } Permission permission = new Permission(); permission.setName(method.getAnnotation(RequiredPermission.class).name()); permission.setExpression(methodExpression); permissionMapper.insert(permission); } } } }
|
StringUtil.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
public static String getMethodExpression(Method method) { String name = method.getDeclaringClass().getSimpleName(); name = name.replace("Controller", ""); name = firstToLowerCase(name); return name + ":" + method.getName(); }
public static String firstToLowerCase(String str) { if (isNotEmpty(str)) { return (str.charAt(0) + "").toLowerCase() + str.substring(1); } return ""; }
|
权限控制思路
- 区分请求是哪个用户发起
- 权限判断流程
- 获取当前请求用户
- 若用户时管理员,直接放行
- 获取访问方法上是否需要权限控制,如果没有贴 @RequirePermission 注解,直接放行
- 获取访问处理器方法,拼接权限表达式,从数据库获取该用户的权限数据,如果包含,则放行
用户岗位变化少,没必要每次都去数据库查询,浪费性能,第一次查询之后进行缓存,可以在登录时缓存权限数据。
具体代码:
PermissionInterceptor.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } Employee e = SessionUtil.getSessionAttribute(Employee.class); if (e.getAdmin()) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler;
RequiredPermission methodAnnotation = handlerMethod.getMethodAnnotation(RequiredPermission.class); if (methodAnnotation == null) { return true; } String methodExpression = StringUtil.getMethodExpression(handlerMethod.getMethod()); Set<String> expressions = (Set<String>) SessionUtil.getSessionAttribute(Permission.class); if (expressions.contains(methodExpression)) { return true; } request.getRequestDispatcher("/common/nopermission.jsp").forward(request, response); return false; }
|
登录优化
详情见:Ajax实现登录