源码剖析Django REST framework的认证方式及自定义认证
源碼剖析Django REST framework的認(rèn)證方式
由Django的CBV模式流程,可以知道在url匹配完成后,會執(zhí)行自定義的類中的as_view方法。
如果自定義的類中沒有定義as_view方法,根據(jù)面向?qū)ο笾蓄惖睦^承可以知道,則會執(zhí)行其父類View中的as_view方法
在Django的View的as_view方法中,又會調(diào)用dispatch方法。
現(xiàn)在來看看Django restframework的認(rèn)證流程
Django restframework是基于Django的框架,所以基于CBV的模式也會執(zhí)行自定義的類中的as_view方法
先新建一個項目,配置url
from django.conf.urls import url from django.contrib import adminfrom app01 import viewsurlpatterns = [url(r'^user/', views.UserView.as_view()), ]views.py文件內(nèi)容
from django.shortcuts import render,HttpResponse from 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")啟動項目,用瀏覽器向http://127.0.0.1:8000/user/發(fā)送get請求
可以知道請求發(fā)送成功。現(xiàn)在來看看源碼流程,由于UserView繼承APIView,查看APIView中的as_view方法
class APIView(View):... 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_evaluationview = super(APIView, cls).as_view(**initkwargs)view.cls = clsview.initkwargs = initkwargsreturn csrf_exempt(view)通過super來執(zhí)行APIView的父類Django的View中的as_view方法。上一篇文章源碼解析Django CBV的本質(zhì)中已經(jīng)知道,View類的as_view方法會調(diào)用dispatch方法。
View類的as_view方法源碼如下所示
class View(object):...@classonlymethoddef as_view(cls, **initkwargs):...def view(request, *args, **kwargs):self = cls(**initkwargs)if hasattr(self, 'get') and not hasattr(self, 'head'):self.head = self.getself.request = requestself.args = argsself.kwargs = kwargsreturn self.dispatch(request, *args, **kwargs)...as_view方法中的self實際上指的是自定義的UserView這個類,上面的代碼會執(zhí)行UserView類中dispatch方法。
由于UserView類中并沒有定義dispatch方法,而UserView類繼承自Django restframework的APIView類,所以會執(zhí)行APIView類中的dispatch方法
def dispatch(self, request, *args, **kwargs):self.args = argsself.kwargs = kwargsrequest = self.initialize_request(request, *args, **kwargs)self.request = requestself.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_allowedresponse = 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可以看到,先執(zhí)行initialize_request方法處理瀏覽器發(fā)送的request請求。
來看看initialize_request方法的源碼
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)在initialize_request方法里,把瀏覽器發(fā)送的request和restframework的處理器,認(rèn)證,選擇器等對象列表作為參數(shù)實例化Request類中得到新的request對象并返回,其中跟認(rèn)證相關(guān)的對象就是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方法通過列表生成式得到一個列表,列表中包含認(rèn)證類實例化后的對象
在這里,authentication_classes來自于api_settings的配置
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES通過查看api_settings的源碼可以知道,可以在項目的settings.py文件中進行認(rèn)證相關(guān)的配置
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通過initialize_request方法對原始的request進行一些封裝后實例化得到新的request對象
然后執(zhí)行initial方法來處理新得到的request對象,再來看看initial方法中又執(zhí)行了哪些操作
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 = negversion, scheme = self.determine_version(request, *args, **kwargs)request.version, request.versioning_scheme = version, schemeself.perform_authentication(request)self.check_permissions(request)self.check_throttles(request)由上面的源碼可以知道,在initial方法中,執(zhí)行perform_authentication來對request對象進行認(rèn)證操作
def perform_authentication(self, request):request.userperform_authentication方法中調(diào)用執(zhí)行request中的user方法,這里的request是封裝了原始request,認(rèn)證對象列表,處理器列表等之后的request對象
class Request(object):... def user(self):"""Returns the user associated with the current request, as authenticatedby the authentication classes provided to the request."""if not hasattr(self, '_user'):with wrap_attributeerrors():self._authenticate()return self._user從request中獲取_user的值,如果獲取到則執(zhí)行_authenticate方法,否則返回_user
def _authenticate(self):"""Attempt to authenticate the request using each authentication instancein turn."""for authenticator in self.authenticators:try:user_auth_tuple = authenticator.authenticate(self)except exceptions.APIException:self._not_authenticated()raiseif user_auth_tuple is not None:self._authenticator = authenticatorself.user, self.auth = user_auth_tuplereturn在這里self.authenticators實際上是get_authenticators方法執(zhí)行完成后返回的對象列表
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 = requestself.parsers = parsers or ()self.authenticators = authenticators or ()...循環(huán)認(rèn)證的對象列表,執(zhí)行每一個認(rèn)證方法的類中的authenticate方法,得到通過認(rèn)證的用戶及用戶的口令的元組,并返回元組完成認(rèn)證的流程
在_authenticate方法中使用了try/except方法來捕獲authenticate方法可能出現(xiàn)的異常
如果出現(xiàn)異常,就調(diào)用_not_authenticated方法來設(shè)置返回元組中的用戶及口令并終止程序繼續(xù)運行
總結(jié),Django restframework的認(rèn)證流程如下圖
Django restframework內(nèi)置的認(rèn)證類
在上面的項目例子中,在UsersView的get方法中,打印authentication_classes和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")打印結(jié)果為
authentication_classes: AnonymousUser由此可以知道,authentication_classes默認(rèn)是Django restframework內(nèi)置的認(rèn)證類,而request._user為AnonymousUser,因為發(fā)送GET請求,用戶沒有進行登錄認(rèn)證,所以為匿名用戶
在視圖函數(shù)中導(dǎo)入這兩個類,再查看這兩個類的源碼,可以知道
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):...從上面的源碼可以發(fā)現(xiàn),這個文件中不僅定義了SessionAuthentication和BasicAuthentication這兩個類,
相關(guān)的類還有TokenAuthentication,而且這三個認(rèn)證相關(guān)的類都是繼承自BaseAuthentication類
從上面的源碼可以大概知道,這三個繼承自BaseAuthentication的類是Django restframework內(nèi)置的認(rèn)證方式.
自定義認(rèn)證功能
在上面我們知道,Request會調(diào)用認(rèn)證相關(guān)的類及方法,APIView會設(shè)置認(rèn)證相關(guān)的類及方法
所以如果想自定義認(rèn)證功能,只需要重寫authenticate方法及authentication_classes的對象列表即可
修改上面的例子的views.py文件
from django.shortcuts import render, HttpResponse from rest_framework.views import APIView from rest_framework.authentication import BaseAuthentication from rest_framework import exceptionsTOKEN_LIST = [ # 定義token_list'aabbcc','ddeeff', ]class UserAuthView(BaseAuthentication):def authenticate(self, request):tk = request._request.GET.get("tk") # request._request為原生的requestif tk in TOKEN_LIST:return (tk, None) # 返回一個元組raise exceptions.AuthenticationFailed("用戶認(rèn)證失敗")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")啟動項目,在瀏覽器中輸入http://127.0.0.1:8000/users/?tk=aabbcc,然后回車,在服務(wù)端后臺會打印
aabbcc把瀏覽器中的url換為http://127.0.0.1:8000/users/?tk=ddeeff,后臺打印信息則變?yōu)?/p> ddeeff
這樣就實現(xiàn)REST framework的自定義認(rèn)證功能
Django restframework認(rèn)證的擴展
基于Token進行用戶認(rèn)證
修改上面的項目,在urls.py文件中添加一條路由記錄
from django.conf.urls import url from django.contrib import admin from app01 import viewsurlpatterns = [url(r'^admin/', admin.site.urls),url(r'^users/',views.UsersView.as_view()),url(r'^auth/',views.AuthView.as_view()), ]修改視圖函數(shù)
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions from django.http import JsonResponsedef gen_token(username):"""利用時間和用戶名生成用戶token:param username: :return: """import timeimport hashlibctime=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):"""獲取用戶提交的用戶名和密碼,如果用戶名和密碼正確,則生成token,并返回給用戶:param request::param args::param kwargs::return:"""res = {'code': 1000, 'msg': None}user = request.data.get("user")pwd = request.data.get("pwd")from app01 import modelsuser_obj = models.UserInfo.objects.filter(user=user, pwd=pwd).first()if user_obj:token = gen_token(user) # 生成用戶口令# 如果數(shù)據(jù)庫中存在口令則更新,如果數(shù)據(jù)庫中不存在口令則創(chuàng)建用戶口令models.Token.objects.update_or_create(user=user_obj, defaults={'token': token})print("user_token:", token)res['code'] = 1001res['token'] = tokenelse:res['msg'] = "用戶名或密碼錯誤"return JsonResponse(res)class UserAuthView(BaseAuthentication):def authenticate(self,request):tk=request.query_params.GET.get("tk") # 獲取請求頭中的用戶tokenfrom app01 import modelstoken_obj=models.Token.objects.filter(token=tk).first()if token_obj: # 用戶數(shù)據(jù)庫中已經(jīng)存在用戶口令返回認(rèn)證元組return (token_obj.user,token_obj)raise exceptions.AuthenticationFailed("認(rèn)證失敗")def authenticate_header(self,request):passclass UsersView(APIView):authentication_classes = [UserAuthView,]def get(self,request,*args,**kwargs):return HttpResponse(".....")創(chuàng)建用戶數(shù)據(jù)庫的類
from django.db import models class 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)創(chuàng)建數(shù)據(jù)庫,并添加兩條用戶記錄
再創(chuàng)建一個test_client.py文件,來發(fā)送post請求
import requestsresponse=requests.post(url="http://127.0.0.1:8000/auth/",data={'user':'user1','pwd':'user123'}, )print("response_text:",response.text)啟動Django項目,運行test_client.py文件,則項目的響應(yīng)信息為
response_text: {"code": 1001, "msg": null, "token": "eccd2d256f44cb25b58ba602fe7eb42d"}由此,就完成了自定義的基于token的用戶認(rèn)證
如果想在項目中使用自定義的認(rèn)證方式時,可以在authentication_classes繼承剛才的認(rèn)證的類即可
authentication_classes = [UserAuthView,]全局自定義認(rèn)證
在正常的項目中,一個用戶登錄成功之后,進入自己的主頁,可以看到很多內(nèi)容,比如用戶的訂單,用戶的收藏,用戶的主頁等
此時,難倒要在每個視圖類中都定義authentication_classes,然后在authentication_classes中追加自定義的認(rèn)證類嗎?
通過對Django restframework認(rèn)證的源碼分析知道,可以直接在項目的settings.py配置文件中引入自定義的認(rèn)證類,即可以對所有的url進行用戶認(rèn)證流程
在應(yīng)用app01目錄下創(chuàng)建utils包,在utils包下創(chuàng)建auth.py文件,內(nèi)容為自定義的認(rèn)證類
from rest_framework import exceptions from api import modelsclass Authtication(object):def authenticate(self,request):token = request._request.GET.get("token") # 獲取瀏覽器傳遞的tokentoken_obj = models.UserToken.objects.filter(token=token).first() # 到數(shù)據(jù)庫中進行token查詢,判斷用戶是否通過認(rèn)證if not token_obj:raise exceptions.AuthenticationFailed("用戶認(rèn)證失敗")# restframework會將元組賦值給request,以供后面使用return (token_obj.user,token_obj)# 必須創(chuàng)建authenticate_header方法,否則會拋出異常def authenticate_header(self,request):pass在settings.py文件中添加內(nèi)容
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES':['app01.utils.auth.Authtication',] }修改views.py文件
from django.shortcuts import render, HttpResponse from rest_framework.views import APIView from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions from django.http import JsonResponsedef gen_token(username):"""利用時間和用戶名生成用戶token:param username::return:"""import timeimport hashlibctime = str(time.time())hash = hashlib.md5(username.encode("utf-8"))hash.update(ctime.encode("utf-8"))return hash.hexdigest()class AuthView(APIView):authentication_classes = [] # 在這里定義authentication_classes后,用戶訪問auth頁面不需要進行認(rèn)證def post(self, request, *args, **kwargs):"""獲取用戶提交的用戶名和密碼,如果用戶名和密碼正確,則生成token,并返回給用戶:param request::param args::param kwargs::return:"""res = {'code': 1000, 'msg': None}user = request.data.get("user")pwd = request.data.get("pwd")from app01 import modelsuser_obj = models.UserInfo.objects.filter(user=user, pwd=pwd).first()if user_obj:token = gen_token(user) # 生成用戶口令# 如果數(shù)據(jù)庫中存在口令則更新,如果數(shù)據(jù)庫中不存在口令則創(chuàng)建用戶口令models.Token.objects.update_or_create(user=user_obj, defaults={'token': token})print("user_token:", token)res['code'] = 1001res['token'] = tokenelse:res['msg'] = "用戶名或密碼錯誤"return JsonResponse(res)class UserView(APIView):def get(self, request, *args, **kwargs):return HttpResponse("UserView GET")class OrderView(APIView):def get(self,request,*args,**kwargs):return HttpResponse("OrderView GET")啟動項目,使用POSTMAN向http://127.0.0.1:8000/order/?token=eccd2d256f44cb25b58ba602fe7eb42d和http://127.0.0.1:8000/user/?token=eccd2d256f44cb25b58ba602fe7eb42d發(fā)送GET請求,響應(yīng)結(jié)果如下
在url中不帶token,使用POSTMAN向http://127.0.0.1:8000/order/和http://127.0.0.1:8000/user/發(fā)送GET請求,則會出現(xiàn)"認(rèn)證失敗"的提示
由此可以知道,在settings.py配置文件中配置自定義的認(rèn)證類也可以實現(xiàn)用戶認(rèn)證功能
配置匿名用戶
修改settings.py文件
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.Authtication', ],'UNAUTHENTICATED_USER': lambda :"匿名用戶", # 用戶未登錄時顯示的名稱'UNAUTHENTICATED_TOKEN': lambda :"無效token", # 用戶未登錄時打印的token名 }修改views.py文件中的OrderView類
class OrderView(APIView):authentication_classes = [] # authentication_classes為空列表表示視圖類不進行認(rèn)證def get(self,request,*args,**kwargs):print(request.user)print(request.auth)return HttpResponse("OrderView GET")使用瀏覽器向http://127.0.0.1:8000/order/發(fā)送GET請求,后臺打印
這說明在settings.py文件中配置的匿名用戶和匿名用戶的token起到作用
建議把匿名用戶及匿名用戶的token都設(shè)置為:None
Django restframework內(nèi)置的認(rèn)證類
從rest_framework中導(dǎo)入authentication
from rest_framework import authentication可以看到Django restframework內(nèi)置的認(rèn)證類
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):...可以看到,Django restframework內(nèi)置的認(rèn)證包含下面的四種:
BasicAuthentication SessionAuthentication TokenAuthentication RemoteUserAuthentication而這四種認(rèn)證類都繼承自BaseAuthentication,在BaseAuthentication中定義了兩個方法:authenticate和authenticate_header
總結(jié):
為了讓認(rèn)證更規(guī)范,自定義的認(rèn)證類要繼承 BaseAuthentication類 自定義認(rèn)證類必須要實現(xiàn)authenticate和authenticate_header方法 authenticate_header方法的作用:在認(rèn)證失敗的時候,給瀏覽器返回的響應(yīng)頭,可以直接pass,不實現(xiàn)authenticate_header程序會拋出異常轉(zhuǎn)載于:https://www.cnblogs.com/wq-mr-almost/p/10649132.html
總結(jié)
以上是生活随笔為你收集整理的源码剖析Django REST framework的认证方式及自定义认证的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Nuget Tips
- 下一篇: 「实战篇」开源项目docker化运维部署