咱們知道,在django項目中無論路由以及對應的視圖類是如何寫的,都會走到 dispatch
方法,進行路由分發,vue
在閱讀 APIView類中的dispatch
方法的源碼中,有個 self.initial(request, *args, **kwargs)
,能夠發現認證、權限、頻率這三個默認組件都在這個方法裏面,若是咱們本身沒有定義這三個組件的配置,那麼就會使用源碼中默認的一些配置。python
源碼:數據庫
# Ensure that the incoming request is permitted #實現認證 self.perform_authentication(request) #權限判斷 self.check_permissions(request) #控制訪問頻率 elf.check_throttles(request)
目前爲止你們知道的認證機制是否是有cookie、session啊,session更安全一些,可是你會發現session的信息都存到我們的服務器上了,若是用戶量很大的話,服務器壓力是比較大的,而且django的session存到了django_session表中,不是很好操做,可是通常的場景都是沒有啥問題的,如今生產中使用的一個叫作token機制的方式比較多,如今咱們是否是就知道個csrf_token啊,其實token有不少種寫法,如何加密,你是hashlib啊仍是base64啊仍是hmac啊等,是否是加上過時時間啊,是否是要加上一個secret_key(客戶端與服務端協商好的一個字符串,做爲雙方的認證依據),是否是要持續刷新啊(有效時間要短,不斷的更新token,若是在這麼短的時間內仍是被別人拿走了token,模擬了用戶狀態,那這個基本是沒有辦法的,可是你能夠在網絡或者網絡設備中加安全,存客戶的ip地址等,防黑客)等等。django
大體流程圖解:json
首先咱們須要建立一個表,用戶表,裏面放一個token字段,其實通常我都是放到兩個表裏面,和用戶表是一個一對一關係的表,看代碼:後端
models.py內容以下:安全
################################# user表 ############################### class User(models.Model): user = models.CharField(max_length=32) pwd = models.CharField(max_length=32) type_choice=((1,"VIP"),(2,"SVIP"),(3,"SSVIP")) user_type = models.IntegerField(choices=type_choice) class UserToken(models.Model): user = models.OneToOneField(to=User) #一對一到用戶表 token = models.CharField(max_length=128) #設置的長度大一些 # expire_time = models.DateTimeField() #若是作超時時間限制,能夠在這裏加個字段
urls.py內容以下:服務器
#登錄認證接口 url(r'^login/$', views.LoginView.as_view(),), #別忘了$符號結尾
views.py內容以下:每次登錄成功以後刷新token值cookie
###################login邏輯接口####################### #關於邏輯接口而不是提供數據的接口,咱們不用ModelViewSet,而是直接寫個類,繼承APIView,而後在類裏面直接寫咱的邏輯 import uuid import os import json class LoginView(APIView): #從先後端分離的項目來說,get請求不須要寫,由於get就是個要登錄頁面的操做,vue就搞定了,因此咱們這裏直接寫post請求就能夠了 def post(self,request): # 通常,請求過來以後,咱們後端作出的響應,都是個字典,不只包含錯誤信息,還有要狀態碼等,讓客戶端明白到底發生了什麼事情 # 'code'的值,1表示成功,0表示失敗,2表示其餘錯誤(本身能夠作更細緻的錯誤代碼昂) res = {'code': 1, 'msg': None, 'user': None,'token':None} print(request.data) try: user = request.data.get('user') pwd = request.data.get('pwd') # 數據庫中查詢 user_obj = models.User.objects.filter(user=user, pwd=pwd).first() if user_obj: res['user'] = user_obj.user # 添加token,用到我們usertoken表 # models.UserToken.objects.create(user=user,token='123456') # 建立token隨機字符串,我寫了兩個方式,簡寫的昂,最好再加密一下 random_str = uuid.uuid4() # random_str = os.urandom(16) bytes類型的16位的隨機字符串 models.UserToken.objects.update_or_create( user=user_obj, # 查找篩選條件 defaults={ # 添加或者更新的數據 "token": random_str, } ) res['token'] = random_str res['msg'] = '登錄成功' else: res['code'] = 0 res['msg'] = '用戶名或者密碼錯誤' return Response(res) except Exception as e: res['code'] = 2 res['msg'] = str(e) return Response(res)
經過上面的代碼咱們將token返回給了用戶,那麼之後用戶無論發送什麼請求,都要帶着我給它的token值來訪問,認證token經過才行,而且更新token。網絡
未來有些數據接口是必需要求用戶登錄以後才能獲取到數據,因此未來用戶登錄完成以後,每次再過來請求,都要帶着token來,做爲身份認證的依據。
在DRF的全局配置信息中,有DRF內置組件的相關配置 :
# 基於django基礎配置信息上的drf框架的配置信息 REST_FRAMEWORK = { ... 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', ), ... }
補充:
django項目的request對象中有一個user屬性,是一個默認的假用戶
AnonymousUser
,當用戶未登陸admin後臺管理系統時,request.user = AnonymousUser
若登陸了admin後臺,在django內置的認證系統中,request.user = 當前登陸用戶
DRF中內置的認證系統
'rest_framework.authentication.SessionAuthentication'
跟django中的認證系統掛鉤的,使用的也是admin後臺的用戶信息
DRF內置組件依據session信息作認證,根據上面的敘述,session不適合作大型項目的認證載體,過重了!因此咱們能夠藉助DRF內置的認證系統進行自定義認證系統。
如下是其認證機制的部分源碼實現:
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是APIView封裝的新的request對象 self.user, self.auth = user_auth_tuple return #退出了這個函數,函數就不會執行了,不會再循環了,因此若是你的第一個認證類有返回值,那麼第二個認證類就不會執行了,因此別忘了return是結束函數的意思,因此若是你有多個認證類,那麼返回值放到最後一個類裏面
from rest_framework.authentication import BaseAuthentication # 'BaseAuthentication'這個類源碼實現中有個'authenticate'方法,咱們能夠重寫這個類進行自定製我們的認證系統。 """ def authenticate(self, request): """ Authenticate the request and return a two-tuple of (user, token). """ raise NotImplementedError(".authenticate() must be overridden.") """
1、基於drf提供的認證類BaseAuthentication進行自定製認證系統
一、自定義認證類MyAuthentication,繼承自BaseAuthentication類且重寫其authenticate方法
# utils.auth.py from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed class MyAuthentication(BaseAuthentication): def authenticate(self, request): if 1: return "aliang", "fengting007" else: return AuthenticationFailed("認證失敗!")
二、全局配置
對自定義的認證類MyAuthentication中的認證流程進行全局配置,即在訪問每一個url時都要通過自定義的認證邏輯
# 基於django基礎配置信息上的drf框架的配置信息 REST_FRAMEWORK = { ... 'DEFAULT_AUTHENTICATION_CLASSES': ( # 自定義認證類的路徑 'four.utils.auth.MyAuthentication', # 註釋掉drf內置的session認證系統,使用本身定製的! # 'rest_framework.authentication.SessionAuthentication', ), ... }
三、局部配置
即在訪問某個指定的url時,須要認證後才能夠獲取到響應頁面,而不是訪問全部url都須要認證,這就要在指定的url所映射的類視圖中進行定製
from rest_framework.views import APIView from rest_framework.response import Response from four.utils.auth import MyAuthentication class AuthAPIView(APIView): # 訪問AuthAPIView類視圖所對應的url請求響應頁面時,要通過自定製的MyAuthentication類的認證邏輯! authentication_classes = [MyAuthentication,] def get(self, request): print(request.user) # 'aliang' print(request.auth) # 'fengting007' return Response({'msg':'嗨,man!'})
2、不繼承drf提供的認證類BaseAuthentication自定製認證類
class MyAuthentication(): def authenticate_header(self,request): pass # authenticate方法是固定的,而且必須有個參數,這個參數是新的request對象 def authenticate(self, request): if 1: # 源碼中會發現,這個方法會有兩個返回值,而且這兩個返回值封裝到了新的request對象中了,request.user-->用戶名 和 request.auth-->token值,這兩個值做爲認證結束後的返回結果 # 以下操做即: # request.user = 'aliang' # request.auth = 'fengting007' return "aliang", "fengting007"
至於此自定義認證類的全局配置以及局部配置的方法與上面的流程是一致的。
# 局部配置示例: from app01.serializer import BookSerializers from four.utils.auth import MyAuthentication class BookView(APIView): #認證組件確定是在get、post等方法執行以前執行的,源碼中這個組件是在dispatch的地方調用的 authentication_classes = [MyAuthentication,] #認證類能夠寫多個,一個一個的順序驗證 def get(self,request): #這樣就拿到了上面MyAuthentication類的authenticate方法的兩個返回值 print(request.user) print(request.auth) book_obj_list = models.Book.objects.all() s_books = BookSerializers(book_obj_list,many=True) return Response(s_books.data)