源碼剖析Django REST framework的認證方式
由Django的CBV模式流程,能夠知道在url匹配完成後,會執行自定義的類中的as_view方法
。javascript
若是自定義的類中沒有定義as_view方法
,根據面向對象中類的繼承能夠知道,則會執行其父類View中的as_view方法
html
在Django的View的as_view方法中,又會調用dispatch方法
。java
如今來看看Django restframework的認證流程python
Django restframework是基於Django的框架,因此基於CBV的模式也會執行自定義的類中的as_view方法nginx
先新建一個項目,配置url數據庫
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^user/', views.UserView.as_view()),
]
views.py文件內容django
from django.shortcuts import render,HttpResponse
from rest_framework.views import APIView
class 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/
發送get請求api
能夠知道請求發送成功。如今來看看源碼流程,因爲UserView繼承APIView,查看APIView中的as_view方法瀏覽器
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方法
。上一篇文章源碼解析Django CBV的本質中已經知道,View類的as_view方法會調用dispatch方法。ruby
View類的as_view方法源碼以下所示
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這個類
,上面的代碼會執行UserView類中dispatch方法。
因爲UserView類中並無定義dispatch方法,而UserView類繼承自Django restframework的APIView類,因此會執行APIView類中的dispatch方法
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
能夠看到,先執行initialize_request方法處理瀏覽器發送的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方法裏,把瀏覽器發送的request和restframework的處理器,認證,選擇器等對象列表做爲參數實例化Request類中獲得新的request對象並返回,其中跟認證相關的對象就是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來自於api_settings的配置
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
經過查看api_settings的源碼能夠知道,能夠在項目的settings.py文件中進行認證相關的配置
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對象
而後執行initial方法來處理新獲得的request對象,再來看看initial方法中又執行了哪些操做
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)
由上面的源碼能夠知道,在initial方法中,執行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
從request中獲取_user
的值,若是獲取到則執行_authenticate方法
,不然返回_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
在這裏self.authenticators
其實是get_authenticators
方法執行完成後返回的對象列表
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 () ...
循環認證的對象列表,執行每個認證方法的類中的authenticate方法
,獲得經過認證的用戶及用戶的口令的元組,並返回元組完成認證的流程
在_authenticate
方法中使用了try/except方法來捕獲authenticate方法可能出現的異常
若是出現異常,就調用_not_authenticated
方法來設置返回元組中的用戶及口令並終止程序繼續運行
總結,Django restframework的認證流程以下圖
Django restframework內置的認證類
在上面的項目例子中,在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")
打印結果爲
authentication_classes: [<class 'rest_framework.authentication.SessionAuthentication'>, <class 'rest_framework.authentication.BasicAuthentication'>]
AnonymousUser
由此能夠知道,authentication_classes
默認是Django restframework內置的認證類,而request._user爲AnonymousUser,由於發送GET請求,用戶沒有進行登陸認證,因此爲匿名用戶
在視圖函數中導入這兩個類,再查看這兩個類的源碼,能夠知道
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):
...
從上面的源碼能夠發現,這個文件中不只定義了SessionAuthentication
和BasicAuthentication
這兩個類,
相關的類還有TokenAuthentication
,並且這三個認證相關的類都是繼承自BaseAuthentication
類
從上面的源碼能夠大概知道,這三個繼承自BaseAuthentication
的類是Django restframework內置的認證方式.
自定義認證功能
在上面咱們知道,Request會調用認證相關的類及方法,APIView
會設置認證相關的類及方法
因此若是想自定義認證功能,只須要重寫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 exceptions
TOKEN_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方法會拋出異常
pass
class 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
,而後回車,在服務端後臺會打印
aabbcc
把瀏覽器中的url換爲http://127.0.0.1:8000/users/?tk=ddeeff
,後臺打印信息則變爲
ddeeff
這樣就實現REST framework的自定義認證功能
Django restframework認證的擴展
基於Token進行用戶認證
修改上面的項目,在urls.py文件中添加一條路由記錄
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^users/',views.UsersView.as_view()),
url(r'^auth/',views.AuthView.as_view()),
]
修改視圖函數
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 JsonResponse
def gen_token(username):
""" 利用時間和用戶名生成用戶token :param username: :return: """
import time
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):
""" 獲取用戶提交的用戶名和密碼,若是用戶名和密碼正確,則生成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 models
user_obj = models.UserInfo.objects.filter(user=user, pwd=pwd).first()
if user_obj:
token = gen_token(user) # 生成用戶口令
# 若是數據庫中存在口令則更新,若是數據庫中不存在口令則建立用戶口令
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'] = "用戶名或密碼錯誤"
return JsonResponse(res)
class UserAuthView(BaseAuthentication):
def authenticate(self,request):
tk=request.query_params.GET.get("tk") # 獲取請求頭中的用戶token
from app01 import models
token_obj=models.Token.objects.filter(token=tk).first()
if token_obj: # 用戶數據庫中已經存在用戶口令返回認證元組
return (token_obj.user,token_obj)
raise exceptions.AuthenticationFailed("認證失敗")
def authenticate_header(self,request):
pass
class UsersView(APIView):
authentication_classes = [UserAuthView,]
def get(self,request,*args,**kwargs):
return HttpResponse(".....")
建立用戶數據庫的類
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)
建立數據庫,並添加兩條用戶記錄
再建立一個test_client.py文件,來發送post請求
import requests
response=requests.post(
url="http://127.0.0.1:8000/auth/",
data={'user':'user1','pwd':'user123'},
)
print("response_text:",response.text)
啓動Django項目,運行test_client.py文件,則項目的響應信息爲
response_text: {"code": 1001, "msg": null, "token": "eccd2d256f44cb25b58ba602fe7eb42d"}
由此,就完成了自定義的基於token的用戶認證
若是想在項目中使用自定義的認證方式時,能夠在authentication_classes
繼承剛纔的認證的類便可
authentication_classes = [UserAuthView,]
全局自定義認證
在正常的項目中,一個用戶登陸成功以後,進入本身的主頁,能夠看到不少內容,好比用戶的訂單,用戶的收藏,用戶的主頁等
此時,難倒要在每一個視圖類中都定義authentication_classes,而後在authentication_classes中追加自定義的認證類嗎?
經過對Django restframework認證的源碼分析知道,能夠直接在項目的settings.py配置文件中引入自定義的認證類,便可以對全部的url進行用戶認證流程
在應用app01目錄下建立utils包,在utils包下建立auth.py文件,內容爲自定義的認證類
from rest_framework import exceptions
from api import models
class 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
在settings.py文件中添加內容
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 JsonResponse
def gen_token(username):
""" 利用時間和用戶名生成用戶token :param username: :return: """
import time
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):
authentication_classes = [] # 在這裏定義authentication_classes後,用戶訪問auth頁面不須要進行認證
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 models
user_obj = models.UserInfo.objects.filter(user=user, pwd=pwd).first()
if user_obj:
token = gen_token(user) # 生成用戶口令
# 若是數據庫中存在口令則更新,若是數據庫中不存在口令則建立用戶口令
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'] = "用戶名或密碼錯誤"
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
發送GET請求,響應結果以下
在url中不帶token,使用POSTMAN向http://127.0.0.1:8000/order/
和http://127.0.0.1:8000/user/
發送GET請求,則會出現"認證失敗"
的提示
由此能夠知道,在settings.py配置文件中配置自定義的認證類也能夠實現用戶認證功能
配置匿名用戶
修改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爲空列表表示視圖類不進行認證
def get(self,request,*args,**kwargs):
print(request.user)
print(request.auth)
return HttpResponse("OrderView GET")
使用瀏覽器向http://127.0.0.1:8000/order/
發送GET請求,後臺打印
這說明在settings.py文件中配置的匿名用戶和匿名用戶的token起到做用
建議把匿名用戶及匿名用戶的token都設置爲:None
Django restframework內置的認證類
從rest_framework中導入authentication
from rest_framework import authentication
能夠看到Django restframework內置的認證類
class BaseAuthentication(object):
def authenticate(self, request):
...
def authenticate_header(self, request):
pass
class 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內置的認證包含下面的四種:
BasicAuthentication
SessionAuthentication
TokenAuthentication
RemoteUserAuthentication
而這四種認證類都繼承自BaseAuthentication
,在BaseAuthentication中定義了兩個方法:authenticate和authenticate_header
總結:
爲了讓認證更規範,自定義的認證類要繼承 BaseAuthentication類
自定義認證類必需要實現authenticate和authenticate_header方法
authenticate_header方法的做用:在認證失敗的時候,給瀏覽器返回的響應頭,能夠直接pass,不實現authenticate_header程序會拋出異常