目錄
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上再添加上注解時過濾語句時,那么效果就是這樣的
gitee和github中同步更新源碼