casbin+python

使用casbin进行权限验证

  • casbin介绍
  • casbin在项目中的使用

casbin介绍

  • casbin的中文文档网址
    casbin的中文文档网址
    casbin的中文文档网址

casbin工作模式:
PERM模型是由4个基础(Policy,Effect,Request,Matchers)描述各个资源和用户之间的相互关系。
P–>Policy–>策略
E–>Effect–>匹配模型
R–>Request–>请求
M–>Matchers–>匹配规则

  • 这段时间一直在做权限和角色认证的管理。做的博主是真的有点头疼,里面有很多的坑,下面我会慢慢把我踩的坑讲给你听,嘿嘿嘿
  • 我用的技术结合体:Django+oauth2+casbin
  • 其中django用来做主框架,oauth2用来做三方认证,casbin用来做权限的验证

先来介绍一下casbin
你以为我会写,没意思,自己看文档去吧
我主要上代码讲解里面的使用及注意的问题

casbin在项目中的使用

梳理一下大只要做的几件事,按照顺序来的哈
第一:定义自己的user对象,也就是给请求添加user属性,就是request.user
第二:写一个中间件,过滤所有的权限和认证。这里是关键步骤。
第二步又分为:
编写自己的匹配规则,即xxx.conf文件和数据库的adapter,注意我这里使用的不是和csv配合,是数据库凹。
写自己的数据库model模型,文档里有提到,自己个去瞄吧。总结来说,就是写两个类:
存储规则的model,功效和csv一样,不过换了个形式而已。这都不懂了,面壁思过两秒。
还有Adapter类
完事!!!
下面直接上代码了

  • 我的restful_model.conf文件
[request_definition]
r = sub, obj, act, app

[policy_definition]
p = sub, obj, act, app

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = regexMatch(r.sub, p.sub) && regexMatch(r.obj, p.obj) && regexMatch(r.act, p.act) && regexMatch(r.app, p.app)

请你注意,如果你使用cv大法的话,也请你和源文档的对比一下有什么不同,不要傻不拉几的直接用,而导致一直报错,回头又怪我的代码有问题,这里只说一次,后面自己问题自己解决。

我这里request_definition和policy_definition后面的app是根据我的需求自己加的哈,你他喵的自己看着啊

  • 我这里定义的Adapter类,和响应的model类。都是写在models.py里的
from django.db import models
from casbin.persist import Adapter as _Adapter, load_policy_line

class RRECasBinRule(models.Model):
    ptype = models.CharField(max_length=255, null=True)  
    v0 = models.CharField(max_length=255, null=True)  
    v1 = models.CharField(max_length=255, null=True)  
    v2 = models.CharField(max_length=255, null=True)  
    v3 = models.CharField(max_length=255, null=True)  
    v4 = models.IntegerField()  
    v5 = models.CharField(max_length=255, null=True)

    class Meta:
        db_table = 'rre_casbin_rule'

    def __str__(self):
        return reduce(lambda x, y: str(x) + ', ' + str(y) if y else x,
                      [self.ptype, self.v0, self.v1, self.v2, self.v3, self.v4, self.v5])

    def __repr__(self):
        if not self.id:
            return "<{cls}: {desc}>".format(cls=self.__class__.__name__, desc=self)
        return "<{cls} {pk}: {desc}>".format(cls=self.__class__.__name__, pk=self.id, desc=self)

class Adapter(_Adapter):
    def __init__(self):
        self.rule = RRECasBinRule
        self.request = None

    def parse_request(self, request, *args, **kwargs):
        self.request = request

    def load_policy(self, model, **kwargs):
        if not kwargs:
            for line in self.rule.objects.all():
                load_policy_line(str(line), model)
            return

        #  正则方式: 暂时舍弃
        # v0 = "^" + kwargs.get("request").user.role + "$"
        #  关键字匹配
        v0 = kwargs.get("request").user.role

        v3 = kwargs.get("request").headers.get("App", None)

        for line in self.rule.objects.filter(v0=v0, v3=v3):
            #  正则方式
            # load_policy_line(str(line), model)
            #  关键字匹配
            s = ",".join(str(line).split(",")[:5])
            load_policy_line(s, model)
        #  正则方式舍弃
        # for line in self.rule.objects.filter(id=1):
        #     load_policy_line(str(line), model)

        #  读取用户分配的权限列表
        roles = CasbinUserRole.objects.filter(user_id=kwargs.get("request").user.user_id)
        for line in roles:
            ss = ",".join(str(line).split(",")[:5])
            load_policy_line(ss, model)

    def _save_policy_line(self, ptype, rule):
        #  正则方式
        # data = dict(zip(['ptype', 'v0', 'v1', 'v2', 'v3', 'user_id'], rule))
        #  关键字匹配
        data = dict(zip(['ptype', 'v0', 'v1', 'v2', 'v3', 'v4', 'user_id'], rule))
        try:
            res = CasbinUserRole.objects.filter(**data)
            if res:
                raise RoleExistsError('user has already had this role')
            c = CasbinUserRole.objects.create(**data)
        except RoleExistsError:
            raise
        except Exception as e:
            traceback.print_exc(e)
            return False
        return True

    def save_policy(self, model):
        """saves all policy rules to the storage."""
        for sec in ["p", "g"]:
            if sec not in model.model.keys():
                continue
            for ptype, ast in model.model[sec].items():
                for rule in ast.policy:
                    self._save_policy_line(ptype, rule)
        return True

    def add_policy(self, sec, ptype, rule):
        """adds a policy rule to the storage."""
        return self._save_policy_line(ptype, rule)

    def remove_policy(self, sec, ptype, rule):
        """removes a policy rule from the storage."""
        if sec in ["p", "g"]:
            data = dict(zip(['v0', 'v1', 'v2', 'v3', 'v4', 'user_id'], rule))
            res = CasbinUserRole.objects.filter(**data).delete()
            if res:
                return True
        return False

    def remove_filtered_policy(self, sec, ptype, field_index, *field_values):
        """removes policy rules that match the filter from the storage.
        This is part of the Auto-Save feature.
        """
        pass


adapter = Adapter()

这是关键的两个类
  • 然后就到我们的中间件了
  • 在此之前让我们先看看django是如何让生成request.user对象的
  • 附上源码
  • 没错就是中间件中的AuthenticationMiddleware搞得事情,让我们去瞄一下源码
# 把AuthenticationMiddleware导出来。注意我放置中间件的先后顺序
from django.contrib.auth.middleware import AuthenticationMiddleware
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    # 'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    # 'django.contrib.auth.middleware.AuthenticationMiddleware',
    # 'django.contrib.messages.middleware.MessageMiddleware',
    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
    "codex.middleware.middle.RequestUserMiddleware",
    "codex.middleware.middle.TokenMiddleware",
    # "codex.middleware.middle.MYLoginMiddleware",
    # "codex.middleware.middle.RoleMiddleware",
]
# 开始欣赏其表现
class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The Django authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE%s setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'django.contrib.auth.middleware.AuthenticationMiddleware'."
        ) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
        request.user = SimpleLazyObject(lambda: get_user(request))
"""
点击去看这个类,主要是实现get_user来生成request.user。再去看SimpleLazyObject类。自己去看哈,其实这个玩意没啥用,把源码copy出来自己写就行了。注意些要实现的是get_user方法啊。让我们看看get_user
"""
def get_user(request):
    if not hasattr(request, '_cached_user'):
        request._cached_user = auth.get_user(request)
    return request._cached_user
"""
再看一下里面的auth.get_user(request)方法
"""
def get_user(request):
    """
    Return the user model instance associated with the given request session.
    If no user is retrieved, return an instance of `AnonymousUser`.
    """
    from .models import AnonymousUser
    user = None
    try:
        user_id = _get_user_session_key(request)
        backend_path = request.session[BACKEND_SESSION_KEY]
    except KeyError:
        pass
    else:
        if backend_path in settings.AUTHENTICATION_BACKENDS:
            backend = load_backend(backend_path)
            user = backend.get_user(user_id)
            # Verify the session
            if hasattr(user, 'get_session_auth_hash'):
                session_hash = request.session.get(HASH_SESSION_KEY)
                session_hash_verified = session_hash and constant_time_compare(
                    session_hash,
                    user.get_session_auth_hash()
                )
                if not session_hash_verified:
                    request.session.flush()
                    user = None

    return user or AnonymousUser()
"""
看到这里return user or AnonymousUser(),你要是还不明白怎么去搞一个自己的request.user,那么神仙姐姐也救不了你了。怎么做呢,重写这个方法啊。
然后继承封装在调用,不就完事了。没事就看看源码呢,有干货在里面。
"""
  • 这就是我们的中间件了middleware.py文件
from xx.models import adapter, RRECasBinRule
from django.utils.functional import SimpleLazyObject
from kp_account.models import xx, yy, M, zz

def get_user(request, user):
    if not hasattr(request, '_cached_user'):
        request._cached_user = RequestUser(user)
    return request._cached_user


class RequestUser(BaseHandler):
    role = 'anonymous'  # 角色名
    available = False  # 逻辑删除

    def __init__(self, user):
        if user:
            self.user_init(user)

    @classmethod
    def __getattr__(cls, item):
        return cls.__dict__.get(item, None)

    @classmethod
    def user_init(cls, user):
        user_id = user.get("xx", "") #自己将xx改为自定义的字段
        user_name = user.get("xx", "")  #自己将xx改为自定义的字段
        try:
            if user_id > 0 and isinstance(user_id, int):
                user_info = xx.objects.get(user_id=user_id)
                if user_info.role_id == 3 and user_info.company_id:
                    company_info = xx.objects.get(cp_code=user_info.company_id)
                    if time.time() - cls.str2tms(
                            str(company_info.time_create.replace(microsecond=0))) > constants.TRIAL:
                        yy.objects.update({"cp_company_role": M.get_value("unauthorized")})
                        yy.objects.update({"role_id": M.get_value("unauthorized")})
                        user_info.role_id = 2
                # 读取user_id,在对应的数据库表里查询角色信息
                user_role_id = user_info.role_id
                role_name = M.get_key(user_role_id)
                cls.role = role_name if role_name else "unauthorized"
        except MacciAccountRole.DoesNotExist:
            cls.role = "unauthorized"
        except KPRole.DoesNotExist:
            cls.role = "unauthorized"
        cls.user_id = user_id  # user_id
        cls.username = user_name  # 用户名


class RequestUserMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 校验token, 其中带有user信息
        token = request.headers.get('Authorization', '') or request.COOKIES.get('Authorization')
        user_dict = cache.get(token) if token else None
        # 正常则将用户信息和权限,符合AnonymousUser形式
        request.user = SimpleLazyObject(lambda: get_user(request, user_dict))
        # request.user = get_user(request, user_dict)
        print(request.user)

#在校验权限中间件之前先封装一个request.user对象
class RequestUserMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 校验token, 其中带有user信息
        token = request.headers.get('Authorization', '') or request.COOKIES.get('Authorization')
        user_dict = cache.get(token) if token else None
        # 正常则将用户信息和权限,符合AnonymousUser形式
        request.user = SimpleLazyObject(lambda: get_user(request, user_dict)) # 这里是主要的执行函数
        # request.user = get_user(request, user_dict)
        print(request.user)

#校验权限的中间件
class TokenMiddleware(MiddlewareMixin):
    enforcer = casbin.Enforcer("auth_path/restful_model.conf", adapter)

    def process_request(self, request):
        """登陆验证"""
        if not self.check_permission(request):
            return KPResponse({'code': 2, 'data': None, 'msg': "无权限操作!"}, content_type='application/json')
        print(request.path, request.body)

    def m_load_policy(self, request):
        """reloads the policy from file/database."""

        self.enforcer.model.clear_policy()
        self.enforcer.adapter.load_policy(self.enforcer.model, request=request)

        self.enforcer.model.print_policy()
        if self.enforcer.auto_build_role_links:
            self.enforcer.build_role_links()

    def check_permission(self, request):
        # Customize it based on your authentication method.
        path = request.path
        method = request.method
        role = request.user.role
        app = request.headers.get("xx", "xx")

        #  两种不改变源类的将自己的参数传入到目的位置的方法,学着点。。。

        #  第一种
        self.m_load_policy(request)
        #  第二种  风险,有线程数据安全问题, 具体自个去看源码分析。
        # self.enforcer.adapter.parse_request(request)
        # self.enforcer.load_policy()

        print(self.enforcer.enforce(role, path, method, app), role, path, method, app)
        return self.enforcer.enforce(role, path, method, app)
  • 我踩的坑
  • 如果你要是用casbin的正则匹配,那么一定下你要明确你的需求,是不是要路由和请求方法都是有权限限制的。如果没有那么细致到每个路由及对应的方法,那么可以考虑使用正则,否则,千万别用正则,负责你会死的很难看。你的东西将会很难满足产品的新需求。最可怕的是在项目中期,你要把你做的正则全部改掉,呵呵,工作量和时间我就不说了,难受的只有自己。

如果您觉得文章对您有所帮助,可以请囊中羞涩的博主吃个鸡腿饭,万分感谢。愿每一个来到这里的人生活幸福美满。

微信赞赏
casbin+python_第1张图片

支付宝赞赏
casbin+python_第2张图片

你可能感兴趣的:(Django,Basic,python,django,开发语言)