<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
上篇文章的資料模型是基於傳統的RBAC模型來設計的,由於我們這裡的應用場景不一樣,所以這裡的資料許可權模型並沒有嚴格按照上篇文章的方案來設計,但是萬變不離其宗,核心原理還是相同的。
首先我來介紹一下我們最終實現的效果
一個元件(可以理解成選單)可以繫結多個授權維度,當給角色授權元件時可以給這個授權元件賦予不同維度的許可權。
關於資料許可權的授權維度有以下幾個關鍵點需要仔細體會:
where 產品線 in ('A產品線')
where 客戶群 in('A客戶群')AND 產品線 in ('B產品線')
where 客戶群 in ('A客戶群','B客戶群') OR 產品線 in ('A產品線')
當然我們業務場景中資料規則比較單一,全部使用
in
作為sql條件連線符,你們可以根據實際業務場景進行補充。
最終的資料模型如下所示:
這裡的元件大家完全可以理解成RBAC模型中的資源、選單,只不過叫法不同而已。
下面是具體的表結構設計
CREATE TABLE `wb_dimension` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵', `DIMENSION_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '維度編碼', `DIMENSION_NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '維度名稱', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='授權維度'
CREATE TABLE `wb_dimension_proc_line` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵', `DIMENSION_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '維度編碼', `PROC_LINE_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '產品線編碼', `PROC_LINE_NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '產品線名稱', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='授權維度-產品線'
跟授權維度表實際是一個表繼承的關係,由於每個授權維度的屬性不一樣,展現形式也不一樣,所以分表儲存。
CREATE TABLE `wb_route` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵ID', `COMPONENT_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '元件ID', `ROUTE_URL` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '路由地址', `AUTHORIZATION_TYPE` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '授權方式:1 自定義,2 上下級授權, 3 範圍授權', `AUTHORIZATION_DIMENSION` json DEFAULT NULL COMMENT '授權維度', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='元件路由' 複製程式碼
當元件屬性授權方式為範圍授權時在應用側會強制要求選擇具體的授權維度,如 產品線、客戶群。
CREATE TABLE `wb_role` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵ID', `ROLE_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色CODE', `ROLE_NAME` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色名稱', `IDENTITY_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '身份ID' PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色表'
角色上有一個身份屬性,多個角色可以歸屬同一個身份,方便對角色進行分類管理。
CREATE TABLE `role_component_relation` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵ID', `ROLE_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色ID', `COMPONENT_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '元件ID', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色授權元件'
CREATE TABLE `wb_role_component_rule` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵', `ROLE_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色ID', `COMPONENT_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '元件ID', `RULE_CODE` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '規則編碼', `RULE_NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '規則名稱', `RULE_CONDITION` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '規則條件', `RULE_VALUE` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '規則值', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色元件維度規則表'
資料許可權的核心表,規則條件的取值為IN,規則值儲存具體的維度編碼,當在資料維度中選擇 全部 時我們將規則值儲存為ALL這個特殊值,方便後續生成SQL語句。
DataPermission
@DataPermission
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) @Documented public @interface DataPermission { /** * 資料許可權型別 * 1 上下級授權 2 資料範圍授權 */ String permissionType() default "2"; /** * 設定選單的元件路徑,用於資料許可權 */ String componentRoute() default ""; }
@Aspect @Slf4j public class DataPermissionAspect { @Autowired private RoleComponentRuleService roleComponentRuleService; @Pointcut("@annotation(com.ifly.workbench.security.annotation.DataPermission)") public void pointCut() { } @Around("pointCut()") public Object around(ProceedingJoinPoint point) throws Throwable{ HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); //獲取請求token String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN); String userName = JwtUtil.getUsername(token); MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DataPermission permissionData = method.getAnnotation(DataPermission.class); //獲取授權方式 String permissionType = permissionData.permissionType(); //獲取元件路由 String componentRoute = permissionData.componentRoute(); if (StringUtils.isNotEmpty(componentRoute)){ // 查詢當前使用者此元件下的所有規則 List<RoleComponentRuleDTO> componentRules = roleComponentRuleService.getRoleComponentRule(userName, componentRoute); if(CollectionUtils.isNotEmpty(componentRules)){ DataPermissionUtils.installDataSearchConditon(request, componentRules); SysUserCacheInfo userInfo = buildCacheUser(userName); DataPermissionUtils.installUserInfo(request, userInfo); } } return point.proceed(); } private SysUserCacheInfo buildCacheUser(String userName) { SysUserCacheInfo info = new SysUserCacheInfo(); info.setSysUserName(userName); info.setOneDepart(true); return info; } }
在AOP中獲取當前使用者、需要存取的元件中所有的資料規則,參考wb_role_component_rule
表設計,並將其放到Request作用域中。
public class DataPermissionUtils { public static final String COMPONENT_DATA_RULES = "COMPONENT_DATA_RULES"; public static final String SYS_USER_INFO = "SYS_USER_INFO"; /** * 往連結請求裡面,傳入資料查詢條件 * @param request * @param componentRules */ public static void installDataSearchConditon(HttpServletRequest request, List<RoleComponentRuleDTO> componentRules) { // 1.先從request獲取MENU_DATA_AUTHOR_RULES,如果存則獲取到LIST List<RoleComponentRuleDTO> list = loadDataSearchCondition(); if (list==null) { // 2.如果不存在,則new一個list list = Lists.newArrayList(); } list.addAll(componentRules); // 3.往list裡面增量存指 request.setAttribute(COMPONENT_DATA_RULES, list); } /** * 獲取請求對應的資料許可權規則 * */ @SuppressWarnings("unchecked") public synchronized List<RoleComponentRuleDTO> loadDataSearchCondition() { return (List<RoleComponentRuleDTO>) SpringContextUtils.getHttpServletRequest().getAttribute(COMPONENT_DATA_RULES); } public synchronized void installUserInfo(HttpServletRequest request, SysUserCacheInfo userinfo) { request.setAttribute(SYS_USER_INFO, userinfo); } }
在Request中儲存資料規則。
public interface RoleComponentRuleService extends IService<RoleComponentRule> { /** * 根據 使用者域賬戶和元件編碼 獲取元件對應的關係 * * @param userName 域賬號 * @param componentCode 元件編碼 * @return 使用者的所有規則 */ List<RoleComponentRuleDTO> getRoleComponentRule(String userName, String componentCode); }
@Service public class RoleComponentRuleServiceImpl extends ServiceImpl<RoleComponentRuleMapper, RoleComponentRule> implements RoleComponentRuleService { @Resource private RoleComponentRuleMapper roleComponentRuleMapper; /** * 根據 使用者域賬戶和元件編碼 獲取元件對應的關係 * @param userName 域賬號 * @param componentCode 元件編碼 * @return 使用者的所有規則 */ @Override public List<RoleComponentRuleDTO> getRoleComponentRule(String userName, String componentCode) { return roleComponentRuleMapper.getRoleComponentRule(userName,componentCode); } }
<select id="getRoleComponentRule" resultType="com.ifly.vo.RoleComponentRuleDTO"> SELECT tab1.id, tab1.role_id, tab4.role_code, tab1.component_id, tab1.rule_code, tab1.rule_name, tab1.rule_condition, tab1.rule_value, tab4.identity_id FROM wb_role_component_rule tab1 LEFT JOIN user_role_relation tab2 ON tab2.role_id = tab1.role_id LEFT JOIN wb_component tab3 ON tab3.id = tab1.component_id LEFT JOIN wb_role tab4 ON tab4.id = tab1.role_id JOIN role_component_relation tab5 ON tab5.role_id = tab1.role_id AND tab5.component_id = tab1.component_id where tab2.user_account = #{userName} and tab3.component_code = #{componentCode} </select>
@ApiOperation(value = "服務BU-領導-總覽") @GetMapping("opp/getLeaderOverviewSve") @DataPermission(componentRoute = "020202") public Result<SalesProjOverviewSve> getLeaderOverviewSve(@RequestParam(name = "identityId") String identityId) { String permissionSql = RuleQueryGenerator.getPermissionSql(identityId); log.info("查服務BU-領導-總覽-permissionSQL==" + permissionSql); return Result.OK(overviewSveService.getLeaderOverviewSve(permissionSql)); }
在controller的請求方法上加上自定義註解@DataPermission並指定元件編碼,然後通過工具類生成SQL條件,最後將SQL條件傳入service層進行處理。
@Slf4j @UtilityClass public class RuleQueryGenerator { private static final String SQL_AND = " and "; private static final String SQL_OR = " or "; private static final String SQL_JOINT = " (%s) "; /** * 獲取帶有資料許可權的SQL * @param identityId 身份ID */ public String getPermissionSql(String identityId) { //------------------------獲取當前身份的資料規則------------------------------------ List<RoleComponentRuleDTO> conditionList = getCurrentIdentyPermission(identityId); if (CollectionUtils.isEmpty(conditionList)) { //沒有許可權 return "1 = 0"; } //存在許可權 //對當前身份根據規則編碼分組-去除不同角色中相同編碼且規則值為ALL的規則 並根據角色id分組 Map<String, List<RoleComponentRuleDTO>> ruleMap = getRuleMapByRoleId(conditionList); StringBuilder sb = new StringBuilder(); String roleSql; if (MapUtils.isNotEmpty(ruleMap)) { //按角色拼接SQL for (Map.Entry<String, List<RoleComponentRuleDTO>> entry : ruleMap.entrySet()) { List<RoleComponentRuleDTO> lists = entry.getValue(); // 同角色之間使用 AND roleSql = buildRoleSql(lists); //角色之間使用 OR if (StringUtils.isNotEmpty(roleSql)) { jointSqlByRoles(sb, roleSql); } } } return sb.toString(); } private static List<RoleComponentRuleDTO> getCurrentIdentyPermission(String identityId) { //----------------------------獲取所有資料規則----------------------------- List<RoleComponentRuleDTO> roleRuleList = DataPermissionUtils.loadDataSearchCondition(); if(CollectionUtils.isEmpty(roleRuleList)){ return null; } //-----------------------------過濾掉不屬於當前身份的規則----------------------------------- return roleRuleList.stream() .filter(item -> item.getIdentityId().equals(identityId)) .collect(Collectors.toList()); } /** * 構建單角色SQL */ private static String buildRoleSql(List<RoleComponentRuleDTO> lists) { StringBuilder roleSql = new StringBuilder(); for (RoleComponentRuleDTO item : lists) { //如果出現全選 則 代表全部,不需要限定範圍 if ("ALL".equals(item.getRuleValue())) { continue; } //將規則轉換成SQL String filedSql = convertRuleToSql(item); roleSql.append(SQL_AND).append(filedSql); } return roleSql.toString(); } /** * 將單一規則轉化成SQL,預設全部使用 In * ruleCode : area_test * ruleValue : 區域1,區域2,區域3 * @param rule 規則值 */ private static String convertRuleToSql(RoleComponentRuleDTO rule) { String whereCondition = " in "; String ruleValueConvert = getInConditionValue(rule.getRuleValue()); return rule.getRuleCode() + whereCondition + ruleValueConvert; } /** * IN字串轉換 * 區域1, 區域2, 區域3 --> ("區域1","區域2","區域3") * 江西大區 --> ("江西大區") */ private static String getInConditionValue(String ruleValue) { String[] temp = ruleValue.split(","); StringBuilder res = new StringBuilder(); for (String string : temp) { res.append(",'").append(string).append("'"); } return "(" + res.substring(1) + ")"; } /** * 拼接單角色的SQL * @param sqlBuilder 總的SQL * @param roleSql 單角色SQL */ private static void jointSqlByRoles(StringBuilder sqlBuilder, String roleSql) { roleSql = roleSql.replaceFirst(SQL_AND, ""); if (StringUtils.isEmpty(sqlBuilder.toString())) { sqlBuilder.append(String.format(SQL_JOINT, roleSql)); } else { sqlBuilder.append(SQL_OR).append(String.format(SQL_JOINT, roleSql)); } } /** * * 1. 對當前身份根據規則編碼分組-去除不同角色中相同編碼且規則值為ALL的規則 * 2. 對角色進行分組 * @param conditionList 資料規則 * @return 分組後的規則list */ private static Map<String, List<RoleComponentRuleDTO>> getRuleMapByRoleId(List<RoleComponentRuleDTO> conditionList) { //--------過濾掉不屬於當前身份的規則,並對條件編碼進行分組----------------------------------- Map<String, List<RoleComponentRuleDTO>> conditionMap = conditionList.stream().collect(Collectors.groupingBy(RoleComponentRuleDTO::getRuleCode)); //--------相同編碼分組中存在ALL的排除掉----------------------------------------------- List<RoleComponentRuleDTO> newRoleRuleList = new ArrayList<>(); if (MapUtils.isNotEmpty(conditionMap)) { for (Map.Entry<String, List<RoleComponentRuleDTO>> entry : conditionMap.entrySet()) { boolean flag = true; List<RoleComponentRuleDTO> lists = entry.getValue(); for (RoleComponentRuleDTO item : lists) { if ("ALL".equals(item.getRuleValue())) { flag = false; break; } } if (flag) { newRoleRuleList.addAll(lists); } } } if (CollectionUtils.isNotEmpty(newRoleRuleList)) { return newRoleRuleList.stream().collect(Collectors.groupingBy(RoleComponentRuleDTO::getRoleId)); } return Maps.newHashMap(); } }
核心類,用於生成資料許可權查詢的SQL指令碼。
<select id="getLeaderOverviewSve" resultType="com.ifly.center.entity.SalesProjOverviewSve"> SELECT <include refid="column_list"/> FROM U_STD_ADS.LTC_SALES_PROJ_OVERVIEW_SVE <where> <if test="permissionSql != null and permissionSql != ''"> ${permissionSql} </if> </where> </select>
Dao層接受service層傳入已經生成好的sql語句,作為查詢條件直接拼接在業務語句之後。
以上,就是資料許可權的實現過程,其實程式碼實現並不複雜,主要還是得理解其中的實現原理。如果你也有資料許可權的需求,不妨參考一下。
到此這篇關於MySQL資料許可權的實現詳情的文章就介紹到這了,更多相關SQL資料許可權內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45