精华 基于RESTful API 怎么设计用户权限控制?
发布于 2 年前 作者 JerryC8080 30041 次浏览 最后一次编辑是 5 个月前 来自 分享

原文链接:简书

前言

有人说,每个人都是平等的; 也有人说,人生来就是不平等的; 在人类社会中,并没有绝对的公平, 一件事,并不是所有人都能去做; 一样物,并不是所有人都能够拥有。 每个人都有自己的角色,每种角色都有对某种资源的一定权利,或许是拥有,或许只能是远观而不可亵玩。 把这种人类社会中如此抽象的事实,提取出来,然后写成程序,还原本质的工作,就是我们程序员该做的事了。 有了一个这么有范儿的开头,下面便来谈谈基于RESTful,如何实现不同的人不同的角色对于不同的资源不同的操作的权限控制。

RESTful简述

本文是基于RESTful描述的,需要你对这个有初步的了解。 RESTful是什么? Representational State Transfer,简称REST,是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。 REST比较重要的点是资源状态转换, 所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。 而 “状态转换”,则是把对应的HTTP协议里面,四个表示操作方式的动词分别对应四种基本操作:

  1. GET,用来浏览(browse)资源
  2. POST,用来新建(create)资源
  3. PUT,用来更新(update)资源
  4. DELETE,用来删除(delete)资源

RESTful CURD

资源的分类及操作

清楚了资源的概念,然后再来对资源进行一下分类,我把资源分为下面三类:

  1. 私人资源 (Personal Source)
  2. 角色资源 (Roles Source)
  3. 公共资源 (Public Source)

Sources

“私人资源”:是属于某一个用户所有的资源,只有用户本人才能操作,其他用户不能操作。例如用户的个人信息、订单、收货地址等等。 “角色资源”:与私人资源不同,角色资源范畴更大,一个角色可以对应多个人,也就是一群人。如果给某角色分配了权限,那么只有身为该角色的用户才能拥有这些权限。例如系统资源只能够管理员操作,一般用户不能操作。 “公共资源”:所有人无论角色都能够访问并操作的资源。

而对资源的操作,无非就是分为四种:

  1. 浏览 (browse)
  2. 新增 (create)
  3. 更新 (update)
  4. 删除 (delete)

角色、用户、权限之间的关系

角色和用户的概念,自不用多说,大家都懂,但是权限的概念需要提一提。
“权限”,就是资源与操作的一套组合,例如"增加用户"是一种权限,"删除用户"是一种权限,所以对于一种资源所对应的权限有且只有四种。

Permissions

角色用户的关系:一个角色对应一群用户,一个用户也可以扮演多个角色,所以它们是多对多的关系。
角色权限的关系:一个角色拥有一堆权限,一个权限却只能属于一个角色,所以它们是一(角色)对多(权限)的关系
权限用户的关系:由于一个用户可以扮演多个角色,一个角色拥有多个权限,所以用户与权限是间接的多对多关系。

Relations

需要注意两种特别情况:

  1. 私人资源与用户的关系,一种私人资源对应的四种权限只能属于一个用户,所以这种情况下,用户和权限是一(用户)对多(权限)的关系。
  2. 超级管理员的角色,这个角色是神一般的存在,能无视一切阻碍,对所有资源拥有绝对权限,甭管你是私人资源还是角色资源。

数据库表的设计

角色、用户、权限的模型应该怎么样设计,才能满足它们之间的关系?

Models

对上图的一些关键字段进行说明:

Source
  • name: 资源的名称,也就是其他模型的名称,例如:user、role等等。
  • identity: 资源的唯一标识,可以像uuid,shortid这些字符串,也可以是model的名称。
  • permissions : 一种资源对应有四种权限,分别对这种资源的browse、create、update、delete
Permission
  • source : 该权限对应的资源,也就是Source的某一条记录的唯一标识
  • action :对应资源的操作,只能是browse、create、update、delete四个之一
  • relation:用来标记该权限是属于私人的,还是角色的,用于OwnerPolicy检测
  • roles: 拥有该权限的角色
Role
  • users : 角色所对应的用户群,一个角色可以对应多个用户
  • permissions: 权限列表,一个角色拥有多项权利
User
  • createBy : 该记录的拥有者,在user标里,一般等于该记录的唯一标识,这一属性用于OwnerPolicy的检测,其他私有资源的模型设计,也需要加上这一字段来标识资源的拥有者。
  • roles : 用户所拥有的角色

策略/过滤器

在sails下称为策略(Policy),在java SSH下称为过滤器(Filter),无论名称如何,他们工作原理是大同小异的,主要是在一条HTTP请求访问一个Controller下的action之前进行检测。所以在这一层,我们可以自定义一些策略/过滤器来实现权限控制。
为行文方便,下面姑且允许我使用策略这一词。

** 策略 (Policy) **

下面排版顺序对应Policy的运行顺序

  1. SessionAuthPolicy
    检测用户是否已经登录,用户登录是进行下面检测的前提。
  2. SourcePolicy
    检测访问的资源是否存在,主要检测Source表的记录
  3. PermissionPolicy
    检测该用户所属的角色,是否有对所访问资源进行对应操作的权限。
  4. OwnerPolicy
    如果所访问的资源属于私人资源,则检测当前用户是否该资源的拥有者。

如果通过所有policy的检测,则把请求转发到目标action。

Policies

Sails下的权限控制实现

在Sails下,有一个很方便的套件sails-permissions,集成了一套权限管理的方案,本文也是基于该套件的源码所引出来的权限管理解决方案。

结语

对程序员最大的挑战,并不是能否掌握了哪些编程语言,哪些软件框架,而是对业务和需求的理解,然后在此基础上,把要点抽象出来,写成计算机能理解的语言。
最后,希望这篇文章,能够帮助你对权限管理这一课题增加多一点点理解。

写作参考

60 回复

呃。。。图片是挂的,简书有防盗链机制

@alsotang 我可以看的到喔,好吧,我处理一下图床

mark一下 自豪地采用 CNodeJS ionic

好东西,感谢分享,已经收藏了

感谢分享。学习。

感谢分享, 不过有2个疑问, 如下:

1. 效率低 先不说那几张权限表是不是放到了 redis 中, 就连数据插入/删除的时候, 还要对应去写一下 source 表才行. 心塞. 其次就是每次都要经过4个管道, 并且这4个管道都是需要遍历数据库的. (根本没办法直接根据 key 来判断是否有权限) 2. 表设计不明确 Premission 中的 source 为什么是一个 int 型? 难道是 source 的自增列? 这4个表之间的关系图中没有表示.

有助于理解REST,mark。

哇哈哈,厉害厉害,这位同志很用心,32个赞

@TossShinHwa 首先谢谢这么用心看完了。请允许我回答一下你的问题:

For 1: 相对于大家所熟悉的auth token的话,本文描述的方法相对来说是有查询频繁的缺陷,你的回复也让我有了另外的研究课题了。 For 2:Permission 中的 source 字段,其实是 Source 里面的某条记录的 id, 在文中已经描述过了。而为何在4个表的图里面,为什么没有标清楚表之间的关系图,我是觉得在讲述“角色、用户、权限之间的关系”的时候,描述了它们之间的关系,在图片,我旨在简单一点,可能会易懂点,着重讲表的关键字段。

真的很谢谢你的评论!!令人有所思考呢!

这里有一点,通常认为一个用户只属于一个角色,一个权限可以被多个角色拥有。虽然结果是一样的。 自豪地采用 CNodeJS ionic

@JerryC8080 所噶. 很高兴能有帮助. 另外说几点个人意见.

  1. 应该把所有权限数据冗余到 redies 里面, 然后实现根据一个 key 就能判断结果: 如果 key 存在, 则有权限. 这样来说会快很多. (比如 key 可以是 userid|action|sourceid 或者 roleid|action|sourceid, 不过相应的这在修改权限的时候会变得很麻烦.)

  2. 在你的表设计里面, 你把业务权限(比如某一种 resource 的 DELETE 权限) 和 数据权限(比如特定某一条 resource/:id 的 DELETE 权限) [也就是你的私有权限] 没有很好的区分, 感觉很不好. (像上面那个 redis 的 key, 可以设计成 userid|action 和 userid|action|sourceid, 分别表示业务权限和数据权限. 感觉会清晰的多)

  3. 另外, 管道的第一步其实应该是判断这个 resource 是不是 public 的. 这一点似乎没有体现?

说的比较乱, 见谅.

@TossShinHwa 目前权限好像只能想到这里,有没有更好的想法,可以讨论下。 感觉目前的权限架构说白了,就是根据uri进行判断 流程很多,也很繁琐。

如果有类似文件系统的资源权限控制的,要怎么实现呢?不太可能每一个文件都进行配置权限吧?

@lonso 不仅 url 呢, 我之前还遇到过一个 resource 里面, 有一些字段有权限, 有一些又是开放的. 这怎么搞?

比如比较常见的情况: User, 一般所有用户都可以查看其他用户的资料, 但是不能查看 password 字段. 这是很常见的. 想想都蛋疼.

@alsotang 不是说https能过滤跳过这个防盗链的嘛?

还有楼主的文章很受用,谢谢!:D

@think2011 我关掉那个特性了。现在我们对外的请求是带 referer 的

有深度的干货 自豪地采用 CNodeJS ionic

@TossShinHwa

像这种字段限制或者数据查询限制,比如限制查询的金额之类的需求, 增加张角色-资源-字段表,专门做字段控制和数据查询控制。

mark,有空了了细细拜读

在用sails.js, 感觉policy挺好使的,就回到这个帖子,发现原来用的模块就是sails里的。。。

我也在找类似这样的需求,express 有类似的policy 的中间件吗? 或者这个sails-permissions 能否结合到express中呢

非常不错,mark

关于sails的resouce 相关的policies,我写了两个库提供了解决resource相关的权限验证

  • sails-hook-deepblueprints 提供一个扩展blueprints rest api的hook,可以实现各层之间resource的关联验证。
  • police-tree 基于sails的policies策略扩展的,可以对path的各个节点进行单独验证

其实以上本质都是基于express的中间件

deepblueprints做最基础的resource之间的关联验证,比如:/resource1/:id1/resource2/:id2/resource3/:id3,会验证id3是否属于id2,id2是否属于id1,这里只是单纯的关联验证,不涉及具体权限验证

权限验证则在police-tree里面做,比如创建一条project:post /company/1/team/2/project/3,我要验证当前登录用户是否对id为1的company具有可以创建project的权限,那么我只需要在sails的policies目录增加对company的police配置:

module.exports = function(req, res, next) {
  require("police-tree")(req, res, next, {
      checkPoint : {
        "post": function (req, res, companyId, cb) {
         	var userId = req.session.user.id;// 获取当前登录用户id
			if(verifyUserCanCreateProject(userId, companyId)){ // 验证用户是否有权限创建project
				cb(true); // verify success, and goto next point
			}else{
				cb(false); // verify fail and reutn 403
			}
        }
      });
}

@JerryC8080 LZ,感觉权限可以被多个角色拥有吧,如果A用户拥有多个角色,那么A用户应该含有这些角色的全部权限,然后取这些角色拥有的权限的并集

感谢 好文收藏

来自炫酷的 CNodeMD

竟然不支持收藏

来自炫酷的 CNodeMD

先收藏,在学习。

来自酷炫的 CNodeMD

好文 学习了

@TossShinHwa 是不是可以吧 用户其他资料看成一种资源,用户密码看成一种资源;如果用户看成资源,貌似可以通过 GET user/:id?own=[0|1],RESTAPI 权限确实比较难控制,还是不懂

角色与权限的关系:一个角色拥有一堆权限,一个权限却只能属于一个角色,所以它们是一(角色)对多(权限)的关系

比如说网站编辑和管理员这两种角色,他们不应该有某个相同的权限吗?所以说一个权限可以属于多个角色的吧?

具体的业务具体分析;这种,针对结构简单型网站;没问题; 商城类以上的,难于掌控了; 它不是在你 访问数据之前就能用权限筛选出来的。

还有列表的筛选 权限范围内的;肯定是先经过 action 再做相应的查询 权限范围内的;所以这个设计感觉,好有限。

几个关系分析的挺好

有个 restful api 神器 LoopBack

mark <br><br>来自<a href=“https://lzxb.github.io/react-cnode/” target="_blank">react-cnode手机版</a>

loopback有全套已经实现好的role控制.可以免去很多自己实现的痛苦

请问楼主 验证分别经过 source permission role 这个 source 表我看好像是对应于某一个资源?那创建一个这种资源的时候应该还不存在相应的资源吧?这个时候该怎么做

@TossShinHwa 基于Redis(Hash)做一下缓基本不会有太大问题的具体实现可以参考论文: https://github.com/orangebook/FlyLab/blob/master/doc/201310-基于RBAC框架实现ABAC.pdf

PS:近日在学习这方面的内容,并整理相关资料

受教

来自酷炫的 CNodeMD

对我学习node有点点帮助!

回到顶部