本文提供相关源码,请放心食用,详见网页侧边栏或底部,有疑问请评论或 Issue
在第一篇中,我们说过,用户<–>角色<–>权限三层中,暂时不考虑权限,在这一篇,是时候把它完成了。
为了方便演示,这里的权限只是对角色赋予权限,也就是说同一个角色的用户,权限是一样的。当然了,你也可以精细化到为每一个用户设置权限,但是这不在本篇的探讨范围,有兴趣可以自己实验,原理都是一样的。
 一、数据准备
 1.1 创建 sys_permission 表
让我们先创建一张权限表,名为 sys_permission:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | CREATE TABLE `sys_permission` (`id` int(11) NOT NULL AUTO_INCREMENT,
 `url` varchar(255) DEFAULT NULL,
 `role_id` int(11) DEFAULT NULL,
 `permission` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `fk_roleId` (`role_id`),
 CONSTRAINT `fk_roleId` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
 ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
 
 | 
内容就是两条数据,通过 url + role_id + permission 唯一标识了一个角色访问某一url时的权限,其中权限暂定为c、r、u、d,代表了增、删、改、查。

 1.2 创建 POJO、Mapper、Service
(1)pojo
| 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
 
 | package jit.wxs.entity;
 import java.io.Serializable;
 import java.util.Arrays;
 import java.util.List;
 
 
 
 
 
 
 public class SysPermission implements Serializable {
 static final long serialVersionUID = 1L;
 
 private Integer id;
 
 private String url;
 
 private Integer roleId;
 
 private String permission;
 
 private List permissions;
 
 
 
 public List getPermissions() {
 return Arrays.asList(this.permission.trim().split(","));
 }
 
 public void setPermissions(List permissions) {
 this.permissions = permissions;
 }
 }
 
 | 
这里需要注意的时相比于数据库,多了一个 permissions 属性,该字段将 permission 按逗号分割为了 list。
(2)mapper
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | import jit.wxs.entity.SysPermission;import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Select;
 
 import java.util.List;
 
 @Mapper
 public interface SysPermissionMapper {
 @Select("SELECT * FROM sys_permission WHERE role_id=#{roleId}")
 List<SysPermission> listByRoleId(Integer roleId);
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | import jit.wxs.demo.entity.SysRole;import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Select;
 
 @Mapper
 public interface SysRoleMapper {
 @Select("SELECT * FROM sys_role WHERE id = #{id}")
 SysRole selectById(Integer id);
 
 @Select("SELECT * FROM sys_role WHERE name = #{name}")
 SysRole selectByName(String name);
 }
 
 | 
(3)Service
SysPermissionService 中有一个方法,根据 roleId 获取所有的 SysPermission。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | package jit.wxs.service;
 import jit.wxs.entity.SysPermission;
 import jit.wxs.mapper.SysPermissionMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
 
 @Service
 public class SysPermissionService {
 @Autowired
 private SysPermissionMapper permissionMapper;
 
 
 
 
 public List<SysPermission> listByRoleId(Integer roleId) {
 return permissionMapper.listByRoleId(roleId);
 }
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | import jit.wxs.demo.entity.SysRole;import jit.wxs.demo.mapper.SysRoleMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 @Service
 public class SysRoleService {
 @Autowired
 private SysRoleMapper roleMapper;
 
 public SysRole selectById(Integer id) {
 return roleMapper.selectById(id);
 }
 
 public SysRole selectByName(String name) {
 return roleMapper.selectByName(name);
 }
 }
 
 | 
 1.3 修改接口
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | @Controllerpublic class LoginController {
 ...
 
 @RequestMapping("/admin")
 @ResponseBody
 @PreAuthorize("hasPermission('/admin','r')")
 public String printAdminR() {
 return "如果你看见这句话,说明你访问/admin路径具有r权限";
 }
 
 @RequestMapping("/admin/c")
 @ResponseBody
 @PreAuthorize("hasPermission('/admin','c')")
 public String printAdminC() {
 return "如果你看见这句话,说明你访问/admin路径具有c权限";
 }
 }
 
 | 
让我们修改下我们要访问的接口,@PreAuthorize("hasPermission('/admin','r')") 是关键,参数1指明了访问该接口需要的url,参数2指明了访问该接口需要的权限。
 二、PermissionEvaluator
我们需要自定义对 hasPermission() 方法的处理,就需要自定义 PermissionEvaluator,创建类 CustomPermissionEvaluator,实现 PermissionEvaluator 接口。
| 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
 49
 50
 51
 52
 53
 54
 55
 
 | import jit.wxs.entity.SysPermission;import jit.wxs.service.SysPermissionService;
 import jit.wxs.service.SysRoleService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.stereotype.Component;
 
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.List;
 
 @Component
 public class CustomPermissionEvaluator implements PermissionEvaluator {
 @Autowired
 private SysPermissionService permissionService;
 @Autowired
 private SysRoleService roleService;
 
 @Override
 public boolean hasPermission(Authentication authentication, Object targetUrl, Object targetPermission) {
 
 User user = (User)authentication.getPrincipal();
 
 Collection<GrantedAuthority> authorities = user.getAuthorities();
 
 
 for(GrantedAuthority authority : authorities) {
 String roleName = authority.getAuthority();
 Integer roleId = roleService.selectByName(roleName).getId();
 
 List<SysPermission> permissionList = permissionService.listByRoleId(roleId);
 
 
 for(SysPermission sysPermission : permissionList) {
 
 List permissions = sysPermission.getPermissions();
 
 if(targetUrl.equals(sysPermission.getUrl())
 && permissions.contains(targetPermission)) {
 return true;
 }
 }
 }
 
 return false;
 }
 
 @Override
 public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
 return false;
 }
 }
 
 | 
在 hasPermission() 方法中,参数 1 代表用户的权限身份,参数 2 参数 3 分别和 @PreAuthorize("hasPermission('/admin','r')") 中的参数对应,即访问 url 和权限。
思路如下:
- 
通过 Authentication取出登录用户的所有Role
 
- 
遍历每一个 Role,获取到每个Role的所有Permission
 
- 
遍历每一个 Permission,只要有一个Permission的url和传入的url相同,且该Permission中包含传入的权限,返回 true
 
- 
如果遍历都结束,还没有找到,返回false 
下面就是在 WebSecurityConfig 中注册 CustomPermissionEvaluator:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | @Configuration@EnableWebSecurity
 @EnableGlobalMethodSecurity(prePostEnabled = true)
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 ...
 
 
 
 @Bean
 public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){
 DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
 handler.setPermissionEvaluator(new CustomPermissionEvaluator());
 return handler;
 }
 ...
 }
 
 | 
 三、运行程序
当我使用角色为 ROLE_USER 的用户仍然能访问,因为该用户访问 /admin 路径具有 r 权限:

SpringBoot 集成 Spring Security(5)——权限控制