zoukankan      html  css  js  c++  java
  • 大话设计,没有模式—通用权限设计与实现

    当代码写多了,总有些是经验,但经验是什么呢?if…else用的次数比别人多?显然不是。有些超棒的设计可以谓之经验!

    功能权限

    网络上流行的经典的权限设计是【主体】- 【领域】 - 【权限】( who、what、how问题原型 ) 的设计思想,其中:

    【主体】可以是用户,可以是角色,也可以是一个部门

    【领域】可以是一个模块,可以是一个页面,也可以是页面上的按钮

    【权限】可以是“可见”,可以是“只读”,也可以是“可用”(如按钮可以点击)

    为了简化程序开发,在OpenAuth.Core中去掉了权限的控制,简化为【主体】- 【领域】的模式,且【主体】限定为角色。即只能给角色分配模块和按钮,不能直接分配给用户账号或部门。且一旦分配即表示该角色拥有操作该模块(按钮)的权限。

    file-read-61

    大道至简,标准的RBAC规范比这个还要简洁,所以它的生命力才最顽强。在此基础上,如果进行二次开发,进行更详细的功能权限控制,可以按照上面的思想进行改造。

    数据权限

    数据权限是在功能权限的基础上面进一步的扩展,比如可以查看订单属于【功能权限】的范围,但是可以查看哪些订单就是【数据权限】的工作了。

    这里面的几个概念:

    【资源】:数据权限的控制对象,业务系统中的各种资源。比如订单单据、销售单等。

    【数据规则】:用于数据权限的条件规则 。

    应用场景

    • 销售单,可以由本人查看
    • 销售单,测试角色能看到自己的订单
    • 销售单,测试角色能看到自己的订单,管理员角色可以看到所有订单
    • 销售单,测试角色能看到自己的订单,管理员角色可以看到所有订单,账号test3可以看到应用名称为"xxx管理系统"的订单

    我们能想到直接的方法,在访问数据的入口加入SQL Where条件来实现,以上4种情况对应的sql语句:

      1 where CreateUserID = {loginUser}
      2 where CreateUserID = {loginUser} and {loginRole} in (测试)
      3 where CreateUserID = ({loginUser} and {loginRole} in (测试)) or {loginRole} in (管理员)
      4 where CreateUserID = ({loginUser} and {loginRole} in (测试)) or {loginRole} in (管理员) or ({loginUser}==test3 and 应用名称==XXX管理系统)
    

    这些一个一个的"条件",简单理解为一个【数据规则】,通常会与原来我们前台的业务过滤条件合并再检索出数据。

    每一个角色有不一样的【数据规则】,那么数据权限设计就变成

      【资源】 - 【数据规则】

    在数据库记录中存放为资源ID+规则的形式,如下:

    微信截图_20191126223141

    为了简化程序开发,在开发过程中可以做出以下约定:

    • 所有需要进行数据权限控制功能的表,在设计时必须包含字段CreateUserId(创建用户Id)。
    • 【资源】的名称限定为模块名称。如部门管理,用户管理,那么资源就是对应的部门列表,用户列表。
    • 如果【资源】没有设置数据规则,那么视为该资源允许被任何主体查看。
    • 数据规则中授权的对象限定为角色、用户。即不能设定为某个部门所有,如果想实现类似的功能,通过角色间接实现。例如:资源属于角色“开发组成员”,“开发组组长”。

    核心实现--查询对象模式

    权限控制总离不开一些条件的限制,如果没有完善的查询机制,那么在做权限条件过滤的时候你会觉得很别扭。马丁在《企业应用架构模式》第13章对象-关系元数据映射模式中提出查询对象模式(Query Object Pattern)。该模式核心结构如下:

    下载

    该结构为【数据规则】的建立提供了理论基础。在设计数据权限的时候,可以照搬该思想。上面例子中的规则,数据规则展开如下:

      1 {
      2 	"Operation": "or",
      3 	"Filters": [{
      4 		"Key": "{loginRole}",
      5 		"Value": "09ee2ffa-7463-4938-ae0b-1cb4e80c7c13",
      6 		"Contrast": "contains",
      7 		"Text": "管理员"
      8 	}],
      9 	"Children": [{
     10 		"Operation": "and",
     11 		"Filters": [{
     12 			"Key": "{loginRole}",
     13 			"Value": "0a7ebd0c-78d6-4fbc-8fbe-6fc25c3a932d",
     14 			"Contrast": "contains",
     15 			"Text": "测试"
     16 		}, {
     17 			"Key": "CreateUserId",
     18 			"Value": "{loginUser}",
     19 			"Contrast": "==",
     20 			"Text": ""
     21 		}]
     22 	}, {
     23 		"Operation": "and",
     24 		"Filters": [{
     25 			"Key": "AppName",
     26 			"Value": "XXX管理平台",
     27 			"Contrast": "==",
     28 			"Text": ""
     29 		}, {
     30 			"Key": "{loginUser}",
     31 			"Value": "229f3a49-ab27-49ce-b383-9f10ca23a9d5,1df68dfd-3b6d-4491-872f-00a0fc6c5a64",
     32 			"Contrast": "in",
     33 			"Text": "test3,test4"
     34 		}]
     35 	}]
     36 }

    规则分为三个部分:【分组】(Children)、【规则】(Filter)、【操作符】(and or),而规则自身就是一个分组。这种简单的结构就可以满足全部的情况。看不懂啥意思?

    微信截图_20191125234345

    这样理解起来就比较简单了。

    48ddd81b9850d6b30edb8c2534b6713c

    还不懂??我们从简单的开始:

    数据权限实例--按角色

    某一角色只能看到自己创建的信息。如:针对资源列表,我们设置了【测试】角色可以看到自己创建的资源。

    微信截图_20191125231835

    这时如果用一个拥有测试角色的账号(test)登录,那么他只能看到自己创建的资源:

    微信截图_20191125232012

    其他角色的账号登录时,会出现无法看到任何信息。我们稍做调整:【管理员】角色可以看到所有资源,【测试】角色只能看到自己创建的资源。

    微信截图_20191125233925

    这时如果用一个拥有管理员角色的账号(admin)登录,那么他就可以看到全部资源:

    微信截图_20191125233839

    以此为基础,我们可以扩展非常复杂的数据权限控制。最终形成最后的复杂规则:【管理员】角色可以看到所有资源,【测试】角色只能看到自己创建的资源。账号test3/test4只能看到应用名称等于“XXX管理平台”的资源。

    数据权限实例--按部门

    管理员】可以看到所管辖部门的所有数据,其他角色只能看到自己的。

    代码解析

    可以在应用层基类BaseApp中,定义一个通用函数,根据当前即将访问的资源Id获取相应的访问【数据规则】,并把当前登录的用户信息注入到该规则中,最终转换成针对数据库的查询,如下:(不想看这个?直接跳过吧,看看如何使用也没有问题)

      1         protected IQueryable<T> GetDataPrivilege(string parametername)
      2         {
      3             var loginUser = _auth.GetCurrentUser();
      4             if (loginUser.User.Account == Define.SYSTEM_USERNAME) return UnitWork.Find<T>(null);  //超级管理员特权
      5 
      6             var moduleName = typeof(T).Name;
      7             var rule = UnitWork.FindSingle<DataPrivilegeRule>(u => u.SourceCode == moduleName);
      8             if (rule == null) return UnitWork.Find<T>(null); //没有设置数据规则,那么视为该资源允许被任何主体查看
      9             if (rule.PrivilegeRules.Contains(Define.DATAPRIVILEGE_LOGINUSER) ||
     10                                              rule.PrivilegeRules.Contains(Define.DATAPRIVILEGE_LOGINROLE))
     11             {
     12 
     13                 //即把{loginUser} =='xxxxxxx'换为 loginUser.User.Id =='xxxxxxx',从而把当前登录的用户名与当时设计规则时选定的用户id对比
     14                 rule.PrivilegeRules = rule.PrivilegeRules.Replace(Define.DATAPRIVILEGE_LOGINUSER, loginUser.User.Id);
     15                 var roles = loginUser.Roles.Select(u => u.Id).ToList();
     16                 roles.Sort(); //按字母排序,这样可以进行like操作
     17                 rule.PrivilegeRules = rule.PrivilegeRules.Replace(Define.DATAPRIVILEGE_LOGINROLE,
     18                     string.Join(',',roles));
     19             }
     20             return UnitWork.Find<T>(null).GenerateFilter(parametername,
     21                 JsonHelper.Instance.Deserialize<FilterGroup>(rule.PrivilegeRules));
     22         }

    这样在我们自己的应用中,使用上面的函数创建一个基础查询,再加上自定义的查询条件即可轻松实现数据权限的控制。

      1             var result = new TableData();
      2             var objs = GetDataPrivilege("u");
      3             if (!string.IsNullOrEmpty(request.key))
      4             {
      5                 objs = objs.Where(u => u.Id.Contains(request.key));
      6             }
      7 
      8             result.data = objs.OrderBy(u => u.Id)
      9                 .Skip((request.page - 1) * request.limit)
     10                 .Take(request.limit);

    最后

    以上所有代码均已实现并开源(配置数据规则的界面可以自己画,layui的前端画起来实在太费力),OpenAuth.Core秉承代码之美,为.net core添砖加瓦,喜欢的star一下!

     

  • 相关阅读:
    几个常用myeclipse快捷键
    5G layer
    customize the entry point of pod runtime
    关于JS Pormise的认识
    修改 /etc/pam.d/login, linux 本地账号密码无法登陆,一直返回 登陆的login界面
    Java支付宝PC网站支付功能开发(详细教程)
    支付宝PC支付功能异步通知签名验证失败解决方案
    提交代码出现 Push to origin/master was rejected 错误解决方法
    易语言连接RCON详细教程实例(演示连接Unturned服务器RCON)
    易语言调用外部DLL详细实例教程
  • 原文地址:https://www.cnblogs.com/yubaolee/p/DataPrivilege.html
Copyright © 2011-2022 走看看