访问控制策略
| 访问策略 | 理解 | 
| 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 对象,添加到数据库中。
权限管理实现
加载权限
自定义权限控制注解
| 12
 3
 4
 5
 
 | @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)
 public @interface RequiredPermission {
 String name();
 }
 
 | 
Permission 的添加跟其他的添加不太一样,它是通过给 Controller 中的方法贴自定义注解来获取对象的。而不是手动去添加
修改权限管理中的添加按钮样式为加载:
| 12
 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 注解
| 12
 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 方法
| 12
 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。
| 12
 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
 
 | @Autowiredprivate 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
| 12
 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
 
 | @Overridepublic 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
| 12
 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
| 12
 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
 
 | @Overridepublic 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实现登录