重庆最专业的房产网站建设,手机浏览器网页加速器,seo教程有什么,led动态视频网站建设今天文章分为两部分 :)
PART1 RBAC权限管理内容分享/ PART2 关于字节跳动一面 10 Minutes Django-RBAC:
PART 1
这权限管理系统主要功能是什么#xff1f;
顾名思义#xff0c;在系统中可以灵活的划分角色组#xff0c;可以根据功能特性来划分#xff1a;- 比如设置系…
今天文章分为两部分 :)
PART1 RBAC权限管理内容分享/ PART2 关于字节跳动一面 10 Minutes Django-RBAC:
PART 1
这权限管理系统主要功能是什么
顾名思义在系统中可以灵活的划分角色组可以根据功能特性来划分- 比如设置系统管理员角色绑定系统所有菜单该角色享有系统全部管理功能- 也可以根据工作职能来划分角色比如设置行政管理角色绑定流程管理功能- 权限的划分可以细致到 数据的浏览、新建、删除、修改等操作行为 - 用户可以关联多个角色组并继承多个角色组的权限 本文更多的是对Django的权限扩展以及如何通过扩展解决实际的业务问题展开讨论并不会对Django权限系统的具体实现和使用方法进行介绍这方面知识未储备好的请先自行脑补。因为学长上周做了一套关于权限的验证方法感到十分痛苦以此记录一下相关内容为了让各位参考也为社会进步。文章内容较长各位同学请心里做好准备 一、Django的权限系统
在介绍Django的权限系统前我们先来认识一下RBAC。RBAC全称Role-Based Access Control即基于角色的访问控制。太学术的东西就不讲了我们可以从企业运营的角度来理解RBAC企业运营过程中需要做很多的事情来达成企业的目标但这些事情并非所有人都可以去做的比如业务人员不可能去处理公司的财务问题所以需要给要做的这些事情设置相应的权限然后将这些权限分配给指定的人但问题来了如果某个员工离职了换了一个新员工来顶替他的位置这时你需要一一的告诉该新员工他应该做什么他不能做什么当工作项非常多的时候这将是非常繁琐的过程且非常容易出错这时就引入了角色的概念角色大家可以理解为企业管理中的岗位通过将工作权限分配给岗位再将员工安排至相应的工作岗位那么该员工就拥有了该岗位应有的权限当人员变动时只需要将新员工安排至该岗位即可整个过程就变得简单多了。这时你也许会问那岗位的工作内容变动时你不是一样需要重新给岗位一一分配权限吗增加了角色反而增加了系统的复杂性话是没错但在企业管理中岗位是相对稳定的而人员则是易变的所以引入角色将极大的提升系统的灵活性和易用性。关于RBAC就简单介绍到这当然RBAC还包含很多的内容希望深入了解的朋友可以Google相关的信息。⛽
Django的权限系统可以认为是轻量级的RBAC系统其中组可以对应RBAC的角色通过将权限分配给组再将用户分配到组中从而实现了授权的过程同时Django还支持直接对用户进行授权这对应对一些特殊情况是十分有用的。关于Django的权限使用的详情可以参考官方文档在此不做详述
Django的权限系统实现了最基本的权限需求但在实际的业务开发过程中仍然存在许多无法满足的需求好在Django是一套异常强大的系统系统内部提供了一套灵活的机制使得开发人员可以方便的对系统的权限进行扩展。本文就我在实际项目过程中所遇到的各种权限方面的问题看我如何通过扩展Django的权限系统实现灵活的权限管理。 拿个上周的Jira说明一下学长遇到的其实是针对不同组来显示不同的数据内容我是用的DRF-去重写get_queryset方法实现因为我想到这个方法最为简单效果也十分不错时间就给三五天于是我就这么干了下面不细说我的项目毕竟签了商汤的保密协议~但是学长找了一个很好拓展权限的例子比我写的好以此来分享给大家✌
二、扩展Django的权限系统
2.1 对象级权限
在开发过程中遇到最多的问题应该就是对象级权限的问题了。Django内置的权限授权是针对Model进行的授权每个Model默认都会生成三个权限add、change、delete。如一旦授予某用户Change权限那么该用户就拥有了该Model所有对象的Change权限但有些时候我们希望仅允许用户对该Model下的指定对象进行操作。
当前学长当前这个项目内容比较乱所有的项目数据报告都归类到了一起其实这么做看似简单后期处理十分麻烦。leader突然要让你分开自己组看自己组的报告...我自己做个数据的过滤也就是上方说的重写get_queryset方法因为接口我用的DRF所以修改起来也还算比较整齐。分享部分代码供参考就是先通过用户判断组别然后在进行数据过滤筛选 所谓对象级权限即针对的Model对象实例进行授权操作使用我们可以精确的对单个对象进行权限的控制以实现更细精度的权限控制。 注此节内容主要分享了下我自己的重写没有太多实质性的东西但很多业务上的权限需求是需要结合多种权限机制实现的在此列出是为方便后文的展开让各位先有个概念上的认识。其实DRF也有一套permission_class可以用的有兴趣同学也可以了解了解。 2.2 系统组的实现
什么是系统组Django默认的权限系统中只有组的概念没有什么系统组啊其实系统组只是我们为了区别与现有组的一种称呼而已你可以给它起一个更酷的名字。那么如何理解系统组呢我们知道Django里的组都需要我们进行硬性的绑定才会生效的比如你创建一个“编辑”的组那么你需要给编辑这个组授予相应内容的编辑权限同时你需要将相应的用户分配至该“编辑”组那么用户才会真正的拥有了编辑应有的权限这在大多数情况下是没有问题的但这种硬性的绑定极大的限制了系统的灵活性因为很多时候系统是需要根据运行时的环境来决定用户应该属于哪些组的。结合上文对象级权限中新闻发布的例子如果使用对象级权限的方式来实现的话我们就必需在用户发布新闻的同时向对象级权限系统中添加一条权限分配的记录对象级权限系统是需要独立的数据表来记录用户或组与对象的权限关系这好像也没什么问题无非是重写save方法或都添加一条signals就可以应对了但如果我们希望用户的上级领导也能够修改该信息呢你可能会说给上级领导也添加一条对象级权限啊但如果该用户的上级领导变更了呢如果我们又希望用户同部门的员工允许修改呢是不是开始变得麻烦了如果有一种机制来处理这种动态关系那事情就变得简单多了而实现这一机制的就是系统组。系统组就是在系统运行期根据运行环境来决定用户所隶属的组从而实现灵活的授权机制。
看完上面的解释好像有点明白了但具体该怎么实现呢
首先大家需要理解的一点是系统组本身并非系统运行时动态创建的而是在开发阶段根据业务需要创建的系统组机制只是在运行时由系统决定用户与系统组的关系而已。因而在业务开发阶段我们就必需确定好业务所需的系统组。当然我们也可以从所有的业务中抽象出一些具备全局通用性的系统组以下列出我们抽象出来的具备通用性的系统组供大家参考 1) Everyone所有人无论是用户还是访客都属于Everyone组。 2) Anonymous匿名用户非认证的用户都属于Anonymous组。 3) Users用户所有认证的用户都属于Users组。 4) Creator创建者针对具体信息的创建者都属于Creator组。 5) Owner所有者具体信息的拥有者都属于Owner组。 看了上边所列出的系统组大家是不是感觉好像又更理解了一些呢做过Windows文件授权的朋友可能还有点似曾相识的感觉对这和Windows系统里的特殊组是一样一样的。除了以上所列出的系统组我们还可以针对特定的业务创建针对性的系统组如上例中允许用户的上级领导修改该用户所发布的信息那么我们可以创建一个名为“信息所有者的上级领导”这样的系统组。
下面我们参考一份拓展django-admin权限的一个设计代码 # 看下面代码我们声明了我们所需的系统组的常量声明为常量是便于代码的调用 SYSTEM_GROUP_EVERYONE Everyone # 所有人 SYSTEM_GROUP_ANONYMOUS Anonymous # 匿名用户 SYSTEM_GROUP_USERS Users # 用户 SYSTEM_GROUP_STAFFS Staffs # 职员 SYSTEM_GROUP_CREATOR Creator # 创建者 SYSTEM_GROUP_OWNER Owner # 所有者
根据上文所述系统组是在开发阶段就应该确定否则后期像学长一样被坑成那么确定后的系统组应该如何在系统中体现呢其实系统组只是我们定义的一个概念为了保证Django系统权限调用接口的一至性它仍然是一个组对象只是是一个我们赋予了特殊意义的组我们仍然需要在Django的“组”这个Model中创建相应的系统组对象当然如果考虑到系统组的特殊性可以通过一些机制来限制对系统组的修改和删除操作以保证系统组始终可用如何限制不是权限系统的核心在此不做详述。你可以通过声明一个系统组初始化的方法并在适当的地方调用他当然你也可以使用Django提供的初始数据方法来初始系统组。看代码 from django.contrib.auth.models import Group def init_systemgroups(): 初始化系统组。 :return: Group.objects.get_or_create(nameSYSTEM_GROUP_EVERYONE) Group.objects.get_or_create(nameSYSTEM_GROUP_ANONYMOUS) Group.objects.get_or_create(nameSYSTEM_GROUP_USERS) Group.objects.get_or_create(nameSYSTEM_GROUP_STAFFS) Group.objects.get_or_create(nameSYSTEM_GROUP_CREATOR) Group.objects.get_or_create(nameSYSTEM_GROUP_OWNER)
好了现在我们已经有了系统组数据了接下来我们就需要实现系统组的核心逻辑了从Django所提供的权限验证接口User.has_perm(perm, objNone)我们可以看出存在两种情况
一种是不提供obj参数的情况我们可以语义化理解为“用户User是否拥有perm权限”一种是提供了obj参数的情况我们可以语义化理解为“用户User是否拥有指定对象obj的perm权限”。
同样的我们系统组也可以分为两种情况一种是与obj参数无关的我们可以语义化理解为“用户User是否为系统组system group的成员”这包括上面所列的Everyone、Anonymous、Users、Staffs等一种是与obj参数有关的我们可以语义化理解为“用户User是否为对象obj的系统组system group的成员”包括上面所列的Creator、Owner等。依赖于相同的参数使得我们可以保持与Django一至的验证接口我们现在要做的是根据这些参数给系统返回恰当的系统组先来看看与obj参数无关的情况的代码 def get_user_systemgroups(user): 获取指定用户所属的系统组集合。 :param user: 指定的用户。 :return: set 表示的用户所属的系统组名称集合。 groups set() groups.add(SYSTEM_GROUP_EVERYONE) if user.is_anonymous(): groups.add(SYSTEM_GROUP_ANONYMOUS) else: groups.add(SYSTEM_GROUP_USERS) if user.is_staff: groups.add(SYSTEM_GROUP_STAFFS) return groups
OK是不是很简单我们只是对User进行简单的验证就可以获得User有关的系统组了而第二种与obj参数有关的情况就比较复杂了比如Creator组我们必需要获取对象的创建者我们才能与User进行比较从而验证User是否为obj的创建者然而Creator组是我们抽象出来的全局通用的组意味着我们的Model需要提供一至的方法来获取对象的创建者这时我们需要定义一个接口Python没有提供接口的概念我们只是通过抽象的方法来模拟来实现 class CreatorMixin(object): 实现创建者的 Model 基类。 def get_creator(self): 获取对象的创建者子类重写该方法实现创建者对象的获取。 :return: 当前对象的创建者。 return None def set_creator(self, user): 设置对象的创建者子类重写该方法实现创建者对象的设置。 :param creator: 要设置为创建者的User对象。 :return: pass class OwnerMixin(object): 实现所有者的 Model 基类。 def get_owner(self): 获取对象的所有者子类重写该方法实现所有者对象的获取。 :return: 当前对象的所有者。 return None def set_owner(self, user): 设置对象的所有者子类重写该方法实现所有者对象的设置。 :param owner: 要设置为所有者的User对象。 :return: pass
现在再来看看第二种情况的实现 def get_user_systemgroups_for_obj(user, obj): 获取指定用户相对于指定的对象所属的系统组集合。 :param user: 指定的用户。 :param obj: 相对于指定的对象。 :return: set 表示的用户所属的系统组名称集合。 groups set() if isinstance(obj, CreatorMixin) and obj.get_creator() user: groups.add(SYSTEM_GROUP_CREATOR) if isinstance(obj, OwnerMixin) and obj.get_owner() user: groups.add(SYSTEM_GROUP_OWNER) return groups
现在我们为了保证系统组的扩展性我们需要定义一套规则使得你可以在你自己的应用中扩展实现自己业务所需要的系统组我们约定在你的应用中应该存在一个模块模块中应该包含有以上声明的get_user_systemgroups(user)和get_user_systemgroups_for_obj(user, obj)两个方法同时你需要在项目的settings.py文件中告诉系统你的系统组实现的模块路径类似如下 # 自定义系统组实现 SYSTEM_GROUP_IMPLEMENTERS [systemgroups.systemgroups, 你自己实现的系统组的路径…]
同时我们需要提供一个方法来根据上面规则依次获取所有应用中的用户所属的系统组集合代码如下 def get_user_systemgroups(user): 从所有应用中获取指定用户所属的系统组集合。 :param user: 指定的用户。 :return: set 表示的用户所属的系统组名称集合。 imps SYSTEM_GROUP_IMPLEMENTERS groups set() if not imps: return groups for imp in imps: imp importlib.import_module(imp) if hasattr(imp, get_user_systemgroups): groups.update(imp.get_user_systemgroups(user)) return groups def get_user_systemgroups_for_obj(user, obj): 从所有应用中获取指定用户相对于指定的对象所属的系统组集合。 :param user: 指定的用户。 :param obj: 相对于指定的对象。 :return: set 表示的用户所属的系统组名称集合。 imps SYSTEM_GROUP_IMPLEMENTERS groups set() if not imps: return groups for imp in imps: imp importlib.import_module(imp) if hasattr(imp, get_user_systemgroups_for_obj): groups.update(imp.get_user_systemgroups_for_obj(user, obj)) return groups
最后我们来实现我们的认证后端 def get_group_permissions(name): 获取指定名称的组所拥有的权限集合。 :param name: 组的名称。 :return: 权限集合。 perms Permission.objects.filter(group__name name) perms perms.values_list(content_type__app_label, codename).order_by() return set([%s.%s % (ct, name) for ct, name in perms])def get_groups_permissions(names): 获取指定名称的组所拥有的权限集合。 :param names: 组的名称集合。 :return: 权限集合。 perms set() for name in names: perms.update(get_group_permissions(name)) return permsclass SystemGroupBackend(object): def authenticate(self, usernameNone, passwordNone, **kwargs): return None def has_perm(self, user_obj, perm, objNone): return perm in self.get_all_permissions(user_obj, obj) def get_all_permissions(self, user_obj, objNone): perms self.get_group_permissions(user_obj, obj) return perms def get_group_permissions(self, user_obj, objNone): result_perms set() groups get_user_systemgroups(user_obj) perms get_groups_permissions(groups) result_perms.update(perms) if obj is None: return result_perms groups get_user_systemgroups_for_obj(user_obj, obj) perms get_groups_permissions(groups) result_perms.update(perms) return result_perms
至此我们完整的实现了系统组的机制以上代码因篇幅原因删减了部分与主逻辑关系不大的代码如权限的缓存等以便于阅读和理解完整的代码请参考项目本著的GitHubhttps://github.com/Kidwind/django-systemgroups。
2.3 权限映射
权限映射又是什么呢要回答这个问题我们还是以上文中的新闻发布的例子来展开我们的新闻应该是根据性质进行分类的比如实事新闻、财经新闻、体育新闻等等这时我们就形成了新闻类别InfoCategory和新闻Info这两个一对多关系的Model随着工作的细分我们需要将不同的分类授权不同的部门来进行管理这时你想到的是什么对就是上文中所提到的对象级权限我们给新闻类别InfoCategory创建一个用于控制新闻类别下的新闻的修改权限名为change_info_by_category通过针对新闻类别InfoCategory进行对象级的change_info_by_category授权如果此时我们要进行某篇新闻的修改权限验证我们需要对新闻所在栏目进行change_info_by_category的权限验证像这样user.has_perm(‘app_label. change_info_by_category’, objinfo.category)有什么问题吗似乎也没什么问题但我们细细分析一下我们原本对新闻Info进行修改的权限验证方法user.has_perm(‘app_label.change_info’, objinfo)需要人为的转换为上面的权限验证相应的Django提供的Admin我们需要重写相应的方法来修改权限验证的逻辑如果新闻Info本身还提供对象级的权限检测我们的逻辑就需要改为要对两个方法都进行验证还有更多复杂的情况情况一变我们就需要重写我们的权限验证逻辑吗很麻烦不是吗如果有一种方法能够实现上述权限验证的自动转换能够保证我们的权限调用方法不变那事情就简单多了而这一方法就是我们所说的权限映射。简而言之权限映射就是将用户对当前对象所执行的权限验证转换为用户对另一个对象的另一个权限进行验证的过程。
看下图 好了分析到这里相信大家对权限映射的作用有了大概的认识下面我们来对代码实现做简单的分析和了解。同系统组一样我们的Model需要提供一至的方法来根据当前的权限验证参数获取映射后的权限验证参数我们定义接口如下 class PermMappableMixin(object): 实现权限映射的 Model 基类。 classmethod def mapping_permission(cls, perm, objNone): 根据当前的权限验证参数获取映射后的权限验证参数。此类方法仅为标记方法子类应实现相应的方法 :param perm: 当前检测的权限。 :param obj: 当前进行检测权限的对象。 :return: 返回值包含两个参数第一个参数为映射后的权限第二个参数为对应映射后的对象其应为映射后权限所对应的 Model 的实例。 return None, None
接下来我们只需要实现我们的认证后端就可以了 from django.contrib.contenttypes.models import ContentTypeclass PermMappingBackend(object): def authenticate(self, usernameNone, passwordNone, **kwargs): return None def has_perm(self, user_obj, perm, objNone): app_label, codename perm.split(.) content_types ContentType.objects.filter( app_label app_label, permission__codename codename) # 根据权限获取其对应的ContentType实例。 for content_type in content_types: model_class content_type.model_class() # 根据 ContentType 实例获取对应的 Model 类 if issubclass(model_class, PermMappableMixin): mapped_perm, mapped_obj model_class.mapping_permission(perm, obj obj) if mapped_perm and user_obj.has_perm(mapped_perm, objmapped_obj): return True return False
是不是很简单接下来看看我们新闻例子的代码 from django.utils.translation import ugettext as _from django.db import modelsclass InfoCategory(models.Model): name models.CharField(max_length128, verbose_name_(分类名称)) class Meta: permissions ( (add_info_by_category, _(允许添加分类信息)), (change_info_by_category, _(允许修改分类信息)), (delete_info_by_category, _(允许删除分类信息)), )class Info(PermMappableMixin, models.Model): category models.ForeignKey(InfoCategory, verbose_name_(所属分类)) title models.CharField(max_length256, verbose_name_(标题)) classmethod def mapping_permission(cls, perm, objNone): mapped_perm None mapped_obj None if perm permmapping.add_info: mapped_perm permmapping.add_info_by_category elif perm permmapping.change_info: mapped_perm permmapping.change_info_by_category elif perm permmapping.delete_info: mapped_perm permmapping.delete_info_by_category if isinstance(obj, cls): mapped_obj obj.category return mapped_perm, mapped_obj
三、写在最后
Django系统提供了一套灵活的认证系统使得我们可以通过扩展其实现灵活的权限控制策略本文结合我在项目过程中的实践经验以及网络大佬的代码分享为大家展示了通过对象级权限、系统组的实现、权限映射来解决项目中所遇到的几种权限问题同时实践中也可以通过组合这几种权限机制实现更多更为复杂的权限策略。其它类的开发语言也可以借鉴Django及上文所提到的几种权限系统的扩展的思路实现各自平台的权限系统。
好了先到这里了如果大家在实践中有什么问题可以给我留言Bye~ -以上简单描述希望对你有所帮助。共勉-
以下为分享的宝藏内容 我认为资料的价值在于能用、好用不是满足人的占有欲和获得感。所以也请各位擦亮双眼提高标准。得到的同时记得他的价值所在收获的同时也请做好择优标准。BTW,学长做的不好的地方欢迎你们提出来又或者如果屏幕前的你将更好的资源拿出分享那真的十分优秀也希望各位能无私互助。获取资料不强制转发。
希望学长分享的内容对你我都有帮助