当前位置: 首页 > 工具软件 > casbin > 使用案例 >

Python Web之casbin集成

公西岳
2023-12-01


要做一个python web系统,做简单的信息管理和案例展示,还要考虑后续功能的扩展。就想着自己搭建一个python web框架。本来想着像.net、java一样,有现成的脚手架项目,拿来改吧改吧就用了,结果发现Python web这一块还真是奇葩:

  1. 很多python web应用或脚手架的维护3~4年前就已经停止维护了
  2. 只有python web的基础框架,像flask、Django,倒是维护的挺活跃。但是flask太轻,需要自己集成;Diango太重,想要的不想要的都给你集成在一起了。

那要不我们自己造个轮子吧。本篇造轮子之-casbin访问控制集成

关于casbin是什么,你可以点击链接自己查看。总的来说,Casbin 是一个强大和高效的开放源码访问控制库,它支持各种访问控制模型以强制全面执行授权。简单来说,就是访问控制,看你有没有权限访问请求的资源。

1.Flask集成访问控制

  1. 首先有一个Casbin官方提供的基础实现库PyCasbin,实现了casbin的两大基本概念模型策略,以及模型和策略的加载和验证。功能够用,但是集成到flask的话,需要我们手动设置flask与casbin实例的关联,需要实现一些注解,方便权限配置。因此我们就找到了flask-authz。有个flask-casbin,名字看上去更像是casbin到flask的集成库,但是已经合并到flask-authz了。

  2. flask-authz如我们所愿,提供以下功能

  • 实例化casbin,根据配置加载权限模型和策略,并进行实例绑定
  • 提供访问控制注解,方便进行权限判断
    flask-authz会从flask配置中读取CASBIN_MODEL配置项,用以初始化casbin框架的模型信息。模型初始化完成,那另一大概念策略,怎么初始化呢?
# 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
    
    ...
  1. 简单的加载策略的方式,可以从csv加载。但是这种方式在系统访问权限维护更新时,使用起来不是很方便。casbin_sqlalchemy_adapter提供了一种基于数据库的策略加载和保存方式。

SQLAlchemy Adapter is the SQLAlchemy adapter for PyCasbin. With this library, Casbin can load policy from SQLAlchemy supported database or save policy to it.

  • casbin_sqlalchemy_adapter提供的数据结构是多元组的形式,比如三元组:sub,obj,act。数据加载和存储都是直接从数据库多元组加载数据。
v0v1v2v3v4v5
pbobdata2writeallow
pdata2_admindata2readallow
pdata2_admindata2writeallow
palicedata2writedeny
galicedata2_admin
  • 这种存储形式是casbin的设计理念的直接体现。但是对于与传统基于关系数据库设计的RBAC权限管理框架来说,却不能直接实现对接。不管是用户权限数据的获取,还是权限数据的更新,都需要进行转化。因此我觉得casbin_sqlalchemy_adapter并没有解决我的问题,我需要一个可以跟我的RBAC管理框架兼容的策略加载和更新方式。

2.初始化策略加载

基于RBAC设计的表结构,如何与casbin三元组权限对接。找了一圈没有找到可用的集成库,那就只有自己动手了,看上去也没有那么复杂。

  • user表
idname
1bob
2alice
  • role表
idname
1role1
2role2
  • permission表
idname
1resource1
2resource2
  • user_role表
iduser_idrole_id
111
212
  • role_permission表
idrole_idpermission_id
111
212
  • flask应用初始化时触发策略的加载
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模式中的策略信息呢?

3.权限更新

其实思路是挺简单的,就是达到信息的同步更新嘛。

  • 找到casbin策略更新接口
接口名称接口描述
add_role_for_user添加用户角色
delete_role_for_user删除用户角色
add_permission_for_user添加角色权限
delete_permission_for_user删除角色权限
  • 用户权限更新时(添加用户、更新用户权限、删除用户,添加角色、更新角色权限、删除角色),调用casbin策略更新接口
权限操作接口调用
添加用户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这一块由于项目,折腾了几个框架,接下来会陆续整理出来,看看能不能有一个系列的知识体系。

 类似资料: