Casbin 是一个强大的,高效的开源访问控制框架,其权限管理机制支持多种访问控制模型,各个编程语言都对 casbin 有支持
目前我的所有项目,都是基于简单的角色管理来的
分为了三类,未登录能访问的,普通用户能访问的,管理员能访问的
后续做 rbac 系统,那就会把角色和访问控制结合起来,casbin 就可以更好的帮助我去做这个事情
网上关于 casbin 的文档其实还挺多的,但是他们的案例很多都不太适合,新手第一次看很容易看懵
这个文档就结合实际的 web 中的访问控制来进行讲解
基本使用
下载
pip install casbin
casbin 有两个部分组成,一个是配置文件,一个是规则集
初次学习,先把 demo 跑通,先不用搞懂这两个东西是什么
准备两个文件,model.conf 和 policy.csv 文件
model.conf
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
[policy_effect]
e = some(where (p.eft == allow))
policy.csv
p, zhangsan, /index, GET
p, zhangsan, /home, GET
p, zhangsan, /users, GET
p, zhangsan, /users, POST
p, wangwu, /index, GET
p, wangwu, /home, GET
demo
import casbin
import logging
def check(e, sub, obj, act):
ok = e.enforce(sub, obj, act)
if ok:
print(f"{sub} CAN {act} {obj}")
else:
print(f"{sub} CANNOT {act} {obj}")
def main():
# 初始化日志配置
logging.basicConfig(level=logging.INFO)
try:
# 创建Casbin enforcer
e = casbin.Enforcer("./model.conf", "./policy.csv")
except Exception as err:
logging.fatal(f"NewEnforcer failed: {err}")
return
# 执行权限检查
check(e, "zhangsan", "/index", "GET")
check(e, "zhangsan", "/home", "GET")
check(e, "zhangsan", "/users", "POST")
check(e, "wangwu", "/users", "POST")
if __name__ == "__main__":
main()
正常情况下,你会得到
zhangsan CAN GET /index
zhangsan CAN GET /home
zhangsan CAN POST /users
wangwu CANNOT POST /users
Casbin 配置文件
最开始看到 model.conf 和 policy.csv 文件,估计还是一脸懵的
那让我们来看看 casbin 的配置文件
Request
代表请求。看我们上面的例子:
[request_definition]
r = sub, obj, act
代表一个请求有三个标准元素,请求主体,请求对象,请求操作
以用户 fengfeng 用 GET 请求 /api/users 接口为例:
请求主体就是 fengfeng
请求对象就是 /api/users
请求操作就是 GET
Policy 和 Policy_Rule
Policy 代表策略,它表示具体的权限定义的规则是什么
在 policy.csv 文件中定义的策略就是 policy_rule。它和 Policy 是一一对应的。
例如我们上面的例子:
[policy_definition]
p = sub, obj, act
我们定义了 policy 的规则如此,那么我们在 policy.csv 中每 ⼀⾏ 定义的 policy_rule 就必须和这个属性 ⼀⼀ 对应
Matcher
有请求,有规则,那么请求是否匹配某个规则,则是 matcher 进行判断的,例如:
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
表示当
r.sub == p.sub && r.obj == p.obj && r.act == p.act
的时候返回 true,否则返回 false,也就是三者完全匹配的时候,返回 true
Effect
Effect ⽤ 来判断如果 ⼀ 个请求满 ⾜ 了规则,是否需要同意请求。它的规则 ⽐ 较复杂 ⼀ 些。
比如我们这里:
[policy_effect]
e = some(where (p.eft == allow))
这里的 some 表示括号中的表达式个数大于等于 1 就行。 我们这句话的意思就是将 request 和所有 policy 比对完之后,所有 policy 的策略结果(p.eft)为 allow 的个数 >= 1,整个请求的策略就是 true
访问控制模型
除了文档中提到的,casbin 还支持多种访问控制模型
ACL 访问控制
ACL 是最早也是最基本的一种访问控制机制,它的原理非常简单:
每一项资源,都配有一个列表,这个列表记录的就是哪些用户可以对这项资源执行 CRUD 中的那些操作。当系统试图访问这项资源时,会首先检查这个列表中是否有关于当前用户的访问权限,从而确定当前用户可否执行相应的操作。总得来说,ACL 是一种面向资源的访问控制模型,它的机制是围绕 资源 展开的。
上面的 demo 就是 acl 访问控制的例子
增加规则
e.AddPolicy("wangwu", "/users", "POST")
e.SavePolicy()
删除规则
e.RemovePolicy("wangwu", "/users", "POST")
e.SavePolicy()
RBAC 访问控制
ACL 模型在用户和资源都比较少的情况下没什么问题,但是用户和资源量一大,ACL 就会变得异常繁琐
想象一下,每次新增一个用户,都要把他需要的权限重新设置一遍是多么地痛苦
RBAC(role-based-access-control)模型通过引入角色(role)这个中间层来解决这个问题
每个用户都属于一个角色,例如开发者、管理员、运维等,每个角色都有其特定的权限,权限的增加和删除都通过角色来进行
这样新增一个用户时,我们只需要给他指派一个角色,他就能拥有该角色的所有权限
修改角色的权限时,属于这个角色的用户权限就会相应的修改
mode.conf文件
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
policy.csv
p, admin, /index, GET
p, admin, /home, GET
p, admin, /users, GET
p, admin, /users, POST
p, yunwei, /index, GET
p, yunwei, /home, GET
g, zhangsan, admin
g, wangwu, yunwei
判断的代码还是一样的
check(e, "zhangsan", "/index", "GET")
check(e, "zhangsan", "/home", "GET")
check(e, "zhangsan", "/users", "POST")
check(e, "wangwu", "/users", "POST")
但是有了角色之后,系统新增一个用户,只需要给这个用户分配角色
然后可以在角色管理中新增一个角色,然后设置角色的访问权限,可以访问哪些接口
在这个模式下,一个用户是可以有多个角色的,例如
p, admin, /index, GET
p, admin, /home, GET
p, admin, /users, GET
p, admin, /users, POST
p, yunwei, /index, GET
p, yunwei, /home, GET
g, zhangsan, admin
g, wangwu, yunwei
g, wangwu, admin
增加角色
e.AddPolicy("kuaiji", "/users", "GET")
e.SavePolicy()
增加用户-角色对应关系
e.AddRoleForUser("zhangsan", "kuaiji")
e.SavePolicy()
删除用户-角色对应关系
e.RemoveGroupingPolicy("zhangsan", "kuaiji")
e.SavePolicy()
删除角色
e.RemovePolicy("kuaiji", "/users", "GET")
e.SavePolicy()
结合数据库使用
其实基于上面的rbac,就可以做一个角色访问控制功能了
但是目前整个规则集是存储在文件中的
实际应用中肯定还是会把casbin的规则集放到数据库里面
import casbin
from casbin_sqlalchemy_adapter import Adapter
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import logging
from datetime import datetime
# 初始化日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
class RBACManager:
def __init__(self):
self.engine = None
self.session = None
self.enforcer = None
self.adapter = None
self.init_db()
self.init_casbin()
def init_db(self):
"""初始化数据库连接"""
try:
# 创建数据库
self.engine = create_engine(
"mysql+pymysql://root:root@127.0.0.1:3306/rule_db?charset=utf8mb4",
pool_pre_ping=True,
pool_size=10,
pool_recycle=3600
)
Session = sessionmaker(bind=self.engine)
self.session = Session()
logger.info("数据库连接成功")
except Exception as e:
logger.fatal(f"数据库连接失败: {str(e)}")
raise
def init_casbin(self):
"""初始化Casbin权限系统"""
try:
# 创建适配器并自动建表
self.adapter = Adapter(self.engine)
self.adapter.create_table() # 自动创建casbin_rule表
# 加载模型文件
model = casbin.Model.from_file("./testdata/model.pml")
# 创建带缓存的Enforcer
self.enforcer = casbin.CachedEnforcer(model, self.adapter)
self.enforcer.set_expire_time(3600) # 1小时缓存
self.enforcer.load_policy()
logger.info("Casbin初始化完成")
except Exception as e:
logger.fatal(f"Casbin初始化失败: {str(e)}")
raise
def check_permission(self, sub, obj, act):
"""权限检查方法"""
ok = self.enforcer.enforce(sub, obj, act)
status = "允许" if ok else "拒绝"
logger.info(f"用户 {sub} 请求 {act} {obj} - {status}")
return ok
def add_permission(self, role, resource, action):
"""添加权限策略"""
try:
self.enforcer.add_policy(role, resource, action)
self.enforcer.save_policy()
logger.info(f"添加策略: {role} 可 {action} {resource}")
except Exception as e:
logger.error(f"添加策略失败: {str(e)}")
def add_role(self, user, role):
"""为用户分配角色"""
try:
self.enforcer.add_role_for_user(user, role)
self.enforcer.save_policy()
logger.info(f"为用户 {user} 分配角色 {role}")
except Exception as e:
logger.error(f"分配角色失败: {str(e)}")
def remove_role(self, user, role):
"""移除用户角色"""
try:
self.enforcer.delete_role_for_user(user, role)
self.enforcer.save_policy()
logger.info(f"移除用户 {user} 的角色 {role}")
except Exception as e:
logger.error(f"移除角色失败: {str(e)}")
def remove_permission(self, role, resource, action):
"""移除权限策略"""
try:
self.enforcer.delete_policy(role, resource, action)
self.enforcer.save_policy()
logger.info(f"移除策略: {role} 不可 {action} {resource}")
except Exception as e:
logger.error(f"移除策略失败: {str(e)}")
if __name__ == "__main__":
try:
rbac = RBACManager()
# 测试用例
rbac.add_permission("admin", "/api/users", "GET")
rbac.add_role("zhangsan", "admin")
# 权限检查
rbac.check_permission("zhangsan", "/api/users", "GET") # 应允许
# 移除权限
rbac.remove_role("zhangsan", "admin")
rbac.remove_permission("admin", "/api/users", "GET")
# 再次检查
rbac.check_permission("zhangsan", "/api/users", "GET") # 应拒绝
# 持久化保存(实际已自动保存)
rbac.enforcer.save_policy()
except Exception as e:
logger.error(f"程序运行出错: {str(e)}")
3172

被折叠的 条评论
为什么被折叠?



