本篇藉由追蹤DRF認證的源碼,幫助初學者能夠更佳理解其相關機制。
以下是簡單的Authentication寫法:
# setting.py
# 全局認證設定
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["authentication.auth.MyAuthentication"]
}
# authentication/auth.py
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('token')
# 假設request有帶token就給過
if token:
return ('user', token)
raise AuthenticationFailed({"code": "401", "message": "Unauthorized"})
# app/views.py
# 提供2個view
class UserView(APIView):
authentication_classes = []
def get(self, request):
print(request.user, request.auth)
return Response({'message': 'success'})
class LoginView(APIView):
def get(self, request):
print(request.user, request.auth)
return Response({'message': 'success'})
# 路由
urlpatterns = [
path('user/', UserView.as_view()),
path('login/', LoginView.as_view()),
]
從源碼分析,以上執行as_view()方法,將返回view作為路由的callback function,並在接收request時執行dispatch,透過反射到我們自定義類中的方法 (get, post, delete, put ...)
# as_view()返回view函式,view函式執行dispatch
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
if not hasattr(self, "request"):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
return view
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)
從上可以觀察到DRF有針對Django的request再進行封裝:
request = self.initialize_request(request, *args, **kwargs)
self.request = request
# 增加 authenticators
def initialize_request(self, request, *args, **kwargs):
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
# authenticators的內容
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
APIView下的 authentication_classes 是api_settings.DEFAULT_AUTHENTICATION_CLASSES,也就是我們一開始在settings.py設定的位置。而如果像我們在自定義類UserView中有設定authentication_classes = [],依繼承規則會優先去使用。
在dispatch中會使用initial,其內最終就會用到request.user,還記得我前一篇文章嗎?
self.initial(request, *args, **kwargs)
# initail 內有 perform_authentication (其他程式碼省略)
def initial(self, request, *args, **kwargs):
self.perform_authentication(request)
# 使用到 request.user
def perform_authentication(self, request):
request.user
至於request.user是在哪裡給值的呢?我們再觀察一下源碼,[auth() for auth in self.authentication_classes] 中會分別取authentication_classes內的類並實例化成對象:
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
在Request類的源碼,會依次嘗試每個認證器進行認證,直到有一個成功,或者所有認證器都失敗
# 一開始沒有_user,故執行self._authenticate()
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
# user_auth_tuple 為我們定義認證類成功的返回值 ('user', token)
def _authenticate(self):
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
self._not_authenticated()
依然承接前篇文章,若認證失敗或沒設定認證器,則會進入_not_authenticated。
如果在純淨版把一些app拿掉,沒有給UNAUTHENTICATED_USER,這邊就會造成報錯了。
def _not_authenticated(self):
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
以上為DRF認證機制的源碼解析,希望你們會喜歡!