在第一篇中,我们说过,用户<–>角色<–>权限三层中,暂时不考虑权限,在这一篇,是时候把它完成了。
为了方便演示,这里的权限只是对角色赋予权限,也就是说同一个角色的用户,权限是一样的。当然了,你也可以精细化到为每一个用户设置权限,但是这不在本篇的探讨范围,有兴趣可以自己实验,原理都是一样的。
目录
一、数据准备
1.1 创建sys_permission表
1.2 创建POJO、Mapper、Service
1.3 修改接口
二、PermissionEvaluator
三、运行程序
让我们先创建一张权限表,名为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=utf8;
内容就是两条数据,url
+role_id
+permission
唯一标识了一个角色访问某一url时的权限,其中权限暂定为c、r、u、d,即增删改查。
(1)pojo
package com.chwx.springbootspringsecurity.pojo;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
@Table(name = "sys_permission")
@Data
public class SysPermission implements Serializable {
static final long serialVersionUID = 1L;
@Id
private Integer id;
private String url;
@Column(name = "role_id")
private Integer roleId;
private String permission;
@Transient
private List permissions;
// 省略除permissions外的getter/setter
public List getPermissions() {
return Arrays.asList(this.permission.trim().split(","));
}
public void setPermissions(List permissions) {
this.permissions = permissions;
}
}
这里需要注意的时相比于数据库,多了一个permissions
属性,该字段将permission
按逗号分割为了list。
(2)mapper
package com.chwx.springbootspringsecurity.dao;
import com.chwx.springbootspringsecurity.pojo.SysPermission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author ZY Wei
* @date 2018/11/20 16:49
*/
@Mapper
public interface SysPermissionMapper {
@Select("select * from sys_permission where role_id = #{roleId}")
List listByRoleId(Integer roleId);
}
(3)Service
package com.chwx.springbootspringsecurity.service;
import com.chwx.springbootspringsecurity.pojo.SysPermission;
import com.chwx.springbootspringsecurity.dao.SysPermissionMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author ZY Wei
* @date 2018/11/20 16:50
*/
@Service
public class SysPermissionService {
@Autowired
private SysPermissionMapper sysPermissionMapper;
/**
* 获取指定角色所有权限
* @param roleId
* @return
*/
public List listByRoleId(Integer roleId){
return sysPermissionMapper.listByRoleId(roleId);
}
}
Service中有一个方法,根据roleId获取所有的SysPermission
@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权限";
}
}
让我们修改下我们要访问的接口,重点是printAdmin()
方法,@PreAuthorize("hasPermission('/admin','r')")
是关键,它第一个参数指明了访问该接口需要的url,第二个参数指明了访问该接口需要的权限。
我们需要自定义对hasPermission()
方法的处理,就需要自定义PermissionEvaluator
,创建类CustomPermissionEvaluator
,实现PermissionEvaluator
接口。
package com.chwx.springbootspringsecurity.common;
import com.chwx.springbootspringsecurity.pojo.SysPermission;
import com.chwx.springbootspringsecurity.service.SysPermissionService;
import com.chwx.springbootspringsecurity.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;
/**
* @author ZY Wei
* @date 2018/11/20 16:57
*/
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private SysPermissionService sysPermissionService;
@Autowired
private SysRoleService sysRoleService;
@Override
public boolean hasPermission(Authentication authentication, Object o, Object o1) {
//获取loadUserByUsername()方法结果
User user = (User) authentication.getPrincipal();
//获取loadUserByUsername()中注入的角色
Collection authorities = user.getAuthorities();
//遍历所有的角色
for (GrantedAuthority authority : authorities){
String roleName = authority.getAuthority();
Integer roleId = sysRoleService.selectByName(roleName);
//得到角色所有的权限
List permissionList = sysPermissionService.listByRoleId(roleId);
//遍历permissionList
for(SysPermission sysPermission : permissionList){
//获取权限集
List permissions = sysPermission.getPermissions();
//如果访问的url和权限用户符合的话,返回true
if(o.equals(sysPermission.getUrl()) && permissions.contains(o1)) {
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:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
/**
* 注入自定义PermissionEvaluator
*/
@Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new CustomPermissionEvaluator());
return handler;
}
...
}
当我使用角色为ROLE_USER
的用户仍然能访问,因为该用户访问/admin
路径具有r
权限: