如何用 Python 实现一个权限管理系列 (一)

最近在实现大型 B2B 系统之后,有很多细节需要考虑,其中一环就是权限控制。之前考虑的比较少,认为这一块框架可以 handle 问题不大,后来发现 Tornado 目前没有一个比较完善的权限管理模块,甚至连个包都没有… 于是只能自己动手,丰衣足食了。参考的方式也是通过 Flask-Principal 的类似实现方式。

原型

一个权限管理模块应该有的功能应该有哪些呢?从基本角度出发,应该包含用户身份 (Identity)、用户角色(RoleNeed)、用户权限(Permission)这三个最基本的分类组成。那么从最开始,就需要定义这几个类:

1
2
3
4
5
6
7
8
class RoleNeed(object):
pass

class Identity(object):
pass

class Permission(object):
pass

再考虑一下,其实 RoleNeed 更像一个权限的枚举数组,除此之外,比如用户可能存在的权限与分组和单独的用户权限都有关系,这里需要更加抽象一下。具体的可以分成 UserNeed 和 RoleNeed,分别对应用户权限与用户组权限。这样如果通过类实现还是比较麻烦,需要继承,但是实际上这也只是一个元组而已。所以可以选择下面的方式:

1
2
3
4
5
6
7
from functools import partial。
from collections import namedtuple

Need = namedtuple('Need', ['method', 'value'])

UserNeed = partial(Need,'user')
RoleNeed = partial(Need,'role')

namedtuple 是 Python 高级数据结构中的一个内容,在 collections 包中,其实实现的效果是生成带名称的元组。比如 Need = namedtuple(‘Need’, [‘method’, ‘value’]) 实际上就是生成了一个名字叫做 Need 的元组,你可以通过 Need(‘a’, ‘b’) 的方式设置对应的 method 和 value 的值。

接下来的 partial 是和之前我另一篇文章中介绍的 wraps 在一个包中的功能,可以简单理解为方便填入一个初始化参数的方法,比如 partial(Need,’user’) 等同于 Need(‘user’, x),但是 x 的这个参数需要在调用 parial 的返回值的时候才会传入,可以用来预定义一部分的环境参数。

现在相当于我们定义了一个以用户为维度身份权限要求和以用户组为维度的身份要求。接下来就可以实现用户身份的一些功能了。

用户身份或者权限保存我们应该交由上层的业务逻辑来实现,以保证最大的模块复用性,那么用户身份就剩下了,声明用户身份和判断用户是否有权限执行某项权限操作的内容。

所以我们可以这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Identity(object):
"""
A set of needs provided by this user

example:
identity = Identity('ali')
identity.provides.add(('role', 'admin'))
"""def __init__(self, name):
self.name = name
self.provides = set()

def can(self, permission):
return permission.allows(self)

这里的 provides 是作为用户权限的声明,是一个每个权限的集合。可以在最开始登录等等时间进行加载。而 can 方法则是判断某个权限是否可以被执行。