• 认证、权限、频率、自定义签发token-多方式登录


    三大认证流程图

    路由配置

    在应用下新建文件router.py

    # router.py
    from rest_framework.routers import Route, DynamicRoute, SimpleRouter as DRFSimpleRouter
    
    
    class SimpleRouter(DRFSimpleRouter):
        routes = [
            # List route.
            Route(
                url=r'^{prefix}{trailing_slash}$',
                mapping={
                    'get': 'list',
                    'post': 'create',
                    'put': 'multiply_update',
                    'patch': 'multiply_partial_update',
                    'delete': 'multiply_destroy'
                },
                name='{basename}-list',
                detail=False,
                initkwargs={'suffix': 'List'}
            ),
            # Dynamically generated list routes. Generated using
            # @action(detail=False) decorator on methods of the viewset.
            DynamicRoute(
                url=r'^{prefix}/{url_path}{trailing_slash}$',
                name='{basename}-{url_name}',
                detail=False,
                initkwargs={}
            ),
            # Detail route.
            Route(
                url=r'^{prefix}/{lookup}{trailing_slash}$',
                mapping={
                    'get': 'retrieve',
                    'put': 'update',
                    'patch': 'partial_update',
                    'delete': 'destroy'
                },
                name='{basename}-detail',
                detail=True,
                initkwargs={'suffix': 'Instance'}
            ),
            # Dynamically generated detail routes. Generated using
            # @action(detail=True) decorator on methods of the viewset.
            DynamicRoute(
                url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
                name='{basename}-{url_name}',
                detail=True,
                initkwargs={}
            ),
        ]
    
    
    router = SimpleRouter()
    
    

    urls.py

    # urls.py
    
    from django.conf.urls import url, include
    from .router import router
    from . import views
    router.register('users', views.UserListAPIViewSet, basename='user')
    
    urlpatterns = [
        url(r'', include(router.urls))
    ]
    
    

    认证组件

    配置drf-jwt框架的认证类 认证组件只能决定request.user,不是断定权限的地方,所以一般配置全局

    # settings.py
    
    REST_FRAMEWORK = {
        # 认证组件
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
        ],
    
    }
    

    新建文件authentications.py,自定义认证

    from rest_framework.authentication import BaseAuthentication
    
    # 自定义认证类
    # 如果使用session认证,drf默认提供了SessionAuthentication
    # 如果使用drf-jwt认证,drf-jwt默认提供了JSONWebTokenAuthentication
    # 如果自定义签发与校验token,才需要将校验token的算法封装到自定义的认证类
    
    from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    class MyAuthentication(BaseAuthentication):
        def authenticate(self, request):
            pass
            # 1、从请求头中拿到前台提交的token(一般从HTTP_AUTHORIZATION中拿,也可以与前台约定)
            #       如果设置了反爬
            # 2、没有token,返回None,代表游客
            # 3、有token,进入校验    不通过,抛异常,代表非法用户;通过,返回(user, token),代表合法用户
    

    自定义认证规则:

    • 从请求头中拿到前台提交的token(一般从HTTP_AUTHORIZATION中拿,也可以与前台约定)

      • 如果设置了反爬等措施,校验一下反爬(头 token)
    • 没有token,返回None,代表游客

    • 有token,进入校验

      • 不通过,抛异常,代表非法用户
      • 通过,返回(user, token),代表合法用户

    认证组件源码分析:

    权限组件

    权限类就是实现 BasePermission类,重写has_permission 方法,如果有权限返回True,没权限返回False。

    局部配置权限:

    # views.py
    
    # 权限组件
    permission_classes = [permissions.MyPermission]
    

    自定义权限类

    # permissions.py
    from rest_framework.permissions import BasePermission
    
    class MyPermission(BasePermission):
        def has_permission(self, request, view):
            """
            根据需求,request和view的辅助,制定权限规则判断条件
            如果条件通过,返回True
            如果条件不通过,返回False
            """
            pass
    
    

    配置drf自带的权限类

    • drf默认提供了一些权限类
      • AllowAny:游客和登录用户全权限
      • IsAuthenticated:只有登录用户有全权限
      • IsAdminUser:只有后台用户(admin)有全权限
      • IsAuthenticatedOrReadOnly:游客有读权限,登录用户有全权限
    • 如果有特殊需要,需要自定义权限类
      • 如:只有superuser有权限、只有vip用户有权限、只有某ip网段用户有权限、只有某个视图及其子类有权限
    # views.py
    from rest_framework.viewsets import GenericViewSet
    from rest_framework import mixins
    from .response import APIResponse
    from rest_framework.permissions import IsAuthenticated
    
    class UserListAPIViewSet(mixins.ListModelMixin, GenericViewSet):
        permission_classes = [IsAuthenticated]
        pass
    

    drf-jwt 签发token源码分析

    """
    drf-jwt 签发token
    1、username、password 通过auth组件的authenticate方法得到user对象
    2、user对象、通过drf-jwt框架的jwt_payload_handler函数包装 payload载荷
    3、payload载荷 通过drf-jwt框架的jwt_encode_handler函数签发 token字符串
    
    注:可以借助jwt_payload_handler和jwt_encode_handler 两个函数完成自定义token的签发
    """
    

    多方式登录 签发token

    • token只能 由在登录接口 签发

    • 登录接口也是APIView的子类,使用一定会进行 认证、权限、组件的校验

    • 结论:不管系统默认、或是全局settings配置的是何 认证与权限组件,登录接口不用参与任何认证与权限的校验。所以,登录接口一定要进行 认证与权限的局部禁用

    from rest_framework.views import APIView
    
    
    class LoginAPIView(APIView):
        authentication_classes = []
        permission_classes = []
    
        def post(self, request, *args, **kwargs):
            serializers = serializer.LoginModelSerializer(data=request.data)
            serializers.is_valid(raise_exception=True)  # 内部在全局钩子中完成token的签发
            return APIResponse(results={
                'username': '',
                'token': ''
            })
    

    这样会有个问题:post方法默认走的是数据库的增方法

    默认的校验规则里面是走的数据库,我们可以自定义字段

    # 序列化类 serializer.py
    from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
    import re
    class LoginModelSerializer(serializers.ModelSerializer):
        username = serializers.CharField(min_length=3, max_length=16)
        password = serializers.CharField(min_length=3, max_length=16)
        class Meta:
            model = models.User
            fields = ['username', 'password']
    
        # 用全局钩子,完成token的签发
        def validate(self, attrs):
            # 1、通过username、password完成多方式登录校验,得到user对象
            user = self._validate_user(attrs)
            # 2、user对象包装怕payload载荷
            payload = jwt_payload_handler(user)
            # 3、payload载荷签发token
            token = jwt_encode_handler(payload)
            # 4、将user与token存储到serializer对象中,方便在视图类中使用
            self.content = {
                'user': user,
                'token': token
            }
            return attrs
    
    
        def _validate_user(self, attrs):
            username = attrs.get('username')
            password = attrs.get('password')
    
            if re.match(r'.*@.*', username):    # 邮箱
                user = models.User.objects.filter(email=username).first()
            elif re.match(r'^1[1-9][0-9]{9}$', username):  # 电话
                user = models.User.objects.filter(mobile=username).first()
            else:  # 用户名
                user = models.User.objects.filter(username=username).first()
    
            if not user or not user.check_password(password):
                raise serializers.ValidationError({'message': '用户信息异常'})
    
            return user
    
    # views.py
    
    from rest_framework.views import APIView
    
    
    class LoginAPIView(APIView):
        authentication_classes = []
        permission_classes = []
    
        def post(self, request, *args, **kwargs):
            serializers = serializer.LoginModelSerializer(data=request.data)
            serializers.is_valid(raise_exception=True)  # 内部在全局钩子中完成token的签发
            print(serializers.content)
            return APIResponse(results={
                'username': serializers.content.get('user').username,
                'token': serializers.content.get('token')
            })
    

    总结:认证与权限绑定使用

    • 每一个视图类都要进行认证,且认证规则一致,多余全局配置认证即可
    • 每一个视图类都要进行权限校验,默认配置的是 不限制(AllowAny),但实际开发中,视图类的访问权限不完全相同,所以要在具体的视图类,配置具体的权限规则

    VIP用户认证权限例子

    # views.py
    
    from rest_framework.viewsets import ViewSet
    class UserViewSet(ViewSet):
        # 权限:只要VIP用户可以查看 个人详细信息
        permission_classes = [permissions.VIPUserPermission]
        def retrieve(self, request, *args, **kwargs):
            return APIResponse(results={
                'username': request.user.username,
                'email': request.user.username,
                'mobile': request.user.username,
                'data_joined': request.user.date_joined,
            })
    
    
    # permission.py
    from rest_framework.permissions import BasePermission
    
    # VIP用户权限
    class VIPUserPermission(BasePermission):
        def has_permission(self, request, view):
            for group in request.user.groups.all():
                if group.name.lower() == 'vip':
                    return True
            return False
    

    频率组件

    # views.py
    class UserListAPIViewSet(mixins.ListModelMixin, GenericViewSet):
        from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
        # 频率组件
        # throttle_classes = [AnonRateThrottle]
        throttle_classes = [UserRateThrottle]
        
        queryset = models.User.objects.filter(is_active=True).all()
        serializer_class = serializer.UserModelSerializer
    
        def update(self, request, *args, **kwargs):
            return APIResponse()
    
        def multiply_update(self, request, *args, **kwargs):
            return APIResponse(msg='群改')
    

    在settings.py配置文件中配置

    REST_FRAMEWORK = {
        # 频率组件,频率类一般做局部配置,但是频率调节在settings中配置
        'DEFAULT_THROTTLE_RATES': {
            'user': '5/min',   # 登录用户限制
            'anon': '3/min',   # 匿名用户限制
        },
    
    }
    

    自定义频率类

    自定义频率类:

    • drf默认提供了一些频率类
      • AnonRateThrottle:只对游客进行频率限制
      • UserRateThrottle:对所有用户进行频率限制
    • 如果由特殊需要,需要自定义频率类
      • 如:对ip进行限次、对电话进行限次、对视图某些信息进行限次

    方法步骤:

    • 设置scope字符串类属性,同时在settings中进行drf配置DEFAULT_THROTTLE_RATES
    • 重写get_catch_key方法
      • 返回与限制条件有关的字符串,表示限制
      • 返回None,表示不限制
    # throttles.py
    
    from rest_framework.throttling import SimpleRateThrottle
    
    class MobileReateThrottle(SimpleRateThrottle):
    
        scope = 'mobile'
        def get_cache_key(self, request, view):
            if not request.user.is_authenticated or not request.user.mobile:
                return None   # 匿名用户   没有电话号的用户都不限制
    
            return self.cache_format % {
                'scope': self.scope,
                'ident': request.user.mobile
            }
    
  • 相关阅读:
    linux线程
    linux线程
    c++之堆、栈、数据段、
    fork()、僵死进程和孤儿进程
    linux之管理mysql
    linux之管理apache
    Django 时间与时区设置问题
    Django rest framework:__str__ returned non-string (type NoneType) 真正原因
    Django获取当前页面的URL——小记
    Django中出现:TemplateDoesNotExist at
  • 原文地址:https://www.cnblogs.com/setcreed/p/12142649.html
Copyright © 2020-2023  润新知