在第一篇中,我们说过,用户<–>角色<–>权限三层中,暂时不考虑权限,在这一篇,是时候把它完成了。
为了方便演示,这里的权限只是对角色赋予权限,也就是说同一个角色的用户,权限是一样的。当然了,你也可以精细化到为每一个用户设置权限,但是这不在本篇的探讨范围,有兴趣可以自己实验,原理都是一样的。
一、数据准备
1.1 创建 sys_permission 表
让我们先创建一张权限表,名为 sys_permission
:
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=utf8mb4;
内容就是两条数据,通过 url
+ role_id
+ permission
唯一标识了一个角色访问某一url时的权限,其中权限暂定为c、r、u、d,代表了增、删、改、查。
1.2 创建 Model、Mapper、Service
(1)Model
public class SysPermission implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String url;
private Integer roleId;
private String permission;
private List permissions;
//省略getter,setter
}
这里需要注意的时相比于数据库,多了一个 permissions
属性,该字段将 permission
按逗号分割为了 list。
(2)mapper
@Mapper
public interface SysPermissionMapper {
@Select("SELECT * FROM sys_permission WHERE role_id=#{roleId}")
List<SysPermission>listByRoleId(Integer roleId);
}
@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
。
@Service
public class SysPermissionService {
@Autowired
SysPermissionMapper permissionMapper;
/**获取指定角色所有权限**/
public List<SysPermission> listByRoleId(Integer roleId){
return permissionMapper.listByRoleId(roleId);
}
}
@Service
public class SysRoleService {
@Autowired
SysRoleMapper sysRoleMapper;
public SysRole selectById(Integer id){
return sysRoleMapper.selectById(id);
}
public SysRole selectByName(String name){
return sysRoleMapper.selectByName(name);
}
}
1.3 修改接口
@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
接口。
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
SysPermissionService permissionService;
@Autowired
SysRoleService roleService;
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object targetPermission) {
//获得loadUserByUsername()方法的结果
User user = (User) authentication.getPrincipal();
//获得loadUserByUsername()中注入的角色
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);
//遍历permissionList
for(SysPermission sysPermission:permissionList){
//获取权限集
List permissions = sysPermission.getPermissions();
//如果访问的url和权限用户符合的话,返回true
if(targetUrl.equals(sysPermission.getUrl()) && permissions.contains(targetPermission)){
return true;
}
}
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
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
:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new CustomPermissionEvaluator());
return handler;
}
}
三、运行程序
当我使用角色为 ROLE_USER
的用户仍然能访问,因为该用户访问 /admin
路径具有 r
权限:
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!