REST 用戶認證源碼
在Django中,從URL調度器中過來的HTTPRequest會傳遞給disatch(),使用REST后也一樣
# REST的dispatch
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 = argsself.kwargs = kwargsrequest = self.initialize_request(request, *args, **kwargs)self.request = requestself.headers = self.default_response_headers # deprecate?try:self.initial(request, *args, **kwargs)# Get the appropriate handler methodif 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
代碼第三行通過一個方法initialize_request()
重新分裝了原來從URL調度器傳來的request對象,并且返回的也是一個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()
返回的是一個Request對象
class Request(object):def __init__(self, request, parsers=None, authenticators=None,negotiator=None, parser_context=None):passself._request = requestself.parsers = parsers or ()# ...
Request這個類使用"組合"將普通的httprequest分裝在它的內部,除此之外還提供了用于身份驗證的authenticators,用于解析請求內容的解析器(parsers)只關心authenticators
authenticators由self.get_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遍歷authentication_classes,并實例化authentication_classes中的對象加入到列表中返回
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
實際上authentication_classes只是一個包含認證類的列表
已經亂了,整理一下
首先,用戶會生成一個httprequest
,這個請求到URL調度器后會執行as_view()
path('shop/', views.ShopView.as_view())
而在as_view()中就會把這個原生的httprequest
傳遞給dispatch()
在dispatch()
中會對這個httprequest
進一步封裝,在這里具體就是增加了一個authenticators
,他是一個列表,列表中是一系列從authentication_classes列表中實例化出來的對象。
然后進入try
塊,執行self.initial(request, *args, **kwargs)
,這條語句用來 “運行在調用方法處理程序之前需要發生的任何事情” 可以說是一個功能集合,聚合了認證管理,權限管理,版本控制等幾個功能模塊
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)
現在只關心用戶認證的工作,進入perform_authentication(request)
(現在的request已經是重新包裝過的的request了),也只有一句話。
def perform_authentication(self, request):request.user
它調用了這個request對象的user屬性,進入user,是一個屬性方法,主體是調用了self._authenticate()
@property
def user(self):if not hasattr(self, '_user'):# 只是一個上下文管理器,方便清理之類的工作with wrap_attributeerrors():self._authenticate()return self._user
現在是那個封裝過的request對象調用了自己的user屬性方法,所以self
已經是request了,之前是在視圖(view.py)中自己定義的ShopView
進入self._authenticate()
def _authenticate(self):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_tuplereturnself._not_authenticated()
他會遍歷self.authenticators
,現在的self是那個分裝過的request,所以self.authenticators
其實就是上面列表生成式生成的那個認證類對象列表,它遍歷并調用每一個認證類對象的authenticate
方法,這個方法必須覆蓋,否則會拋出NotImplementedError異常
def authenticate(self, request):raise NotImplementedError(".authenticate() must be overridden.")
這里的邏輯是一旦authenticate()
拋出exceptions.APIException
異常,就調用self._not_authenticated()
也就是認證失敗,如果沒有拋出異常,就進入下面的if語句,判斷返回值是否是None
如果是,本次循環就結束,也就是不使用這個認證類對象,轉而使用下一個認證類對象,如果不為None
則進行一個序列解包操作,把元組中的第一個元素賦值給self.user
第二個元素賦值給self.auth
,終止循環,如果遍歷完整個self.authenticators
還是沒認證成功,就會執行最后一行的self._not_authenticated()
和認證時拋出異常一樣,認證失敗。
def _not_authenticated(self):"""設置authenticator,user&authToken表示未經過身份驗證的請求。默認值為None,AnonymousUser&None。"""self._authenticator = Noneif api_settings.UNAUTHENTICATED_USER:self.user = api_settings.UNAUTHENTICATED_USER()else:self.user = Noneif api_settings.UNAUTHENTICATED_TOKEN:self.auth = api_settings.UNAUTHENTICATED_TOKEN()else:self.auth = None
認證失敗后的邏輯是:先看配置文件中有沒有UNAUTHENTICATED_USER
,如果有,就把這個配置內容作為默認的“匿名用戶”,否則就把self.user
賦值為None,self.auth
也一樣。
這大概就是認證的基本流程了。
過程總結
用戶發出請求,產生request,傳遞到URL調度器,url調度器將request傳遞給as_view()
,as_view()
再傳遞給dispatch()
,在這里會給原來的request
封裝用來身份驗證的authenticators
,他是一個儲存認證類對象的列表,封裝完成后遍歷這個列表,如果拋出exceptions.APIException
異常,認證失敗,使用匿名用戶登錄,否則如果返回一個二元組,就將他們分別賦值給user和auth,如果返回None,同樣認證失敗,使用匿名用戶登錄。
全局驗證
可以設置對所有視圖驗證,因為
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
def reload_api_settings(*args, **kwargs):setting = kwargs['setting']if setting == 'REST_FRAMEWORK':api_settings.reload()
所以在Django的配置文件中添加
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ['demo.utils.MyAuthentication.MyAuthentication']
}
就可以設置所有視圖都要使用MyAuthentication驗證,如果由別的視圖不需要驗證,可在視圖類內把authentication_classes
設置為空列表。