本文提供相关源码,请放心食用,详见网页侧边栏或底部,有疑问请评论或 Issue
在第一篇中,我们说过,用户<–>角色<–>权限三层中,暂时不考虑权限,在这一篇,是时候把它完成了。
为了方便演示,这里的权限只是对角色赋予权限,也就是说同一个角色的用户,权限是一样的。当然了,你也可以精细化到为每一个用户设置权限,但是这不在本篇的探讨范围,有兴趣可以自己实验,原理都是一样的。
一、数据准备
1.1 创建 sys_permission 表
让我们先创建一张权限表,名为 sys_permission
:
1 2 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
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
| 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
1 2 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); }
|
1 2 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
。
1 2 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); } }
|
1 2 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 修改接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Controller public 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
接口。
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 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
:
1 2 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)——权限控制