系列目录
SpringSecurity权限管理系统实战—一、项目简介和开发环境准备
SpringSecurity权限管理系统实战—二、日志、接口文档等实现
SpringSecurity权限管理系统实战—三、主要页面及接口实现
SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)
SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)
SpringSecurity权限管理系统实战—六、SpringSecurity整合JWT
SpringSecurity权限管理系统实战—七、处理一些问题
SpringSecurity权限管理系统实战—八、AOP 记录用户日志、异常日志
SpringSecurity权限管理系统实战—九、数据权限的配置
前言
这一章的部分是我写到现在最累的一部分,累就累在逻辑的处理上,因为涉及到很多多表的操作,幸好数据库没有设计外键,不然更加恶心。
也让我发现了数据库设计之初的一些命名问题(之后再解决这个问题)。
就像是用户表中的id最好还是用user_id,而我之前用的却是id。这会导致什么问题呢?比如说你在role_user这张关联表中存的肯定是user_id吧,那么你在关联这两个表写sql语句是就要不停的转换id和user_id,脑壳子都给你绕晕了。血淋淋的教训,大家要注意了。
这篇文章只是给一个思路,内容和逻辑都太复杂,还要对原有的部分进行修改,不能再像之前那样一步一步贴代码了
希望各位小伙伴能够多多star支持,您的点赞就是我维护的动力
一、什么是数据权限
权限设计具体来说可以分为功能权限和数据权限。功能权限就是角色能操作哪些接口,而数据权限就是角色能够获取到的哪些数据。
形象点来说,如果现在有一个公司,公司上下有很多部门,部门里有很多员工,而数据权限就是为了让某个部门的人只能获取到自己部门或着是指定部门的员工信息。
二、新建如下表
分别是岗位表,部门表,用户岗位关联表和角色部门关联表
my_user表中添加dept_id字段。my_role表中添加data_scpoe字段。前一个很好理解,就是部门的id,后一个代表的就是角色的数据权限范围
(这篇文章很难写,文章所代表的代码内容也很难写,大家想要理解透彻还是要从源码里看,自己做一遍才能真正的理解)
三、效果
效果 | |
---|---|
效果就是这样,代码也不贴了,这部分逻辑有点复杂,代码量很大,可以自行去源码中查看。
四、实现
这里只是介绍下如何实现数据权限,参考了若依项目中的实现方法
首先我们自定义一个注解
/**
* 数据权限过滤注解
* @author codermy
* @createTime 2020/8/22
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别名
*/
public String userAlias() default "";
}
再定义一个切面类
/**
* 数据过滤处理
* @author codermy
* @createTime 2020/8/22
*/
@Aspect
@Component
public class DataScopeAspect {
@Autowired
public RoleUserService roleUserService;
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
/**
* 配置织入点
*/
@Pointcut("@annotation(com.codermy.myspringsecurityplus.admin.annotation.DataPermission)")
public void dataScopePointCut()
{
}
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) throws Throwable
{
handleDataScope(point);
}
protected void handleDataScope(final JoinPoint joinPoint)
{
// 获得注解
DataPermission controllerDataScope = getAnnotationLog(joinPoint);
if (controllerDataScope == null)
{
return;
}
// 获取当前的用户
JwtUserDto currentUser = SecurityUtils.getCurrentUser();
if (currentUser != null)
{
// 如果是超级管理员,则不过滤数据
if (!currentUser.isAdmin())
{
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias());
}
}
}
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param deptAlias 部门别名
* @param userAlias 用户别名
*/
public static void dataScopeFilter(JoinPoint joinPoint, JwtUserDto user, String deptAlias, String userAlias)
{
StringBuilder sqlString = new StringBuilder();
for (MyRole role : user.getRoleInfo())
{
String dataScope = role.getDataScope();
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
sqlString.append(StrUtil.format(
" OR {}.id IN ( SELECT dept_id FROM my_role_dept WHERE role_id = {} ) ", deptAlias,
role.getId()));
}
else if (DATA_SCOPE_DEPT.equals(dataScope))
{
sqlString.append(StrUtil.format(" OR {}.id = {} ", deptAlias, user.getMyUser().getDeptId()));
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
sqlString.append(StrUtil.format(
" OR {}.id IN ( SELECT id FROM my_dept WHERE id = {} or find_in_set( {} , ancestors ) )",
deptAlias, user.getMyUser().getDeptId(), user.getMyUser().getDeptId()));
}
else if (DATA_SCOPE_SELF.equals(dataScope))
{
if (StrUtil.isNotBlank(userAlias))
{
sqlString.append(StrUtil.format(" OR {}.id = {} ", userAlias, user.getMyUser().getId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(" OR 1=0 ");
}
}
}
if (StrUtil.isNotBlank(sqlString.toString()))
{
BaseEntity baseEntity;
for (int i = 0;i < joinPoint.getArgs().length ;i++ ){
if (joinPoint.getArgs()[i] instanceof BaseEntity){
baseEntity= (BaseEntity) joinPoint.getArgs()[i];
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
}
/**
* 是否存在注解,如果存在就获取
*/
private DataPermission getAnnotationLog(JoinPoint joinPoint)
{
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
return method.getAnnotation(DataPermission.class);
}
return null;
}
}
解释一下这个切面类的作用就是:
当你在ServiceImpl的某个方法上定义了这个注解时,它就会获取当前登录用户的角色的dataScope(就是数据范围),然后比较它的值,将相应的sql语句存入baseEntity的params中
然后我们只需要在对于需要配置数据权限的mapper.xml中添加${params.dataScope}
再在调用此方法的service方法上添加注解@DataPermission(deptAlias = “d”, userAlias = “u”)
即可
这里我在BaseEntity中添加了一个params属性来用于存放数据权限的sql
这样当我们需要调用这个sql时,如果未过滤权限时,sql是这样的
SELECT u.id, u.dept_id, u.user_name, u.password, u.nick_name
, u.phone, u.email, u.status, u.create_time, u.update_time
FROM my_user u
LEFT JOIN my_dept d ON u.dept_id = d.id
WHERE u.dept_id = ?
OR u.dept_id IN (
SELECT e.id
FROM my_dept e
WHERE FIND_IN_SET(?, ancestors)
)
ORDER BY u.id
如果我们给这个角色的数据权限种类是自定数据权限,sql就会是下面这样
SELECT u.id, u.dept_id, u.user_name, u.password, u.nick_name
, u.phone, u.email, u.status, u.create_time, u.update_time
FROM my_user u
LEFT JOIN my_dept d ON u.dept_id = d.id
WHERE (u.dept_id = ?
OR u.dept_id IN (
SELECT e.id
FROM my_dept e
WHERE FIND_IN_SET(?, ancestors)
))
AND d.id IN (
SELECT dept_id
FROM my_role_dept
WHERE role_id = 2
)
ORDER BY u.id
那么如果我们给角色 ‘普通用户’如下数据权限
那么当我们登录该角色的用户时,便只能访问到相应部门下的用户信息。
当我们再在部门的相应方法和sql语句中再添加上注解和过滤语句时,那么效果就是这样的