SpringSecurity許可權管理系統實戰—九、資料許可權的配置

codermy發表於2020-08-25

目錄

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支援,您的點贊就是我維護的動力

giteegithub中同步更新原始碼

附:阿里巴巴資料庫設計規範

一、什麼是資料許可權

許可權設計具體來說可以分為功能許可權和資料許可權。功能許可權就是角色能操作哪些介面,而資料許可權就是角色能夠獲取到的哪些資料。

形象點來說,如果現在有一個公司,公司上下有很多部門,部門裡有很多員工,而資料許可權就是為了讓某個部門的人只能獲取到自己部門或著是指定部門的員工資訊。

二、新建如下表

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
分別是崗位表,部門表,使用者崗位關聯表和角色部門關聯表

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上再新增上註解時過濾語句時,那麼效果就是這樣的
在這裡插入圖片描述
giteegithub中同步更新原始碼

相關文章