Source code Anatomy of the Django REST Framework authentication method and custom authentication

Source: Internet
Author: User
Tags md5
Source code Anatomy of the Django REST Framework authentication method

By the Django CBV mode flow, you can know the url匹配完成后,会执行自定义的类中的as_view方法 .

If there is no definition in the custom class as_view方法 , depending on the inheritance of classes in the object-oriented class, you can会执行其父类View中的as_view方法

在Django的View的as_view方法中,又会调用dispatch方法

Now look at the Django Restframework certification process

Django Restframework is a Django-based framework, so the CBV-based pattern also executes As_view methods in the custom class

Create a new project first, configure the URL

from django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [    url(r'^user/', views.UserView.as_view()),]

views.py File Contents

from django.shortcuts import render,HttpResponsefrom rest_framework.views import APIViewclass UserView(APIView):    def get(self,request,*args,**kwargs):        print(request.__dict__)        print(request.user)        return HttpResponse("UserView GET")    def post(self,request,*args,**kwargs):        return HttpResponse("UserView POST")

Start the project and send a http://127.0.0.1:8000/user/ GET request in the browser

You can know that the request was sent successfully. Now look at the source flow, because Userview inherit Apiview, view the As_view method in Apiview

class APIView(View):    ...    @classmethod    def as_view(cls, **initkwargs):        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):            def force_evaluation():                raise RuntimeError(                    'Do not evaluate the `.queryset` attribute directly, '                    'as the result will be cached and reused between requests. '                    'Use `.all()` or call `.get_queryset()` instead.'                )            cls.queryset._fetch_all = force_evaluation        view = super(APIView, cls).as_view(**initkwargs)        view.cls = cls        view.initkwargs = initkwargs        return csrf_exempt(view)

通过super来执行APIView的父类Django的View中的as_view方法。 The source code of the previous article to parse the nature of Django CBV already know that the As_view method of the view class calls the Dispatch method.

View class As_view method source code is as follows

class View(object):    ...    @classonlymethod    def as_view(cls, **initkwargs):        ...        def view(request, *args, **kwargs):            self = cls(**initkwargs)            if hasattr(self, 'get') and not hasattr(self, 'head'):                self.head = self.get            self.request = request            self.args = args            self.kwargs = kwargs            return self.dispatch(request, *args, **kwargs)        ...

as_view方法中的self实际上指的是自定义的UserView这个类, the code above executes the dispatch method in the Userview class.

Because the dispatch method is not defined in the Userview class, and the Userview class inherits from the Apiview class of the Django Restframework, the Apiview method in the dispatch class is executed

def dispatch(self, request, *args, **kwargs):    self.args = args    self.kwargs = kwargs    request = self.initialize_request(request, *args, **kwargs)    self.request = request    self.headers = self.default_response_headers  # deprecate?    try:        self.initial(request, *args, **kwargs)        if request.method.lower() in self.http_method_names:            handler = getattr(self, request.method.lower(),                              self.http_method_not_allowed)        else:            handler = self.http_method_not_allowed        response = handler(request, *args, **kwargs)    except Exception as exc:        response = self.handle_exception(exc)    self.response = self.finalize_response(request, response, *args, **kwargs)    return self.response

can see that 先执行initialize_request方法处理浏览器发送的request请求 .

Take a look at the source code of Initialize_request method

def initialize_request(self, request, *args, **kwargs):    """    Returns the initial request object.    """    parser_context = self.get_parser_context(request)    return Request(        request,        parsers=self.get_parsers(),        authenticators=self.get_authenticators(),        negotiator=self.get_content_negotiator(),        parser_context=parser_context    )

In the Initialize_request method, the browser sends the request and the Restframework processor, authentication, selector and other object list as parameters to the request class to get a new request object and return, Which is related to the certification of the object is authenticators.

def get_authenticators(self):    """    Instantiates and returns the list of authenticators that this view can use.    """    return [auth() for auth in self.authentication_classes]

get_authenticators方法通过列表生成式得到一个列表,列表中包含认证类实例化后的对象

Over hereauthentication_classes来自于api_settings的配置

authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

You can know by looking at the source code of the api_settings, you can do the authentication related configuration in the project's settings.py file.

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)def reload_api_settings(*args, **kwargs):    setting = kwargs['setting']    if setting == 'REST_FRAMEWORK':        api_settings.reload()

Django restframework A new Request object by instantiating some encapsulation of the original request through the Initialize_request method

Then execute the initial method to handle the new Request object, and then take a look at what has been done in the initial method

def initial(self, request, *args, **kwargs):    self.format_kwarg = self.get_format_suffix(**kwargs)    neg = self.perform_content_negotiation(request)    request.accepted_renderer, request.accepted_media_type = neg    version, scheme = self.determine_version(request, *args, **kwargs)    request.version, request.versioning_scheme = version, scheme    self.perform_authentication(request)    self.check_permissions(request)    self.check_throttles(request)

By the above source code can know, in the initial method,执行perform_authentication来对request对象进行认证操作

def perform_authentication(self, request):    request.user

perform_authentication方法中调用执行request中的user方法这里的request是封装了原始request,认证对象列表,处理器列表等之后的request对象

class Request(object):    ...    @property    def user(self):        """        Returns the user associated with the current request, as authenticated        by the authentication classes provided to the request.        """        if not hasattr(self, '_user'):            with wrap_attributeerrors():                self._authenticate()        return self._user

Gets _user the value from request, if it gets to the execution _authenticate方法 , otherwise returns_user

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            self.user, self.auth = user_auth_tuple            return

Here self.authenticators is actually the get_authenticators list of objects returned after the completion of the method execution

class Request(object):    def __init__(self, request, parsers=None, authenticators=None,                 negotiator=None, parser_context=None):        assert isinstance(request, HttpRequest), (            'The `request` argument must be an instance of '            '`django.http.HttpRequest`, not `{}.{}`.'            .format(request.__class__.__module__, request.__class__.__name__)        )        self._request = request        self.parsers = parsers or ()        self.authenticators = authenticators or ()        ...

A list of cyclic authentication objects, a 执行每一个认证方法的类中的authenticate方法 tuple that gets authenticated by the user and the user's password, and returns the tuple to complete the authentication process

The _authenticate Try/except method was used in the method to capture the possible exceptions of the Authenticate method

If an exception occurs, call _not_authenticated the method to set the user and password in the returned tuple and terminate the program to continue running

As a summary, the Django restframework certification process

Django Restframework built-in authentication class

In the above project example, in the Get method of Usersview, the value of printing authentication_classes and request._user

class UserView(APIView):    # authentication_classes = [MyAuthentication,]    def get(self,request,*args,**kwargs):        print('authentication_classes:', self.authentication_classes)        print(request._user)        return HttpResponse("UserView GET")

Prints the result as

authentication_classes: [<class 'rest_framework.authentication.SessionAuthentication'>, <class 'rest_framework.authentication.BasicAuthentication'>]AnonymousUser

It can be known that the authentication_classes default is the Django Restframework built-in authentication class, and Request._user is Anonymoususer, because send GET request, the user does not log on authentication, so for anonymous users

Import these two classes in the View function, and then look at the source code of these two classes, you can know

class BasicAuthentication(BaseAuthentication):    www_authenticate_realm = 'api'     def authenticate(self, request):        ...    def authenticate_credentials(self, userid, password):        ...class SessionAuthentication(BaseAuthentication):    def authenticate(self, request):        ...    def enforce_csrf(self, request):        ...        class TokenAuthentication(BaseAuthentication):    ...

From the above source can be found that this file is not only defined SessionAuthentication and BasicAuthentication these two classes,

Related classes are also TokenAuthentication , and these three authentication-related classes are inherited from the BaseAuthentication class

From the above source can probably know, these three inherited from BaseAuthentication the class is the Django Restframework built-in authentication method.

Custom Authentication Features

As we know above, request invokes authentication-related classes and methods, APIView sets authentication-related classes and methods

So if you want to customize the authentication function, you only need to rewrite authenticate the method and authentication_classes the list of objects

Modify the views.py file for the example above

from django.shortcuts import render, HttpResponsefrom rest_framework.views import APIViewfrom rest_framework.authentication import BaseAuthenticationfrom rest_framework import exceptionsTOKEN_LIST = [  # 定义token_list    'aabbcc',    'ddeeff',]class UserAuthView(BaseAuthentication):    def authenticate(self, request):        tk = request._request.GET.get("tk")  # request._request为原生的request        if tk in TOKEN_LIST:            return (tk, None)  # 返回一个元组        raise exceptions.AuthenticationFailed("用户认证失败")    def authenticate_header(self, request):        # 如果不定义authenticate_header方法会抛出异常        passclass UserView(APIView):    authentication_classes = [UserAuthView, ]    def get(self, request, *args, **kwargs):        print(request.user)        return HttpResponse("UserView GET")

Start the project, enter it in the browser, http://127.0.0.1:8000/users/?tk=aabbcc and then enter and print in the backend of the server

aabbcc

The URL in the browser is changed to http://127.0.0.1:8000/users/?tk=ddeeff , and the background print information becomes

ddeeff

This enables the custom authentication feature of the rest framework

Django restframework certified extensions based on token for user authentication

Modify the above item to add a route record in the urls.py file

from django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [    url(r'^admin/', admin.site.urls),    url(r'^users/',views.UsersView.as_view()),    url(r'^auth/',views.AuthView.as_view()),]

modifying view functions

From django.shortcuts import render,httpresponsefrom rest_framework.views import Apiviewfrom rest_ Framework.authentication Import baseauthenticationfrom rest_framework import exceptionsfrom django.http Import    Jsonresponsedef Gen_token (username): "" "Use time and username to generate user Token:p Aram Username:: return:" "" " Import Hashlib Ctime=str (Time.time ()) Hash=hashlib.md5 (Username.encode ("Utf-8")) Hash.update (Ctime.encode ("utf- 8 ")) return Hash.hexdigest () class Authview (Apiview): Def post (self, request, *args, **kwargs):" "" gets used        User name and password, if the user name and password are correct, the token is generated and returned to the user:p Aram request::p Aram args::p Aram Kwargs:: Return: "" "res = {' Code ': $, ' msg ': None} user = Request.data.get (" user ") pwd = Request.data.get (" pwd ") from APP01 import models User_obj = models.      UserInfo.objects.filter (User=user, Pwd=pwd). First () If User_obj:token = Gen_token (user) # Generate user Password      # If a password exists in the database is updated, the user password models is created if no password exists in the database.            Token.objects.update_or_create (user=user_obj, defaults={' token ': token}) Print ("User_token:", token) res[' Code ' = 1001 res[' token '] = token else:res[' msg '] = "username or password error" return JSONRESP Onse (RES) class Userauthview (baseauthentication): Def authenticate (self,request): Tk=request.query_params. Get.get ("TK") # Gets the user token from the request header from APP01 import models token_obj=models. Token.objects.filter (TOKEN=TK). First () If token_obj: # user password already exists in user database returns authentication tuple return (Token_obj.user,tok En_obj) Raise exceptions. Authenticationfailed ("Authentication failed") def Authenticate_header (self,request): Passclass Usersview (apiview): Authenticat Ion_classes = [Userauthview,] def get (Self,request,*args,**kwargs): Return HttpResponse ("...")

To create a class for a user database

from django.db import modelsclass UserInfo(models.Model):    user=models.CharField(max_length=32)    pwd=models.CharField(max_length=64)    email=models.CharField(max_length=64)class Token(models.Model):    user=models.OneToOneField(UserInfo)    token=models.CharField(max_length=64)

Create a database and add two user records

Then create a test_client.py file to send the POST request

import requestsresponse=requests.post(    url="http://127.0.0.1:8000/auth/",    data={'user':'user1','pwd':'user123'},)print("response_text:",response.text)

Start the Django project and run the test_client.py file, the project's response information is

response_text: {"code": 1001, "msg": null, "token": "eccd2d256f44cb25b58ba602fe7eb42d"}

This completes the custom token-based user authentication

If you want to use a custom authentication method in your project, you can authentication_classes inherit the class that you just authenticated

authentication_classes = [UserAuthView,]
Global Custom Authentication

In the normal project, a user login success, access to their home page, you can see a lot of content, such as the user's order, the user's collection, the user's homepage, etc.

At this point, do you want to define authentication_classes in each view class, and then append the custom authentication class to the authentication_classes?

Through the source analysis of the Django Restframework authentication, you can introduce a custom authentication class directly in the project's settings.py configuration file, that is, the user authentication process can be done for all URLs.

Create a Utils package under the app App01 directory, create a auth.py file under the Utils package, and customize the authentication class with the content

from rest_framework import exceptionsfrom api import modelsclass Authtication(object):    def authenticate(self,request):        token = request._request.GET.get("token")       # 获取浏览器传递的token        token_obj = models.UserToken.objects.filter(token=token).first()    # 到数据库中进行token查询,判断用户是否通过认证        if not token_obj:            raise exceptions.AuthenticationFailed("用户认证失败")        # restframework会将元组赋值给request,以供后面使用        return (token_obj.user,token_obj)        # 必须创建authenticate_header方法,否则会抛出异常    def authenticate_header(self,request):        pass

Adding content to the settings.py file

REST_FRAMEWORK = {    'DEFAULT_AUTHENTICATION_CLASSES':['app01.utils.auth.Authtication',]}

Modify the views.py file

From django.shortcuts import Render, Httpresponsefrom rest_framework.views import Apiviewfrom rest_ Framework.authentication Import baseauthenticationfrom rest_framework import exceptionsfrom django.http Import    Jsonresponsedef Gen_token (username): "" "Use time and username to generate user Token:p Aram Username:: return:" "" " Import hashlib CTime = str (time.time ()) hash = HASHLIB.MD5 (Username.encode ("Utf-8")) Hash.update (Ctime.encode ("UT F-8 ")) return Hash.hexdigest () class Authview (Apiview): Authentication_classes = [] # defined here authentication_classes After the user accesses the Auth page does not need to authenticate the Def post (self, request, *args, **kwargs): "" "gets the user name and password submitted by the user, if the user name and password are correct, generate token, and return to the User:p Aram Request::p Aram args::p Aram Kwargs:: Return: "" "res = {' Code ': 1000,        ' msg ': None} user = Request.data.get ("user") pwd = Request.data.get ("pwd") from APP01 import models User_obj = models. UserInfo.objects.filter (User=user, PWD=PWD). First () If User_obj:token = Gen_token (user) # Generate password # If a password exists in the database is updated, if the password does not exist in the database Create user password models.            Token.objects.update_or_create (user=user_obj, defaults={' token ': token}) Print ("User_token:", token) res[' Code ' = 1001 res[' token '] = token else:res[' msg '] = "username or password error" return JSONRESP Onse (RES) class Userview (Apiview): Def get (self, request, *args, **kwargs): Return HttpResponse ("Userview get") cl Orderview (Apiview): Def get (Self,request,*args,**kwargs): Return HttpResponse ("Orderview get")

Start the project, use postman http://127.0.0.1:8000/order/?token=eccd2d256f44cb25b58ba602fe7eb42d to http://127.0.0.1:8000/user/?token=eccd2d256f44cb25b58ba602fe7eb42d send a GET request, and respond with the following result

The prompt that appears when you use Postman to http://127.0.0.1:8000/order/ send a http://127.0.0.1:8000/user/ GET request without token in the URL "认证失败"

As a result, it is possible to configure a custom authentication class in the settings.py configuration file to implement user authentication functions.

Configure anonymous users

Modify the settings.py file

REST_FRAMEWORK = {    'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.Authtication', ],    'UNAUTHENTICATED_USER': lambda :"匿名用户",     # 用户未登录时显示的名称    'UNAUTHENTICATED_TOKEN': lambda :"无效token", # 用户未登录时打印的token名}

Modify the Orderview class in the views.py file

class OrderView(APIView):    authentication_classes = []         # authentication_classes为空列表表示视图类不进行认证    def get(self,request,*args,**kwargs):        print(request.user)        print(request.auth)        return HttpResponse("OrderView GET")

Use the browser to http://127.0.0.1:8000/order/ send a GET request to print spool

This indicates that the tokens of anonymous users and anonymous users configured in the settings.py file play a role

It is recommended to set both anonymous and anonymous users ' tokens to: None

Django Restframework built-in authentication class

Import authentication from Rest_framework

from rest_framework import authentication

You can see the built-in authentication class for Django Restframework

class BaseAuthentication(object):    def authenticate(self, request):        ...    def authenticate_header(self, request):        passclass BasicAuthentication(BaseAuthentication):    def authenticate(self, request):        ...    def authenticate_credentials(self, userid, password, request=None):        ...    def authenticate_header(self, request):        ...class SessionAuthentication(BaseAuthentication):    def authenticate(self, request):        ...    def enforce_csrf(self, request):        ...class TokenAuthentication(BaseAuthentication):    def authenticate(self, request):        ...    def authenticate_credentials(self, key):        ...    def authenticate_header(self, request):        ...class RemoteUserAuthentication(BaseAuthentication):    def authenticate(self, request):        ...

As you can see, the built-in certifications for Django Restframework include the following four types:

BasicAuthenticationSessionAuthenticationTokenAuthenticationRemoteUserAuthentication

And these four kinds of authentication classes are inherited from BaseAuthentication ,在BaseAuthentication中定义了两个方法:authenticate和authenticate_header

Summarize:

为了让认证更规范,自定义的认证类要继承 BaseAuthentication类自定义认证类必须要实现authenticate和authenticate_header方法authenticate_header方法的作用:在认证失败的时候,给浏览器返回的响应头,可以直接pass,不实现authenticate_header程序会抛出异常

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.