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程序会抛出异常