DRF-(10)

内容概览

  • 排序和过滤源码分析
  • 基于jwt的认证类
  • restframework-jwt执行流程分析
  • RBAC的介绍和使用
  • 后台管理simpleUI的介绍和使用

排序和过滤源码分析

"""
继承了GenericAPIView和ListModelMixin,只要在视图类中配置filter_backends就能够实现过滤与排序
1. drf内置的过滤类(SearchFilter),排序类(OrderingFilter)
2. 第三方模块django-filter
3. 自定义过滤排序类:继承BaseFilterBackend,重写filter_queryset,返回过滤或排序后的queryset对象
"""

"""只有获取所有数据才需要使用排序,所以应该查看list方法中的代码"""
# list方法
    def list(self, request, *args, **kwargs):
        # self.get_queryset()所有数据,经过了self.filter_queryset返回了queryset对象
        # self.filter_queryset完成的过滤
        queryset = self.filter_queryset(self.get_queryset())
        # 如果有分页,走的分页----》视图类中配置了分页类
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
	   # 如果没有分页,走正常的序列化,返回
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)
    
    
# self.filter_queryset完成了过滤,当前在视图类中,self是视图类的对象,去视图类中找没找到,去父类---》GenericAPIView---》filter_queryset
    
        def filter_queryset(self, queryset):
            for backend in list(self.filter_backends):  # 循环获取我们配置的过滤类
                queryset = backend().filter_queryset(self.request, queryset, self)  # 执行过滤类中的filter._queryset方法过滤数据
            return queryset

"""
总结:
	1. 写过滤类要重写filter_queryset方法,返回过滤或排序后的queryset对象
	2. 如果不想写过滤类,因为调用的是父类GenericAPIView中的filter_queryset方法,所以可以直接在视图类中重写filter_queryset方法,在方法中过滤或排序后返回queryset对象即可
"""

基于jwt的认证类

class JWTAuthentication(BaseAuthentication):
    def authenticate(self, request):
        jwt_value = request.META.get('HTTP_TOKEN')  # 自定义token发送到后端的方式
        # 验证token是否合法,jwt模块下一定有个验证token的函数
        try:
            payload = jwt_decode_handler(jwt_value)
        except jwt.ExpiredSignature:
            raise AuthenticationFailed('token过期了')
        except jwt.DecodeError:
            raise AuthenticationFailed('token解码失败')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('认证失败')
        # 执行到这,说明token合法,payload可以使用
        user = UserInfo.objects.filter(pk=user_id).first()  # 每次都要查数据库,效率不太好
        return (user, jwt_value)
        # return (payload,jwt_value)  # 可以返回paylod,包含用户姓名与id,可以在需要的时候从request.user.get()获取,不用每次查数据库

restframework-jwt执行流程分析

"""签发的token,有过期时间,过期时间是?配置一般设为7天"""
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
}

"""双token认证"""
	-用户正在app或者应用中操作 token突然过期,此时用户不得不返回登陆界面,重新进行一次登录,这种体验性不好,于是引入双token校验机制
    -实现原理:首次登陆时服务端返回两个token ,accessToken和refreshToken,accessToken过期时间比较短,refreshToken时间较长,且每次使用后会刷新,每次刷新后的refreshToken都是不同

   
    
    -refreshToken假设7天,accessToken过期时间5分钟
    -正常使用accessToken即可,如果accessToken过期了,重新发请求,携带refreshToken过来,能正常返回,并且这次响应中又带了acessToken


    
# django中顶格写的代码,都会执行
    
"""签发流程--》本质就是登录接口---》【校验用户是否正确,如果正确签发token】写到了序列化类中,如果不正确返回错误"""
	-obtain_jwt_token:核心代码--ObtainJSONWebToken.as_view()
    -ObtainJSONWebToken 视图类,实现了登录功能
    class ObtainJSONWebToken(JSONWebTokenAPIView):
    	serializer_class = JSONWebTokenSerializer


    -父类ObtainJSONWebToken
    class JSONWebTokenAPIView(APIView):
        # 局部禁用掉权限和认证
        permission_classes = () 
        authentication_classes = ()

        def get_serializer_context(self):
            return {
                'request': self.request,
                'view': self,
            }

        def get_serializer_class(self):
            return self.serializer_class

        def get_serializer(self, *args, **kwargs):
            serializer_class = self.get_serializer_class()
            kwargs['context'] = self.get_serializer_context()
            return serializer_class(*args, **kwargs)

        def post(self, request, *args, **kwargs):
            # JSONWebTokenSerializer实例化得到一个序列号类的对象,传入前端传的只
            serializer = self.get_serializer(data=request.data)

            if serializer.is_valid(): # 校验前端传入的数据是否合法:
                #1 字段自己的规则 2 局部钩子 3 全局钩子(序列化类的validate方法)
                # 获取当前登录用户和签发token是在序列化类中完成的
                # 从序列化类对象中取出了当前登录用户
                user = serializer.object.get('user') or request.user
                # # 从序列化类对象中取出了token
                token = serializer.object.get('token')
                # 自定义过
                response_data = jwt_response_payload_handler(token, user, request)
                response = Response(response_data)
                return response

            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

      -序列化类:JSONWebTokenSerializer
    	class JSONWebTokenSerializer(Serializer):
            def validate(self, attrs):
                credentials = {
                    'username': attrs.get('username'),
                    'password': attrs.get('password')
                }

                if all(credentials.values()):
                    # auth的校验用户名和密码是否正确
                    user = authenticate(**credentials)

                    if user:
                        # 通过用户获得payload:{}
                        payload = jwt_payload_handler(user)
                        return {
                            'token': jwt_encode_handler(payload),
                            'user': user
                        }
                    else:
                        # 根据用户名和密码查不到用户
                        raise serializers.ValidationError(msg)
                else:	
                    # 用户名和密码不传,传多了都不行
                    raise serializers.ValidationError(msg)
                    

# 认证
	-认证类;JSONWebTokenAuthentication
    class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
        def get_jwt_value(self, request):
            # get_authorization_header(request)根据请求头中HTTP_AUTHORIZATION,取出token
            # jwt adsfasdfasdfad
            # auth=['jwt','真正的token']
            auth = get_authorization_header(request).split()
            auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
            if not auth:
                if api_settings.JWT_AUTH_COOKIE:
                    return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
                return None
            if smart_text(auth[0].lower()) != auth_header_prefix:
                return None
            if len(auth) == 1:
                msg = _('Invalid Authorization header. No credentials provided.')
                raise exceptions.AuthenticationFailed(msg)
            elif len(auth) > 2:
                msg = _('Invalid Authorization header. Credentials string '
                        'should not contain spaces.')
                raise exceptions.AuthenticationFailed(msg)
            return auth[1]
        
   -父类中:BaseJSONWebTokenAuthentication---》authenticate
class BaseJSONWebTokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # jwt_value前端传入的token
        jwt_value = self.get_jwt_value(request)
        # 前端没有传入token,return None,没有带token,认证类也能过,所有咱们才加权限类
        if jwt_value is None:
            return None
        try:
            payload = jwt_decode_handler(jwt_value) # 验证token,token合法,返回payload
        except jwt.ExpiredSignature:
            msg = _('Signature has expired.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = _('Error decoding signature.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()

        user = self.authenticate_credentials(payload) # 通过payload得到当前登录用户

        return (user, jwt_value) # 后期的request.user就是当前登录用户
    
    
 # 它这个认证类:只要带了token,request.user就有只,如果没带token,不管了,继续往后走

RBAC的介绍和使用

  • RBAC(Role-Based Access Control )是基于角色的访问控制
    在 RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。
    这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便
    RBAC权限管理的模式,适用于公司内部的管理系统,不适合用于对外互联网的系统
    用户:用户表
    角色(部门):角色(部门)表
    权限:权限表
    所有权限都记录在权限表中,通过多对多的方式给各个角色分配权限
    用户表中的用户,通过多对多的方式关联角色表,得到各自角色所拥有的权限
    所以一共有5张表:用户表,角色表,权限表,用户与角色关联表,角色与权限关联表

  • django的后台管理admin自带了RBAC的权限管理,通过auth模块实现,不过比普通RBAC多了一个功能
    django额外在用户与权限之间开设了多对多的关系表
    在用户需要一些权限,通过角色获取的权限又太多了的情况下,就可以单独给用户开设权限
    启用了admin与auth迁移表时,这6张表就迁移进去了
    auth_user:用户表
    auth_group:角色,组,部门表
    auth_permission:权限表
    auth_user_groups:用户和角色中间表
    auth_group_permissions:角色跟权限中间表
    auth_user_user_permissions:用户和权限的中间表

ACL、RBAC、ABAC(PBAC/CBAC)权限控制的介绍
"""ACL(Access Control List,访问控制列表)"""
将用户或组直接与对象的权限对接:用户表,权限表,中间表;

"""RBAC(Role-Based Access Control,基于角色的访问控制)"""
将用户与角色对接,然后角色与对象的权限对接将用户与角色对接,然后角色与对象的权限对接

"""RBAC+ACL,django使用,公司用的较多"""

"""
ABAC(Attribute-Based Access Control,基于属性的访问控制)
又称为PBAC(Policy-Based Access Control,基于策略的访问控制)
CBAC(Claims-Based Access Control,基于声明的访问控制)
"""

传统的ACL、RBAC的架构是
{subject,action,object},
而ABAC的架构是
{subject,action,object,contextual}且为他们添加了parameter(参数)。

subject属性:比如用户的年龄、部门、角色、威望、积分等主题属性。

action属性:比如查看、读取、编辑、删除等行为属性。

object属性:比如银行账户、文章、评论等对象或资源属性。

contextual属性:比如时段、IP位置、天气等环境属性。
casbin:方便快速的做权限控制
import casbin

e = casbin.Enforcer("./model.conf", "./policy.csv")

sub = "lqz"  # 想要访问资源的用户
obj = "book"  # 将要被访问的资源
act = "get"  # 用户对资源进行的操作


# 自己写acl的控制
# 当前用户id,去权限和用户表查询即可,有记录就是有权限

# 自己写rbac
# 当前用户id,找到他的角色,根据角色拿出权限,判断当前访问有没有


if e.enforce(sub, obj, act):
    # 允许alice读取data1
    print('有权限')
    return True
else:
    # 拒绝请求,抛出异常
    print('没有权限')
    return False

model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

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

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

policy.csv

p,alice,data1,read
p,bob,data2,write
p,lqz,book,get

后台管理simpleUI的介绍与使用

Simple UI文档

"""
django admin自带了权限控制,但是是前后端混合,我们可以二次开发,开发出公司内部的自动化运行,自动化测试等;但是默认界面的样式不太好看
对django admin进行美化
xadmin(不用了,过时了)
simpleui(正红)
"""

# 1. 下载第三方模块
pip install django-simpleui

# 2. 注册app(放在第一位)
INSTALLED_APPS = [
    'simpleui',
    'django.contrib.admin',
    'django.contrib.auth',
	...
]

"""配置完成后后台管理样式就已经改变了"""
自定义菜单

settings.py

import time
SIMPLEUI_CONFIG = {
    'system_keep': False,  # 该字段用于告诉simpleui,是否需要保留系统默认的菜单,默认为False,不保留。 如果改为True,自定义和系统菜单将会并存
    'menu_display': ['我的首页','图书管理','权限认证', '多级菜单测试', '动态菜单测试'],      # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示.
    'dynamic': True,    # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
    'menus': [{
        'name': '我的首页',
        'icon': 'fas fa-code',
        'url': '/index/',
        # 浏览器新标签中打开
        'newTab': True,
    },
    {
            'app': 'app01',
            'name': '图书管理',
            'icon': 'fas fa-code',
            'models': [
                {
                    'name': '用户',
                    'icon': 'fa fa-user',
                    'url': 'app01/userinfo/'
                },
                {
                    'name': '图书',
                    'icon': 'fa fa-user',
                    'url': 'app01/book/'
                },
                {
                    'name': '出版社',
                    'icon': 'fa fa-user',
                    'url': 'app01/publish/'
                },
            ]
        },
     {
        'app': 'auth',
        'name': '权限认证',
        'icon': 'fas fa-user-shield',
        'models': [{
            'name': '用户',
            'icon': 'fa fa-user',
            'url': 'auth/user/'
        }]
    }, {
        # 自2021.02.01+ 支持多级菜单,models 为子菜单名
        'name': '多级菜单测试',
        'icon': 'fa fa-file',
      	# 二级菜单
        'models': [{
            'name': 'Baidu',
            'icon': 'far fa-surprise',
            # 第三级菜单 ,
            'models': [
                {
                  'name': '爱奇艺',
                  'url': 'https://www.iqiyi.com/dianshiju/'
                  # 第四级就不支持了,element只支持了3级
                }, {
                    'name': '百度问答',
                    'icon': 'far fa-surprise',
                    'url': 'https://zhidao.baidu.com/'
                }
            ]
        }]
    }, {
        'name': '动态菜单测试' ,
        'icon': 'fa fa-desktop',
        'models': [{
            'name': time.time(),
            'url': 'http://baidu.com',
            'icon': 'far fa-surprise'
        }]
    }]
}


"""配置"""
SIMPLEUI_LOGIN_PARTICLES = False  # 关闭登录页面粒子动画
SIMPLEUI_HOME_INFO = False  # 是否显示服务器信息
自定义按钮

admin.py

@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'price')  # 表中的字段

    # 增加自定义按钮
    actions = ['make_copy', ]
    def make_copy(self, request, queryset):
        print('adsfasdf')  # 点击按钮触发的事件
    make_copy.short_description = '自定义按钮'  # 按钮显示的文本
    # # icon,参考element-ui icon与https://fontawesome.com
    make_copy.icon = 'fas fa-audio-description'
    # # 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
    make_copy.type = 'danger'
    make_copy.confirm = '你是否执意要点击这个按钮?'
simpleui集成监控大屏
# 从gitee上找到开源的前端页面---》集成到项目中即可https://gitee.com/lvyeyou/DaShuJuZhiDaPingZhanShi?_from=gitee_search
# 本质就是前后端混合的项目
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值