那要不我们自己造个轮子吧。本篇造轮子之-casbin访问控制集成。
关于casbin是什么,你可以点击链接自己查看。总的来说,Casbin 是一个强大和高效的开放源码访问控制库,它支持各种访问控制模型以强制全面执行授权。简单来说,就是访问控制,看你有没有权限访问请求的资源。
首先有一个Casbin官方提供的基础实现库PyCasbin,实现了casbin的两大基本概念模型和策略,以及模型和策略的加载和验证。功能够用,但是集成到flask的话,需要我们手动设置flask与casbin实例的关联,需要实现一些注解,方便权限配置。因此我们就找到了flask-authz。有个flask-casbin,名字看上去更像是casbin到flask的集成库,但是已经合并到flask-authz了。
flask-authz如我们所愿,提供以下功能
# casbin config
CASBIN_MODEL = os.path.join(basedir, 'casbinmodel.conf')
def create_app(config_name):
app = Flask(__name__)
app.config["CASBIN_MODEL"] = CASBIN_MODEL
register_extensions(app)
# init casbin
casbin_enforcer = CasbinEnforcer(app)
app.casbin_enforcer = casbin_enforcer
...
SQLAlchemy Adapter is the SQLAlchemy adapter for PyCasbin. With this library, Casbin can load policy from SQLAlchemy supported database or save policy to it.
v0 | v1 | v2 | v3 | v4 | v5 |
---|---|---|---|---|---|
p | bob | data2 | write | allow | |
p | data2_admin | data2 | read | allow | |
p | data2_admin | data2 | write | allow | |
p | alice | data2 | write | deny | |
g | alice | data2_admin |
基于RBAC设计的表结构,如何与casbin三元组权限对接。找了一圈没有找到可用的集成库,那就只有自己动手了,看上去也没有那么复杂。
id | name |
---|---|
1 | bob |
2 | alice |
id | name |
---|---|
1 | role1 |
2 | role2 |
id | name |
---|---|
1 | resource1 |
2 | resource2 |
id | user_id | role_id |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
id | role_id | permission_id |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
def create_app(config_name):
...
# load policy
with app.app_context(): # 手动创建app_context,用于数据查询获取casbin初始化policy
AuthService.load_all_role_policy()
AuthService.load_all_user_policy()
...
这里的实现有个缺陷,就是手动创建了一个app_context。这样看上去不太优雅,其实完全可以像flask扩展初始化的形式,构建init_**方法,然后在方法参数中传入app实例即可。大概像这样
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config_by_name[config_name])
...
# load policy
casbin_policy.init_policy(app)
...
当然要是这样,角色权限、用户权限初始加载查询方法的app也不再访问current_app。这里暂未修改,有兴趣的可以考虑一下。
from flask import current_app
from ..model import User, Role
class AuthService:
# 加载所有用户权限casbin
@classmethod
def load_all_user_policy(cls):
users = User.query.filter_by(status=1).all()
for user in users:
if user.roles is not None and len(user.roles) > 0:
cls.load_user_policy(user)
current_app.logger.info("user_roles:{0}".format(current_app.casbin_enforcer.e.get_grouping_policy()))
# 加载单个用户权限到casbin
@staticmethod
def load_user_policy(user):
current_app.casbin_enforcer.e.delete_roles_for_user(user.employee_no)
for role in user.roles:
current_app.casbin_enforcer.e.add_role_for_user(user.employee_no, role.name)
current_app.logger.info("update user_roles:{0}".format(current_app.casbin_enforcer.e.get_grouping_policy()))
# 加载所有角色权限到casbin
@classmethod
def load_all_role_policy(cls):
roles = Role.query.filter_by(status=1).all()
for role in roles:
cls.load_role_policy(role)
current_app.logger.info("role_permissions:{0}".format(current_app.casbin_enforcer.e.get_policy()))
# 加载单个角色权限到casbin
@staticmethod
def load_role_policy(role):
current_app.casbin_enforcer.e.delete_role(role.name)
for permission in role.permissions:
current_app.casbin_enforcer.e.add_permission_for_user(role.name, permission.path, permission.method)
current_app.logger.info("update role_permissions:{0}".format(current_app.casbin_enforcer.e.get_policy()))
这样就实现了,应用初始化时,从关系数据库用户权限相关记录中,加载casbin策略信息,实现对RBAC记录的无缝对接。做为一个功能完备的web系统,用户权限更新是再正常不过的事情,那用户权限更新时,如何同步更新casbin模式中的策略信息呢?
其实思路是挺简单的,就是达到信息的同步更新嘛。
接口名称 | 接口描述 |
---|---|
add_role_for_user | 添加用户角色 |
delete_role_for_user | 删除用户角色 |
add_permission_for_user | 添加角色权限 |
delete_permission_for_user | 删除角色权限 |
权限操作 | 接口调用 |
---|---|
添加用户 | add_role_for_user |
更新用户角色 | delete_role_for_user&add_role_for_user |
删除用户 | delete_role_for_user |
添加角色 | add_permission_for_user |
更新角色权限 | delete_permission_for_user&add_permission_for_user |
删除角色 | delete_permission_for_user |
来个删除用户的示例吧
from flask import current_app
from .base_model import BaseModel
class User(BaseModel):
def remove(self):
self.query.filter_by(id=self.id).update({'status': self.status_remove})
ret = db.session.commit()
if ret:
current_app.casbin_enforcer.e.delete_roles_for_user(self.id)
return ret
Python Web这一块由于项目,折腾了几个框架,接下来会陆续整理出来,看看能不能有一个系列的知识体系。