rest_framework框架之認證的使用和源碼實現流程分析
一、認證功能的源碼流程
- 創建視圖函數
Note
創建視圖函數后,前端發起請求,url分配路由,執行視圖類,視圖類中執行對應方法必須經過dispatch()即調度方法
from rest_framework.views import APIView
from django.shortcuts import HttpResponse
import json
class DogView(APIView):
def get(self, request, *args, **kwargs):
result = {
'code': '10000',
'msg': '數據創建成功'
}
return HttpResponse(json.dumps(result))
def post(self, request, *args, **kwargs):
return HttpResponse('創建一條訂單')
def put(self, request, *args, **kwargs):
return HttpResponse('更新一條訂單')
def delete(self, request, *args, **kwargs):
return HttpResponse('刪除一條訂單')
- 運行dispatch方法
Note
如果自己定義了dispatch方法,則程序運行自定義方法,如果沒有,程序運行源碼中的dispatch方法。從dispatch方法中可以找到原生request在作為參數傳遞后被initialize_request()函數進行了加工,通過加工的request獲得的值包括原生的request和BaseAuthentication實例化對象,所以我們需要找到initialize_request()。
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
'''
對原生的request進行加工,獲得到的request已經不是原來的request,還包括了其他的參數,
可以通過新的request獲取到內部包含的參數
加工后的request : Restquest(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
))
'''
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# 把加工后的request當作參數傳遞給了initial()函數
# 需要把在這里查找initial()函數
# Get the appropriate handler method
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
- 查看initialize_request()函數
Note
在initialize_request()函數中返回了authenticators, 通過觀察可以看出,authenticators的值來自於另外一個函數get_authenticators()。
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request(
request,
# 原生request
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
# authenticators獲取到的是實例化后的認證類對象列表,即[Foo(), Bar()]
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
- 找到函數self.get_authenticators()
Note
這個函數中實質上是把一個認證類列表實例化為對象列表進行返回,這里就可以得出在上一個函數中的authenticators是一個實例化對象列表。需要繼續往源頭找,查找authentication_classes
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
# 例如self.authentication_classes = [foo, bar]
return [auth() for auth in self.authentication_classes]
# 列表生成式,auth獲取到的是列表中的類,auth()是把獲取到的類對象進行實例化操作
- 查找authentication_classes類
Note
在自己編寫的代碼中如果定義了認證類,則執行自定義認證類,如果沒有定義authentication_classes類,程序會從繼承的類中去查找,視圖類繼承自APIView,所以在APIView中找到類authentication_classes。
class APIView(View):
# The following policies may be set at either globally, or per-view.
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
# 繼承自APIView中的api_settings.DEFAULT_AUTHENTICATION_CLASSES類
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
Summary
從上述的邏輯可以看出最終要執行的是AUTHENTICATION_CLASSES,所有的程序中都是如果有自定義程序會覆蓋掉框架封裝好的,沒有自定義,程序才會執行封裝好的代碼。AUTHENTICATION_CLASSES類是這個邏輯中最重要的一環。
-
上邊的代碼查找到了最基本的Authentication_classes,並且得到加工后的request包含兩部分內容:原生的request、Authentication_classes實例化后得到的對象列表,此時需要繼續執行dispatch(),執行到try語句時,加工后的request作為參數傳遞給initial()函數,並執行該函數,此時需要到request.py中查找initial()函數。
self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method 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 -
查找initial()方法,在該方法中找到perform_authentication(request)方法,繼續查找perform_authentication(request)方法
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler. """ self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request) -
perform_authentication方法中調用了request.py中的Request類的user()方法
def perform_authentication(self, request): """ Perform authentication on the incoming request. Note that if you override this and simply 'pass', then authentication will instead be performed lazily, the first time either `request.user` or `request.auth` is accessed. """ request.user -
在Request類中查找到request被傳遞進行,原生的參數在調用的時候格式為:request._request, 加工后的直接是request.屬性
class Request: """ Wrapper allowing to enhance a standard `HttpRequest` instance. Kwargs: - request(HttpRequest). The original request instance. - parsers_classes(list/tuple). The parsers to use for parsing the request content. - authentication_classes(list/tuple). The authentications used to try authenticating the request's user. """ 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 # 加工后的request被作為參數傳遞,那么傳遞后相對於本類即為原生的request。 self.parsers = parsers or () self.authenticators = authenticators or () self.negotiator = negotiator or self._default_negotiator() self.parser_context = parser_context self._data = Empty self._files = Empty self._full_data = Empty self._content_type = Empty self._stream = Empty -
如果進行認證,必須通過user,此時需要查找user程序是否存在,在Request類中找到了user方法,user()方法執行了_authenticate(),查找_authenticate()
@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() # 執行_authenticate() return self._user -
查找_authenticate(),在_authenticate()方法中查找到Authenticator_classes生成的實例化列表類對象,循環的對象具有authenticate()屬性/方法,可以直接調用,並通過條件語句判斷,如果登陸返回元組,如果沒有登陸返回錯誤提示。此時基本的邏輯已經梳理完成。
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: raise self._not_authenticated() # 沒有返回值則拋出_not_authenticated()異常 if user_auth_tuple is not None: self._authenticator = authenticator self.user, self.auth = user_auth_tuple # authenticate()方法返回的元組存在,那么把元組的內容分別賦值給user, auth return self._not_authenticated() -
查找異常處理方法_not_authenticated(),當前邊的方法判斷后沒有收到元組數據,程序拋出了異常,這個異常執行_not_authenticated()方法,方法中直接調用框架自定義的api_settings.UNAUTHENTICATED_USER()類,如果存在user為AnonymousUser(匿名用戶), auth為None,如果不存在,user和auth都直接賦值為None。
def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """ self._authenticator = None if api_settings.UNAUTHENTICATED_USER: self.user = api_settings.UNAUTHENTICATED_USER() else: self.user = None if api_settings.UNAUTHENTICATED_TOKEN: self.auth = api_settings.UNAUTHENTICATED_TOKEN() else: self.auth = None
二、自定義認證類
通過上述邏輯的整體分析,我們可以編寫一個自定義的認證類供視圖函數來調用,自定義的認證類必須具有兩個方法:authenticate()和authenticate_header()方法,authenticate()必須返回一個元組,元組第一個元素為user,第二個元素為token對象
# 為測試程序臨時創建的數據
ORDER_DICT = {
1: {
'name': 'dog',
'age': 2,
'gender': 'male'
},
2: {
'name': 'cat',
'age': 3,
'gender': 'female'
}
}
# 自定義Authentication_classes
from rest_framework import exceptions
from api.models import UserToken
class MyAuthentication(object):
def authenticate(self, request, *args, **kwargs):
token = request._request.GET.get('token')
token_obj = UserToken.objects.filter(token=token).first()
if not token_obj:
raise exceptions.AuthenticationFailed("用戶尚未登陸")
return (token_obj.user, token_obj)
def authenticate_header(self, request):
pass
# 生成隨機字符串token
def md5(username):
# 以用戶post傳過來的username和時間來作為參數,隨機生成token,
# 需要注意的是在創建models是username字段必須是唯一的。
import time
import hashlib
ctime = str(time.time)
m = md5.hashlib(ytes(username, encodig='utf-8'))
# 生成的隨機字符串編碼為utf-8
m.update(bytes(ctime, encoding='utf-8'))
return m.hexdigest()
# 創建認證視圖
from rest_framework.views import APIView
from api.models import UserInfo
from django.http import JsonResponse
class AuthView(APIView):
def post(self, request, *args, **kwargs):
# 雖然是驗證信息,也是需要用戶提交過來信息的,所以這里是post方法
result = {
'code': '1000',
'msg': None
}
try:
username = request._request.GET.get('username')
password = request._request.GET.get('password')
user_obj = UserInfo.objects.filter(username=username, password=password).first()
if not user_obj:
result['code'] = '1001'
result['msg'] = "用戶不存在"
# 如果不存在返回不存在異常
token = md5(username)
# 創建函數生成token(隨機字符串)
result['token'] = token
UserToken.objects.update_or_create(user=user_obj, defaults={'token': token})
# 如何實例化對象存在,則創建或者更新token
except Exception as e:
result['code'] = '1002'
result['msg'] = '請求異常'
return JsonResponse(result)
# 創建處理request的視圖函數
class OrderView(APIView):
authentication_classes = [MyAuthentication,]
def get(self, request, *args, **kwargs):
result = {
'code': '1003',
'msg': None,
'data': None
}
try:
result['data'] = ORDER_DICT
except Exception as e:
result['code'] = '1004',
result['msg'] = '請求錯誤'
return result
Note
在上邊自定義的程序中,基本邏輯是:
- 首先是創建認證視圖類,這個類解決的是哪些用戶可以訪問和獲取到數據,認證視圖中的思路是: dispatch調度方法獲取到request后,進行加工,從加工的request中可以的到原生request通過post方法傳過來的username和password信息,通過這些信息調用數據庫查找匹配對象,如果沒有拋出異常,如果存在,需要設置一個函數生成一個專屬token
- 創建生成token函數,該函數需要用到time和hashlib兩個第三方庫,以request傳過來的username和傳入時間為參數進行設置生成
- 收到生成的token后認證視圖將token作為參數返回,同時創建或者更新實例化對象的token字段信息,在用戶再次登陸后傳過來的信息中就自動包含token
- 創建處理request的視圖類,視圖類中調用已經自定義好的authentication_classes,這個類專門用於認證信息,在該類中接收到token信息,並與數據庫中的驗證,如果驗證不一致,拋出異常,反之,則返回一個元組信息,並繼續執行視圖類。需要注意的是,authentication_classes中可以存在多個自定義的認證類,但一般用使用的都是一個。
- 驗證成功后dispatch調度方法執行對應的方法,並返回值給前端頁面。
框架內置的認證類
- BaseAuthentication
- BaseAuthentication類中是兩個方法authenticate()和authenticate_header(), 我們在自定義認證類的時候需要繼承自基類,並且對這兩個進行重寫,如果不重寫,系統自動拋出異常。
- 其他認證類:BasicAuthentication認證
一般程序中用到的是我們自定義的認證類來進行開發
自定義認證類的使用方式
-
方式一:全局使用,需要在settings.py文件中設置
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ # 'rest_framework.authentication.BasicAuthentication', # 'rest_framework.authentication.SessionAuthentication', 'api.views.Authentication' # 這里是通過路徑的方式把自定義的認證類加載到全局文件中 ] } -
方式二:局部使用,需要在視圖類中調用具體的自定義認證類
class OrderView(APIView): ''' 用於訂單相關業務 ''' authentication_classes = [Authentication,] def get(self, request, *args, **kwargs): result = { 'code': '1000', 'msg': None, 'data': None } try: result['data'] = ORDER_DICT except Exception as e: result['code': '1001'] result['msg': '訪問出錯'] return JsonResponse(result)
