当前位置: 首页 > 科技观察

如何从零开始设计一个结构清晰、操作友好的权限管理模块

时间:2023-03-14 17:04:47 科技观察

前言对于管理自己群的销售人员,我们来将用户的销售数据进行分组。场景二:马里奥,今天市场部说要分开几个版块,公众号会管理所有的文章投稿和评论,推广会管理各平台的宣传策略计划和执行。是的,竞品的相关信息和数据也应该分开。我们要把它们分开。之前讨论的销售部的事情与此无关。场景三:嗯,xx店老板说,他管理的两家店要同时处理两家店的事务,需要兼顾两家店的销售和进货数据。别人说的需求场景说的有道理,你给他做就行了,对了,别影响其他店的管理!场景四:今天总部运营经理说,他们要从门店、部门、个人用户的角度来看所有的门店。让我们为他们做吧。天天导入excel要疯了。我们一开始讲权限,很容易想到在用户发起请求的时候在界面上做一层权限校验,或者限制读写数据库的权限,简单粗暴。因为限制是基于用户操作的,所以我们在一个接口的handler中定义:ifnotuserin['xxx','yyy','zzz']:return'hasnopermission!'或者在处理数据模型的model层定义:access_users=['xxx','yyy','zzz']限制用户的操作行为。然而,这种硬编码形式简直太可怕了。今天添加一个接口,我们将在处理程序代码中定义一段此权限。明天,另一个处理程序的权限发生变化,我们将再次更改该代码。你说写在装饰器里统一管理,ok,那在添加修改handler的时候,也需要找到对应的有权限的用户,在装饰器里定义。在产品不断迭代的场景下,这是不可接受的。所以我们有必要引入一个模型来规范权限控制。结构化管理通过以上场景,我们很容易得到一个user(单个用户)、group(用户组)、permissions(单一权限)多对多关系的权限结构模型(参考django)。实现了当用户请求接口APIa时,此时获取到的是用户,用户所属的组,以及用户所属组的权限。如果无法获得记录,则限制请求。利用这个模型,我们每次都以模块和函数的形式定义权限,同时定义请求方法。这将一个组与一个权限(即一个处理程序)的权限相关联。同时,将用户与用户组关联起来,这样在权限不断变化的情况下,可以配置一个对应关系,将用户权限限制在单个handler。使用python的同学都知道,由于python装饰器@的语法糖,我们喜欢用这种形式来处理这些与业务逻辑无关的代码,比如日志记录、检查缓存、检查用户session等。有时候写的像在方法装饰器里建楼,一不小心就忘记了。有时候开发者对项目不熟悉,写完新的接口,配置权限,却忘了加装饰器,导致这部分没有被限制。@route()@login_required@check_cache@add_log@permission_verify@transaction.atomicdefinterface_a():所以这里引入一个中间件来处理这个事情,不用每次都额外加代码。中间件操作发生在后端路由调度时。对于每一个请求,我们根据请求的会话找到用户,找到对应的用户组,然后找到权限。将定义的具有权限的模块函数与请求处理程序进行比较。如果它们不同则受到限制。defpermission_middleware():permissions=request.get.user_permissions()if'{}_{}'.format(handler.__class__,handler.func_name)notinpermissions:returnpermission_denied这个后端限制逻辑比较结构化,省了很多麻烦的是方便修改和管理。同时,具有良好的可扩展性。我们将用户定义为单个用户对象,并将权限视为单个处理程序对象。所有门店、部门、集团、小集团等都可以划分到集团层。几乎大部分的业务需求都可以相应的实现。逻辑很准确,好吧,他妈的。受限闭环,对象细化第二天PM来了,说xx部门的小x说他们部门的工作内容需要分类,(我在点头,心里想:嗯,我会的为你的每个类别用户权限添加一个组配置)一个工作类别中有很多工人,并且有一个经理。manager需要查看所有worker的工作进度,job内容manager也可以做(心想:嗯,我就加个manager的组吧,所有权限都一样,单独一个权限查看工作进度)同时!分类1下的管理者也能看到分类2、3下的小y、小z的工作进度!(心想:什么?这不科学吧)PM继续说,因为他们的工作需要衔接,是一个需要查看的功能(我觉得:如果经理不这样就好了’t分类,所有内容都可以看)而且他们也看不到全部!PM继续说,他的主要工作还是这个分类下的工作内容,只是查看其他分类下几个人的进度,你不能给他看所有的内容,有些还是保密的。这确实是个问题,不仅在这种特殊情况下,只要涉及到对某个用户或某个特定内容进行限制操作,就无法实现。辗转反侧后发现,我们之前所做的只是将权限限制在操作行为上,没有形成闭环,也没有定义操作哪些对象。每个限制默认为所有对象。这样虽然方便管理,但是有些场景不能满足需求。因此,引入权限操作对象范围的概念:在之前的权限层上,增加:是否有对象粒度限制,限制哪个模型的对象,可以操作的对象id列表。由于这部分是数据过滤的处理,我们不能在中间件中完成这个操作。把这部分放到数据处理层(DAO层)进行过滤是个不错的主意。首先在数据处理方法中,我们在context中获取本次请求的user对象,按照老方法获取其所有权限。这个时候我们根据我们预先定义的判断:是否有对象粒度限制,限制是根据当前处理的模型过滤掉操作的对象id列表。这样返回数据时,所有的记录都是有操作权限的对象。现在我们满足所有(至少)就是目前遇到的业务需求,后续权限的所有需求也可以按照这个思路配置处理,就可以解决(祝好^_^)前端限制提升体验至此,我们的开发工作会变得很愉快,逻辑清晰,结构严谨,同时给c端用户带来的体验是,哪里都点不进去,点了也不给你,不是不好给个美图或者弹窗,肯定看到403、404或者500的跳转,这时候用户只有一句mmp,再也没有什么破东西了,所以,必须在前端预先限制权限。幸运的是,我们使用大多数情况下使用模板引擎,在页面上判断权限非常方便。在服务器端渲染页面时,根据当前请求用户,获取所有权限,在某个DOM上简单随意写一段判断代码,在流程中放置匹配中间件中handler的过程模板渲染。这样就过滤掉了未经许可的条目,给用户一个安静的世界,也可以避免点击不该点击的东西。{%ifuser.permissions.can_modify_xxx%}修改xxx{%endif%}如果没有使用模板引擎,我们需要在浏览器中渲染页面,添加一个ajax请求加载到用户的权限列表中,后面用js把不该看到的东西遮住。前端组件化大大提升了用户体验,但是回顾一下我们的做法,写了一个新的函数,在处理掉所有的权限和关系配置之后,我们需要在页面的所有地方都使用这个模块函数字符串写一个判断语句很麻烦。而如果要修改一个权限的定义,那就是一场灾难。为了解决这个问题,我们不得不借鉴组件化设计模式,即模板复用。这个目标可以通过使用模板引擎的继承和导入来实现。当然,React是一种天然的组件设计理念。当一个顶层组件初始化时,我们从服务器拉取用户的权限列表,存储在本地,然后依次向下流到各级子组件。每个组件都配置了一个id(可以和模块函数字符串一致,也可以做一个映射)当组件在权限列表中找到自己对应的id时,就会渲染。最后整个页面根据权限列表渲染所有可展示的部分。刚才说的组件id是前后端限制的关键。后端根据模块函数判断handler处理,前端根据id判断是否渲染。那么这个配置应该是前端编写,后端配置,或者后端提供。前端自己配置权限入口?我们在讨论开发模型的时候,总是会遇到这样一个问题:1.是否分离前端分离前端工作负载、路由、组件加载、部署等等都要处理,有时候项目不是很大,这种分离得不偿失,但好处是前后端各司其职,各司其职,没有不必要的工作量。不分离的情况是使用模板引擎,在前端提供静态页面,在服务端渲染,这样有时候在后端处理数据的时候页面样式或者动画效果可能会被破坏,然后两端都要反复修改。优点是开发简单,部分功能简单,比较单一,直接一起处理即可。无论分离还是不分离,都需要提供一个配置服务作为中间产品。分离的时候,我们可以让前端写好组件后??在系统中配置自己的组件id,后端按照正常的限制逻辑进行处理。不分开的时候,直接根据前端页面写的id进行配置,这部分可以由后端来完成。公共区有了这个系统,大家一定很开心,又可以一起开心的玩(coding)了。有很多开源项目都在处理这个问题。比如基于流行的djangoadmin、flaskadmin等后台管理项目,就可以很好的构建这种自定义的公共配置部分。当然,具体的设计结构需要根据系统需求进行修改。可能存在一些不需要粒度的场景。我们不需要关闭操作对象的循环。如果是权限严格的场景(马里奥遇到的场景)似乎有必要。.追求用户体验,前端限制是必要的。组件化会使结构非常清晰,同时增加工作量。公共区域的配置模块就像一个前后端枢纽,或者说一个合约,不仅在权限限制方面有帮助,在所有的共同开发场景中也有帮助。【本文为栏目组织《奇安科技》原创文章,转载请微信公众号(bigsec)联系原作者】点此查看更多本作者好文