zoukankan      html  css  js  c++  java
  • 三大认证之多方式登录,自定义权限类,自定义频率类,自定义异常类

    一、三大认证框架

    """
    1)前后台权限管理:
        后台管理:基于RBAC(auth模块的六表),用admin|xadmin来管理
        前台管理:基于三大认证权限管理,认证采用的是jwt认证,jwt一般也是依赖auth模块的六表
    
    2)认证模块:
        继承BaseAuthentication,实现authenticate方法,规则:
            没认证信息,返回None:游客
            有认证信息,认证成功,返回(user, token):合法用户
            有认证信息,认证失败,抛异常:非法用户
        
    3)权限模块:
        继承BasePermission,实现has_permission方法,规则:
            权限条件判断成立,返回True:有权限
            权限条件判断失败,返回Fasle:无权限
            
    4)jwt认证规则:
        json web token
        组成:header.payload.sign
            header:基础信息
            payload:核心信息 - 用户主键、客户端设备信息、过期时间(token过期时间,token可刷新过期时间)
            sign:安全信息 - 前两段加密结果 + 服务器安全码
        优势:
            没有数据库写:高效
            数据库不存:低耗
            只是算法:集群
        
    5)三大认证使用:
        i)是否要自定义三大认证类:
            认证类采用第三方jwt,所以自己就不写了
            权限类drf通过了一些,如果够用,直接使用,不够用会自定义
            频率类系统也提供了一些,一般也会自定义
        ii)配置:
            局部配置:在视图类中初始化类属性:permission_classes
            全局配置:在settings中自定义drf的配置:DEFAULT_AUTHENTICATION_CLASSES
    """

    二、token刷新机制(了解)

    drf-jwt直接提供刷新机制

    """
    1)运用在像12306这样极少数安全性要求高的网站
    2)第一个token由登录签发
    3)之后的所有正常逻辑,都需要发送两次请求,第一次是刷新token的请求,第二次是正常逻辑的请求
    """

    settings.py

    import datetime
    
    JWT_AUTH = {
        # 配置过期时间
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=5),
    
        # 是否可刷新
        'JWT_ALLOW_REFRESH': True,
        # 刷新过期时间
        'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    }

    urls.py

    from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken
    urlpatterns = [
        url('^login/$', ObtainJSONWebToken.as_view()),  # 登录签发token接口
        url('^refresh/$', RefreshJSONWebToken.as_view()),  # 刷新toekn接口
    ]

    postman

    # 接口:/api/refresh/
    # 方法:post
    # 数据:{"token": "登录签发的token"}

    三、认证组件自定义实现多方式登录

    urls.py

    # 自定义登录(重点):post请求 => 查操作(签发token返回给前台) - 自定义路由映射
    url('^user/login/$', views.LoginViewSet.as_view({'post': 'login'})),

    views.py

    # 重点:自定义login,完成多方式登录
    from rest_framework.viewsets import ViewSet
    from rest_framework.response import Response
    class LoginViewSet(ViewSet):
        # 登录接口,要取消所有的认证与权限规则,也就是要做局部禁用操作(空配置)
        authentication_classes = []
        permission_classes = []
    
        # 需要和mixins结合使用,继承GenericViewSet,不需要则继承ViewSet
        # 为什么继承视图集,不去继承工具视图或视图基类,因为视图集可以自定义路由映射:
        #       可以做到get映射get,get映射list,还可以做到自定义(灵活)
        def login(self, request, *args, **kwargs):
            serializer = serializers.LoginSerializer(data=request.data, context={'request': request})
            serializer.is_valid(raise_exception=True)
            token = serializer.context.get('token')
            return Response({"token": token})

    serializers.py

    from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
    
    # 重点:自定义login,完成多方式登录
    class LoginSerializer(serializers.ModelSerializer):
        # 登录请求,走的是post方法,默认post方法完成的是create入库校验,所以唯一约束的字段,会进行数据库唯一校验,导致逻辑相悖
        # 需要覆盖系统字段,自定义校验规则,就可以避免完成多余的不必要校验,如唯一字段校验
        username = serializers.CharField()
        class Meta:
            model = models.User
            # 结合前台登录布局:采用账号密码登录,或手机密码登录,布局一致,所以不管账号还是手机号,都用username字段提交的
            fields = ('username', 'password')
    
        def validate(self, attrs):
            # 在全局钩子中,才能提供提供的所需数据,整体校验得到user
            # 再就可以调用签发token算法(drf-jwt框架提供的),将user信息转换为token
            # 将token存放到context属性中,传给外键视图类使用
            user = self._get_user(attrs)
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            self.context['token'] = token
            return attrs
    
        # 多方式登录
        def _get_user(self, attrs):
            username = attrs.get('username')
            password = attrs.get('password')
            import re
            if re.match(r'^1[3-9][0-9]{9}$', username):
                # 手机登录
                user = models.User.objects.filter(mobile=username, is_active=True).first()
            elif re.match(r'^.+@.+$', username):
                # 邮箱登录
                user = models.User.objects.filter(email=username, is_active=True).first()
            else:
                # 账号登录
                user = models.User.objects.filter(username=username, is_active=True).first()
            if user and user.check_password(password):
                return user
    
            raise ValidationError({'user': 'user error'})

    四、自定义权限组件,频率组件,异常组件

    1、自定义权限组件:VIP用户才有权限

    数据准备

    """
    1)User表创建两条数据
    2)Group表创建一条数据,name叫vip
    3)操作User和Group的关系表,让1号用户属于1号vip组
    """

    permissions.py

    from rest_framework.permissions import BasePermission
    
    from django.contrib.auth.models import Group
    class IsVipUser(BasePermission):
        def has_permission(self, request, view):
            if request.user and request.user.is_authenticated:  # 必须是合法用户
                try:
                    vip_group = Group.objects.get(name='vip')
                    if vip_group in request.user.groups.all():  # 用户可能不属于任何分组
                        return True  # 必须是vip分组用户
                except:
                    pass
    
            return False

    views.py

    from .permissions import IsVipUser
    class CarViewSet(ModelViewSet):
        permission_classes = [IsVipUser]
    
        queryset = models.Car.objects.all()
        serializer_class = serializers.CarSerializer

    serializers.py

    class CarSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Car
            fields = ('name', )

    urls.py

    router.register('cars', views.CarViewSet, 'car')

    2、自定义频率组件

    重点

    """
    1)如何自定义频率类
    2)频率校验的规则
    3)自定义频率类是最常见的:短信接口一分钟只能发生一条短信
    """

    自定义频率类原理

    """
    1)自定义类继承SimpleRateThrottle
    2)设置类实现scope,值就是一个字符串,与settings中的DEFAULT_THROTTLE_RATES进行对应
        DEFAULT_THROTTLE_RATES就是设置scope绑定的类的频率规则:1/min 就代表一分钟只能访问一次
    3)重写 get_cache_key(self, request, view) 方法,指定限制条件
        不满足限制条件,返回None:代表对这类请求不进行频率限制
        满足限制条件,返回一个字符串(是动态的):代表对这类请求进行频率限制
            短信频率限制类,返回 "throttling_%(mobile)s" % {"mobile": 实际请求来的电话}
    """

    系统频率类

    #1)UserRateThrottle: 限制所有用户访问频率
    #2)AnonRateThrottle:只限制匿名用户访问频率

    频率组件的使用:请求方式频率限制

    throttles.py

    from rest_framework.throttling import SimpleRateThrottle
    # 只限制查接口的频率,不限制增删改的频率
    class MethodRateThrottle(SimpleRateThrottle):
        scope = 'method'
        def get_cache_key(self, request, view):
            # 只有对get请求进行频率限制
            if request.method.lower() not in ('get', 'head', 'option'):
                return None
    
            # 区别不同的访问用户,之间的限制是不冲突的
            if request.user.is_authenticated:
                ident = request.user.pk
            else:
                # get_ident是BaseThrottle提供的方法,会根据请求头,区别匿名用户,
                # 保证不同客户端的请求都是代表一个独立的匿名用户
                ident = self.get_ident(request)
            return self.cache_format % {'scope': self.scope, 'ident': ident}

    views.py

    # 自定义权限类
    from . permission import IsVipUser
    from . throttles import MethodRateThrottle
    class CarViewSet(ModelViewSet):
        permission_classes = [IsVipUser]
        throttle_classes = [MethodRateThrottle]
        queryset = models.Car.objects.all()
        serializer_class = serializers.CarSerializer
    
        # 直接拿的create源码
        def create(self, request, *args, **kwargs):
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=201, headers=headers)

    settings.py

    REST_FRAMEWORK = {
        #  ...
        # 频率规则配置
        'DEFAULT_THROTTLE_RATES': {
            # 只能设置 s,m,h,d,且只需要第一个字母匹配就ok,m = min = maaa 就代表分钟
            'user': '3/min',  # 配合drf提供的 UserRateThrottle 使用,限制所有用户访问频率
            'anon': '3/min',  # 配合drf提供的 AnonRateThrottle 使用,只限制匿名用户访问频率
            'method': '3/min',
        },
    }

    3、异常组件项目使用:记录异常信息到日志中

    自定义exception.py

    from rest_framework.views import exception_handler as drf_exception_handler
    from rest_framework.response import Response
    def exception_handler(exc, context):
        # 只处理客户端异常,不处理服务器异常,
        # 如果是客户端异常,response就是可以直接返回给前台的Response对象
        response = drf_exception_handler(exc, context)
    
        if response is None:
            # 没有处理的服务器异常,处理一下
            # 其实给前台返回 服务器异常 几个字就行了
            # 那我们处理异常模块的目的是 不管任何错误,都有必要进行日志记录(线上项目只能通过记录的日志查看出现过的错误)
            response = Response({'detail': '%s' % exc})
    
        # 需要结合日志模块进行日志记录的
        return response

    settings.py

    REST_FRAMEWORK = {
        # ...
        # 异常模块
        # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',  # 原来的,只处理客户端异常
        'EXCEPTION_HANDLER': 'api.exception.exception_handler',
    }
  • 相关阅读:
    nginx端口被占用解决方案
    linux安装pip报错
    小程序学习-小程序特点及适用场景
    总结行内元素与块级元素
    重装系统之无法在驱动器0的分区1上安装windows
    重装系统之win10不能进入bios界面
    重装系统之U盘设为第一启动项
    重装系统之制作U盘启动盘
    Vue-条件渲染v-if与v-show
    Cookie与Session
  • 原文地址:https://www.cnblogs.com/yafeng666/p/12363592.html
Copyright © 2011-2022 走看看