Python中一种新的RESTful API权限设计方案讨论

请求权限映射

根据RESTful的相关风格规范, 我们将请求映射为以下几种操作

GET     /users/                ----->  `list.users` 
GET     /users/:id/            ----->  `retrieve.users`
POST    /users/                ----->  `create.users`
PUT     /users/:id/password/   ----->  `replace.users`
PATCH   /users/:id/            ----->  `update.users`
DELETE  /users/:id/            ----->  `destroy.users`

权限控制器映射

如果后端以MVC模式进行开发, 那么我们可以映射如下控制器

`list.users`        ----->  list(users) 
`retrieve.users`    ----->  retrieve(user,id)  
`create.users`      ----->  create(users)       
`replace.users`     ----->  replace(users,id,field)   
`update.users`      ----->  update(users,id)           
`destroy.users`     ----->  destroy(users,id)           

鉴权流程

权限的管理采用传统的RBAC模式

  1. 身份验证,返回具体user或者anonymous,接下来我们把这一步返回的user都作为正常user
  2. 验证请求权限,即上述验证请求权限映射
  3. 验证资源存在性与所属权, 这里存在争议.
    • 如果放到控制器之前, 那么可能会出现格外数据库查询,同时会增加代码上的复杂性, 但是可以把所有鉴权过程放到一起.
    • 如果放到控制器中,鉴权过程分开了,由于不同的资源可能有不同的所属权判断标准,这样可以增加灵活性.

讨论点

  1. 资源存在性与所属权放到控制器里还是作为中间件放到控制器之前?
  2. 请求权限映射有哪些需要改进的地方?
  3. 能否将整个认证鉴权流程规范化?

Python中一种新的RESTful API权限设计方案讨论

25 回复

并没觉得新在哪里。

我们的做法:功能权限前置,数据权限后置(对应你这里资源权限)。

现在想想,设计良好的系统也可以做到数据权限前置,而不会(明显)增加查询次数。比如,将数据权限中的查询结果透传给控制器。


我最近也在琢磨这事儿。传统的RBAC在微服务里确实有点力不从心,特别是跨服务权限校验的时候。

我试过一种基于资源策略的权限设计,感觉还不错。核心是把权限规则写成JSON策略文件,每个API端点关联一个策略ID。网关层统一校验,业务层只关心业务逻辑。

# 策略定义示例
permission_policies = {
    "project:read": {
        "resource": "project",
        "action": "read",
        "conditions": [
            {"field": "user.role", "operator": "in", "value": ["admin", "member"]},
            {"field": "project.team_id", "operator": "eq", "value": "user.team_id"}
        ]
    }
}

# 权限校验器
class PolicyValidator:
    def __init__(self, policies):
        self.policies = policies
    
    def check(self, user, policy_id, resource=None):
        policy = self.policies.get(policy_id)
        if not policy:
            return False
        
        for condition in policy["conditions"]:
            if not self._eval_condition(user, condition, resource):
                return False
        return True
    
    def _eval_condition(self, user, condition, resource):
        # 实现条件判断逻辑
        field_value = self._get_field_value(condition["field"], user, resource)
        return self._apply_operator(field_value, condition["operator"], condition["value"])

这种设计的好处是权限规则可配置,不用改代码就能调整权限逻辑。配合缓存的话,性能也还行。

不过得注意策略管理的安全性,别让人随便改策略文件。还有就是条件表达式别搞太复杂,不然调试起来要命。

总结:策略驱动比硬编码灵活多了。

数据权限前置我发现的最大问题就是不同类型的资源可能有不同的判断方式,如果针对不同资源写不同方法,前置的意义也就不大了

对,会丧失一定的灵活性。通常数据权限的判断逻辑非常复杂多样。所以要综合权衡一下利弊。

市面上现在似乎没有鉴权规范化的项目

  1. RESTful 标准中 PATCH 的语义是 update/replace , PUT 的语义是 update/modify 。我认为用一个 PUT 方法就可以,没必要多一个 PATCH ,少一接口,多一份放心。很多 API 都没有实现 PATCH 方法,都是用 PUT 实现的。

    2. 我觉得放在控制器中比较好。一般来说中间件是一种通用的解决方案,权限控制又是一种十分复杂而且多变的。作为一个中间件,没有任何复用性可言。

不是 PUT 添加, POST 修改吗

童鞋,你确定你看的不是假文档?

POST 添加, PUT 修改, PUT 的 URL 指向唯一资源

PATCH 可以是 partial entity , PUT 不行,所以这里还是有区别的

其实这样写 性能低,有测试过,在解析 url 时候

yes ,只要你知道资源的唯一 URL 就无论添加还是修改都行,但如果只知道一个 collection 的 URL 就不行

也就是将权限验证绑定到控制器里效率更高?

就是单单测试 url 性能,{id}get 和 get?id=xx
这两种比较。权限可以在 filter 或者 interseter 里面判断。反正都是 具体功能前的,而且最佳就是加上权限缓存。

反正我不用 resetful 规范,因为业务多了就不优雅了。

话说 PATCH 好像不支持 formdata 吧?有图片上传的操作这个的话就比较麻烦了

文件上传通常是统一做成单独的接口,以 Post 方式上传至 oss 或者其它地方,获取到返回的文件信息后再用于其它接口

可以说一下你现在使用的规范吗

最好设计上保持鉴权逻辑的通用和统一 ,然后鉴权前置。

至于所说的多一次数据库查询问题,可以把鉴权时查询到的数据放到一个当前请求的 context 中,业务逻辑如有需要,可以直接使用这个 context 中的数据,请求结束数据根据 context 一起销毁。

这样会不会增加客户端的工作?这样做法 客户端所有涉及文件 的都 得先上转 oss 再把回来的信息 post 到服务器上

之前我也有想过这个方式 ,但好像会增加客户端的工作量,之后就没有这样做了

给写入方法都 是用 POST + formdata 实现

  1. 使用用 OAuth/OpenID 或者 JWT 时,返回的 Token 中都可以添加一个 scopes ,在用户登录时取得该用户所有的权限。与服务器交互一般都是 Stateless 方式去请求 API , Header 加上 Token 。
    2. 服务器端解密 Token , 可以得到该用户分配的所有权限。直接对照检测 API 权限就行了。

    上述方法与所用语言无关。

没觉得新在哪啊, apache shiro 不就在干这事吗,不过那个是 java 的

Patch 已经有相关的具体标准 JSON Patch 等,去修改 Entity 部分属性。

https://tools.ietf.org/html/rfc6902

之前我用到了 Spring Sync 支持 Patch 。

https://spring.io/blog/2014/10/22/introducing-spring-sync

下一代的 JAXRS 2.1 ( Java EE 8 标准)会加入官方的 Patch 支持(虽然自己实现不难)。

权限
如果你是了解 spring mvc,权限验证可以放在 interceptor
http->tomcat->interceptor(check user is login and auth resource)->biz_service
restful 风格,感觉它只是给了一个指导,实际需要我们自己拓展.
我建议使用 biz_group/biz_add or other 数据放在 post 中
因为在复杂业务下,需要表达的含义远多于 crud,从可读代码上看

我是觉得作为中间件放到控制器之前好,首先代码侵入性较小,其次只需要单独维护一套权限系统就行,只要简单修改就可以用到其它系统中,权限的验证无非是验证 + 回调,我不认为会增加复杂性,当然,会造成额外的数据查询这是个问题

回到顶部