DjangoRestFramework Learn the three components of authentication, permission, frequency, url registrar, responder, paging

A Certification Component

1. Local Authentication Components

We know that we go to the dispatch method to distribute regardless of the route or the corresponding view class.

In the source code of the dispatch method in the APIView class we see, there is a self.initial(request, *args, **kwargs), so the three default components of authentication, permission, frequency are all in this method. If we do not configure these three components ourselves, some default configurations in the source code will be used.Go into the source code and you will see the following three things:

# Ensure that the incoming request is permitted
#Implement authentication
self.perform_authentication(request)
#Judgment of privileges
self.check_permissions(request)
#Control access frequency
elf.check_throttles(request)

The authentication mechanism you know so far is cookie s, sessions, sessions are safer, but you will find that session information is stored on our server. If there are a lot of users, server pressure is greater, and Django sessions are stored in the django_session table, which is not very good to operate, but the general scene is notWhat's wrong? There are many ways to use a mechanism called token in production. Now we know if we have a csrf_token. In fact, there are many ways to write token. How to encrypt it? Are you hashlib, base64 or hmac, add expiration time, add a secret_key (a string negotiated between client and server)As the authentication basis for both sides), whether to refresh continuously (short effective time, keep updating token, if someone else takes token in such a short time, and simulates the user status, then this is basically impossible, but you can add security in the network or network devices, store the ip address of customers, anti-hacker) and so on.

Overview flowchart illustration:

      

    

First, we need to create a table, a user table, with a token field in it. In fact, I usually put in two tables, and the user table is a one-to-one relationship table, see the code:

################################# user surface ###############################
class User(models.Model):
    user = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    type_choice=((1,"VIP"),(2,"SVIP"),(3,"SSVIP"))
    user_type = models.IntegerField(choices=type_choice)

class UserToken(models.Model):
    user = models.OneToOneField(to=User) #One-to-one user table
    token = models.CharField(max_length=128) #Set a longer length
    # expire_time = models.DateTimeField() #If you have a time-out limit, you can add a field here. I'm not writing high here. It's simple.

urls.py is as follows:

#Logon Authentication Interface
url(r'^login/$', views.LoginView.as_view(),), #Don't forget the end of the $symbol

views.py reads as follows: Write your own token value to refresh after each successful login

###################login Logical Interface#######################
#Instead of using ModelViewSet for logical interfaces, we write a class directly, inherit APIView, and write our logic directly inside the class.
import uuid
import os
import json
class LoginView(APIView):
    #For items separated from front-end and back-end, get requests do not need to be written, because get is a landing page operation and vue is done, so we can write post requests directly here
    def post(self,request):
        # In general, the response from our backend after the request comes in is a dictionary that contains not only error information but also status codes to let the client know what is happening.
        # The value of'code', 1 for success, 0 for failure, and 2 for other errors (you can do more detailed error codes yourself)
        res = {'code': 1, 'msg': None, 'user': None,'token':None}
        print(request.data)
        try:
            user = request.data.get('user')
            pwd = request.data.get('pwd')
            # Query in database
            user_obj = models.User.objects.filter(user=user, pwd=pwd).first()
            if user_obj:
                res['user'] = user_obj.user
                # Add token, use our usertoken table
                # models.UserToken.objects.create(user=user,token='123456')
                # To create a token random string, I've written two ways. Short form Ang, better to encrypt it again
                random_str = uuid.uuid4()
                # Random_str = os.urandom(16) 16-bit random string of bytes type
                models.UserToken.objects.update_or_create(
                    user=user_obj,  # Find filter criteria
                    defaults={  # Data added or updated
                        "token": random_str,
                    }
                )
                res['token'] = random_str
                res['msg'] = 'Landing Success'
            else:
                res['code'] = 0
                res['msg'] = 'User name or password error'
                return Response(res)
        except Exception as e:
            res['code'] = 2
            res['msg'] = str(e)

        return Response(res)

* We return the token to the user through the code above, so in the future, no matter what request they send, the user will access it with the token value that I give it, authenticate the token to pass, and update the token.

Let's play with the authentication components provided by drf.

Authentication components for DRF

In the future, some data interfaces will have to require users to log on before they can get data, so after the future users log on, they will bring token with them for each request they make.

from app01.serializer import BookSerializers

#####################Book Table Operation##########################

class UserAuth():
   def authenticate_header(self,request):
         pass
#The authenticate method is fixed and must have a parameter, which is a new request object, don't trust it, look at the source code
    def authenticate(self,request):

        if 1:
        #The source code will find that this method has two return values, which are encapsulated in a new request object, request.user-->user name and request.auth-->token, which are used as the return values after authentication ends.
            return "chao","asdfasdfasdf"

class BookView(APIView):
    #Certification component must have been executed before get, post, etc. Remember where the source code is, this component was called at dispatch, we wrote a UserAuth class above
    authentication_classes = [UserAuth,] #Authentication classes can write multiple, one-by-one sequential validation
    def get(self,request):
        '''
        //View all books
        :param request:
        :return:
        '''
        #This gives you two return values for the authenticate method of the UserAuth class above
        print(request.user)  
        print(request.auth)
        book_obj_list = models.Book.objects.all()
        s_books = BookSerializers(book_obj_list,many=True)
        return Response(s_books.data)
    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self) 
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                #Notably, self is a new request object encapsulated by APIView
                self.user, self.auth = user_auth_tuple 
                return   #If you exit this function, the function will not execute and will not loop again, so if your first authentication class has a return value, the second authentication class will not execute, so don't forget that return is the end function, so if you have more than one authentication class, the return value will be placed in the last class

OK, let's write down the function to get the token value, then check it out, and see the code for views.py:

from django.shortcuts import render,HttpResponse,redirect
from django.views import View
from rest_framework import serializers
from app01 import models
from rest_framework.views import APIView
from rest_framework.response import Response
#Put the serialization components in a separate file and bring them in
from app01.serializer import BookSerializers,PublishSerializers,AuthorSerializers
#Exception for authentication failure provided by drf
from rest_framework.exceptions import AuthenticationFailed

class UserAuth():
    #Each authentication class requires an authenticate_header method with a parameter request
    def authenticate_header(self,request):
        pass
    #The authenticate method is fixed and must have a parameter, which is a new request object, don't trust it, look at the source code
    def authenticate(self,request):
        # token = request._request.GET.get("token")
        #Since our request is a new request object and the old request object is encapsulated in the new request object with the name self._request, there is no problem with this, but APIView not only encapsulates the old request object, but also adds the query_params attribute to it, which is the same as what the old request.GET gets, so it canWrite directly as follows
        token = request.query_params.get("token")
        #When the user requests it, we get the token value and verify it in the database
        usertoken = models.UserToken.objects.filter(token=token).first()
        if usertoken:
            #After successful validation, you can either return two values or nothing
            return usertoken.user.user,usertoken.token

        #The source code will find that this method has two return values, which are encapsulated in a new request object, request.user-->user name and request.auth-->token, which are used as the return values after authentication ends.
        else:
            #Because the source code has an exception trap inside it and gives you a forbiden error on your own initiative, we can proactively throw an exception here.
            raise AuthenticationFailed("Authentication Failure")

urls.py is as follows:

url(r'^books/$', views.BookView.as_view(),),

* Send requests through postman and you will find errors:

      

If we request a token value stored in the database, we will be able to successfully retrieve the data and see the token value in the database:

      

Then ask again through postman, with the token value, to see the effect and succeed:

      

    

) Writing of the BaseAuthentication certification class that inherits drf:

from app01 import models
from rest_framework.views import APIView
from rest_framework.response import Response
#Put the serialization components in a separate file and bring them in
from app01.serializer import BookSerializers,PublishSerializers,AuthorSerializers
#Exception for authentication failure provided by drf
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
#BaseAuthentication Class Inheriting drf
class UserAuth(BaseAuthentication):
    # After inheriting the BaseAuthentication class, this method does not have to be written
    # def authenticate_header(self,request):
    #     pass
    def authenticate(self,request):
        # token = request._request.GET.get("token")
        token = request.query_params.get("token")
        #With a request object, you can authenticate not only token, but also other content inside the request
        usertoken = models.UserToken.objects.filter(token=token).first()
        if usertoken:
            #After successful validation
            return usertoken.user.user,usertoken.token
        else:
            raise AuthenticationFailed("Authentication Failure")

class BookView(APIView):
    #Looking at the source code, the process of looking up the authentication class is the same as that of parsing the components
    authentication_classes = [UserAuth,]
    def get(self,request):
        '''
        //View all books
        :param request:
        :return:
        '''
        print(request.user)
        print(request.auth)
        book_obj_list = models.Book.objects.all()
        s_books = BookSerializers(book_obj_list,many=True)
        return Response(s_books.data)

* Time-stamped random strings:

def get_random_str(user):
    import hashlib,time
    ctime=str(time.time())

    md5=hashlib.md5(bytes(user,encoding="utf8"))
    md5.update(bytes(ctime,encoding="utf8"))

    return md5.hexdigest()

Global View Authentication Component:

Configure in the settings.py file: If I write our own authentication class under the auth folder in the service folder under the app01 folder, then the global configuration will be written as follows.

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",]  #Inside is the path string
}

Authentication components say that. Look at the permission components.

Two-privilege component

Local view permissions:

In app01.service.permissions.py

from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
    message="SVIP Can access!" #Variables can only be called message s
    def has_permission(self, request, view):  #Rewrite the has_permission method, write the permission logic yourself, and look at the source code to see that this view is an instantiated object of our current class and is generally not needed, but you must write a parameter here.
        if request.user.user_type==3:
            return True  #Through permissions
        return False     #Failed

In views.py:

from app01.service.permissions import *

class BookViewSet(generics.ListCreateAPIView):
    permission_classes = [SVIPPermission,]
    queryset = Book.objects.all()
    serializer_class = BookSerializers

Global view permissions:

settings.py is configured as follows:

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",]
}

Three Frequency Component

Local view throttle, anti-crawl, anti-attack

In throttles.py:

from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
import time
from rest_framework import exceptions
visit_record = {}
class VisitThrottle(BaseThrottle):
    # Limit access time
    VISIT_TIME = 10
    VISIT_COUNT = 3

    # Definition method method name and parameters cannot be changed
    def allow_request(self, request, view):
        # Get the id of the Login Host
        id = request.META.get('REMOTE_ADDR')
        self.now = time.time()

        if id not in visit_record:
            visit_record[id] = []

        self.history = visit_record[id]
        # Limit access time
        while self.history and self.now - self.history[-1] > self.VISIT_TIME:
            self.history.pop()
        # Only access records for the last 10 seconds are saved in history at this time
        if len(self.history) >= self.VISIT_COUNT:
            return False
        else:
            self.history.insert(0, self.now)
            return True

    def wait(self):
        return self.history[-1] + self.VISIT_TIME - self.now

In views.py:

from app01.service.throttles import *

class BookViewSet(generics.ListCreateAPIView):
    throttle_classes = [VisitThrottle,]
    queryset = Book.objects.all()
    serializer_class = BookSerializers

Global view throttle

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
    "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",]
}

Built-in throttle class

In throttles.py, change to:

class VisitThrottle(SimpleRateThrottle):

    scope="visit_rate"
    def get_cache_key(self, request, view):

        return self.get_ident(request)

settings.py settings:

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
    "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
    "DEFAULT_THROTTLE_RATES":{
        "visit_rate":"5/m",
    }
}

Four url registrar

Help us automatically generate four url s, similar to what we wrote ourselves:

from django.conf.urls import url,include
from django.contrib import admin
from app01 import views
from rest_framework import routers

router = routers.DefaultRouter()
#Automatically generate four URLs for us
router.register(r'authors', views.AuthorView)
router.register(r'books', views.BookView)


urlpatterns = [

    # url(r'^books/$', views.BookView.as_view(),), #Don't forget the end of the $symbol
    # url(r'api/', include(router.urls)),
    url(r'', include(router.urls)),   #http://127.0.0.1:8000/books/can also be written as follows: http://1270.0..1:8000/books.json/
   
    #Logon Authentication Interface
    url(r'^login/$', views.LoginView.as_view(),), #Don't forget the end of the $symbol

]

But one premise is that we are using the ModelViewSet serialization component.

from rest_framework.viewsets import ModelViewSet
class AuthorView(ModelViewSet):
    queryset = models.Author.objects.all() 
    serializer_class = AuthorSerializers

class BookView(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializers

So this url registrar isn't really that useful, of course, depending on your needs.

Five responders

Just look at it:

from rest_framework.viewsets import ModelViewSet
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
#If we do not configure these two JSONRenderers, Browsable APIRenderers, which are used by default in the source code
#Browsable APIRenderer is a browser, when the client is browser, the reply data will automatically generate a page form of data display for you, generally developed without the page form
#JSONRenderer: The reply is json data

class BookView(ModelViewSet):
    # renderer_classes = [JSONRenderer,] #The default is this JSONRenderer, so you don't normally configure it here
    queryset = models.Book.objects.all()
    serializer_class = BookSerializers

Six Pager Component

Simple use:

#Introduce Paging
from rest_framework.pagination import PageNumberPagination
class BookView(APIView):
    # Looking at the source code, the process of looking up the authentication class is the same as that of parsing the components
    # authentication_classes = [UserAuth,]
    # throttle_classes = [VisitThrottle,]

    def get(self,request):
        '''
        //View all books
        :param request:
        :return:
        '''
        
        book_obj_list = models.Book.objects.all()
        #To create a pager object, there is a page attribute in the PageNumberPagination class in addition to the PAGE_SIZE attribute, which is the first page in the PageNumberPagination class and is used at http://127.0.0.1:8000/books/?page=1
        pnb = PageNumberPagination()
        #Paging is done by the paginate_queryset method of the pager object.
        paged_book_list = pnb.paginate_queryset(book_obj_list,request,)
        #Serializing Paged Data
        s_books = BookSerializers(paged_book_list,many=True)
        return Response(s_books.data)

settings configuration file:

REST_FRAMEWORK={

    # "DEFAULT_THROTTLE_RATES":{
    #     "visit_rate":"5/m",
    # },
    'PAGE_SIZE':5, #This is a global configuration showing how many items per page, but it is not generally used because different data displays may show different amounts per page
}

If we don't want to configure with a global page_size, we can write our own class to inherit the paging class components and override the properties inside:

#Introduce Paging
from rest_framework.pagination import PageNumberPagination
class MyPagination(PageNumberPagination):
    page_size = 3 #Number of data bars per page
    page_query_param = 'pp'  #http://127.0.0.1:8000/books/?pp=1, which page of data to query
    page_size_query_param = 'size' #If one page of data we show isn't enough for you, you want to show some data temporarily, you can set this up by you page_size_query_param Access as a parameter: http://127.0.0.1:8000/books/? Pp=2&size=5 #Then you see the second page, but you see five pieces of data, meaning that the number of page_size has been temporarily expanded, and each page shows more or less data. Look at the value set by your page_size_query_param
    max_page_size = 10 #Maximum number of pages per page, even if your front-end temporarily adjusts the value of page_size through page_size_query_param, the maximum cannot exceed the value we set for max_page_size

class BookView(APIView):
    def get(self,request):
        '''
        //View all books
        :param request:
        :return:
        '''
        book_obj_list = models.Book.objects.all()
        pnb = MyPagination()
        paged_book_list = pnb.paginate_queryset(book_obj_list,request,)
        s_books = BookSerializers(paged_book_list,many=True)
        return Response(s_books.data)

And what we're playing with is the attempt class that inherits the ModelViewSet class to use the paging method:

from rest_framework.viewsets import ModelViewSet

from rest_framework.pagination import PageNumberPagination
class MyPagination(PageNumberPagination):
    page_size = 3
    page_query_param = 'pp'
    page_size_query_param = 'size'
    max_page_size = 10

class BookView(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializers
    pagination_class = MyPagination #Configure our own paging classes

There is also an offset paging, just look at it

from rest_framework.pagination import LimitOffsetPagination

Keywords: Python Django Database JSON Attribute

Added by mjl4975 on Mon, 16 Dec 2019 03:43:19 +0200