DjangoRestFramework學習三之認證組件、權限組件、頻率組件、url註冊器、響應器、分頁組件
本節目錄html
1. 局部認證組件前端
咱們知道,咱們無論路由怎麼寫的,對應的視圖類怎麼寫的,都會走到dispatch方法,進行分發,vue
在我們看的APIView類中的dispatch方法的源碼中,有個self.initial(request, *args, **kwargs),那麼認證、權限、頻率這三個默認組件都在這個方法裏面了,若是咱們本身沒有作這三個組件的配置,那麼會使用源碼中默認的一些配置。進源碼去看看你就會看到下面三個東西:數據庫
# 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字段,其實通常我都是放到兩個表裏面,和用戶表是一個一對一關係的表,看代碼:後端
################################# 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內容以下:api
#登錄認證接口
url(r'^login/$', views.LoginView.as_view(),), #別忘了$符號結尾
views.py內容以下:本身寫一個每次登錄成功以後刷新token值瀏覽器
###################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。安全
下面咱們玩一下drf提供的認證組件的玩法。
DRF的認證組件
未來有些數據接口是必需要求用戶登錄以後才能獲取到數據,因此未來用戶登錄完成以後,每次再過來請求,都要帶着token來,做爲身份認證的依據。
from app01.serializer import BookSerializers
#####################Book表操做##########################
class UserAuth():
def authenticate_header(self,request):
pass
#authenticate方法固定的,而且必須有個參數,這個參數是新的request對象,不信,看源碼
def authenticate(self,request):
if 1:
#源碼中會發現,這個方法會有兩個返回值,而且這兩個返回值封裝到了新的request對象中了,request.user-->用戶名 和 request.auth-->token值,這兩個值做爲認證結束後的返回結果
return "chao","asdfasdfasdf"
class BookView(APIView):
#認證組件確定是在get、post等方法執行以前執行的,還記得源碼的地方嗎,這個組件是在dispatch的地方調用的,咱們是上面寫個UserAuth類
authentication_classes = [UserAuth,] #認證類能夠寫多個,一個一個的順序驗證,假若有多個認證組件,只有最後一個有return值,別的return個None 或者直接return 後面不跟參數
def get(self,request):
'''
查看全部書籍
:param request:
:return:
'''
#這樣就拿到了上面UserAuth類的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)
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
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是結束函數的意思,因此若是你有多個認證類,那麼返回值放到最後一個類裏面
好,咱們寫一寫獲取token值,而後校驗的功能,看views.py的代碼:
from django.shortcuts import render,HttpResponse,redirect
from django.views import View
from rest_framework import serializers
from app01 import models
from rest_framework.views import APIView
from rest_framework.response import Response
#將序列化組件都放到一個單獨的文件裏面,而後引入進來
from app01.serializer import BookSerializers,PublishSerializers,AuthorSerializers
#drf提供的認證失敗的異常
from rest_framework.exceptions import AuthenticationFailed
class UserAuth():
#每一個認證類,都須要有個authenticate_header方法,而且有個參數request
def authenticate_header(self,request):
pass
#authenticate方法固定的,而且必須有個參數,這個參數是新的request對象,不信,看源碼
def authenticate(self,request):
# token = request._request.GET.get("token")
#因爲咱們這個request是新的request對象,而且老的request對象被封裝到了新的request對象中,名字是self._request,因此上面的取值方式是沒有問題的,不過人家APIView不只封裝了老的request對象,而且還給你加了query_params屬性,和老的request.GET獲得的內容是同樣的,因此能夠直接按照下面的方式來寫
token = request.query_params.get("token")
#用戶請求來了以後,咱們獲取token值,到數據庫中驗證
usertoken = models.UserToken.objects.filter(token=token).first()
if usertoken:
#驗證成功以後,能夠返回兩個值,也能夠什麼都不返回
return usertoken.user.user,usertoken.token
#源碼中會發現,這個方法會有兩個返回值,而且這兩個返回值封裝到了新的request對象中了,request.user-->用戶名 和 request.auth-->token值,這兩個值做爲認證結束後的返回結果
else:
#由於源碼內部進行了異常捕獲,而且給你主動返回一個forbiden錯誤,因此咱們在這裏主動拋出異常就能夠了
raise AuthenticationFailed("認證失敗")
urls.py內容以下:
url(r'^books/$', views.BookView.as_view(),),
經過postman發送請求,你會發現錯誤:
若是咱們請求中帶了數據庫中保存的token值,那麼就會成功獲取數據,看數據庫中的token值:
而後經過postman再請求,帶着token值,看效果,成功了:
繼承drf的BaseAuthentication認證類的寫法:
from app01 import models
from rest_framework.views import APIView
from rest_framework.response import Response
#將序列化組件都放到一個單獨的文件裏面,而後引入進來
from app01.serializer import BookSerializers,PublishSerializers,AuthorSerializers
#drf提供的認證失敗的異常
from rest_framework.exceptions import AuthenticationFailed from rest_framework.authentication import BaseAuthentication
#繼承drf的BaseAuthentication類
class UserAuth(BaseAuthentication):
# 繼承了BaseAuthentication類以後,這個方法就不用寫了
# def authenticate_header(self,request):
# pass
def authenticate(self,request):
# token = request._request.GET.get("token")
token = request.query_params.get("token")
#有request對象,那麼不只僅能夠認證token,還能夠認證請求裏面的其餘內容
usertoken = models.UserToken.objects.filter(token=token).first()
if usertoken:
#驗證成功以後
return usertoken.user.user,usertoken.token
else:
raise AuthenticationFailed("認證失敗")
class BookView(APIView):
#經過源碼看,認證類的查找過程,和解析組件的查找過程是同樣的
authentication_classes = [UserAuth,]
def get(self,request):
'''
查看全部書籍
:param request:
:return:
'''
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)
帶時間戳的隨機字符串:
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
def get_random_str(user):
import hashlib,time
ctime=str(time.time())
md5=hashlib.md5(bytes(user,encoding="utf8"))
md5.update(bytes(ctime,encoding="utf8"))
return md5.hexdigest()
全局視圖認證組件:
在settings.py文件中配置:若是我再app01文件夾下的service文件夾下的auth文件夾下寫了咱們本身的認證類,那麼全局配置的寫法就按照下面的方式寫。
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",] #裏面是路徑字符串
}
認證組件就說這些,看權限組件吧。
局部視圖權限:
在app01.service.permissions.py中
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
message="SVIP才能訪問!" #變量只能叫作message
def has_permission(self, request, view): #重寫has_permission方法,本身寫權限邏輯,看看源碼就明白了,這個view是咱當前類的實例化對象,通常用不到,可是必須給個參數寫在這裏。
if request.user.user_type==3:
return True #經過權限
return False #沒有經過
在views.py:
from app01.service.permissions import *
class BookViewSet(generics.ListCreateAPIView):
permission_classes = [SVIPPermission,]
queryset = Book.objects.all()
serializer_class = BookSerializers
全局視圖權限:
settings.py配置以下:
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
"DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",]
}
局部視圖throttle,反爬,防攻擊
在throttles.py中:
from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
import time
from rest_framework import exceptions
visit_record = {}
class VisitThrottle(BaseThrottle):
# 限制訪問時間
VISIT_TIME = 10
VISIT_COUNT = 3
# 定義方法 方法名和參數不能變
def allow_request(self, request, view):
# 獲取登陸主機的id
id = request.META.get('REMOTE_ADDR')
self.now = time.time()
if id not in visit_record:
visit_record[id] = []
self.history = visit_record[id]
# 限制訪問時間
while self.history and self.now - self.history[-1] > self.VISIT_TIME:
self.history.pop()
# 此時 history中只保存了最近10秒鐘的訪問記錄
if len(self.history) >= self.VISIT_COUNT:
return False
else:
self.history.insert(0, self.now)
return True
def wait(self):
return self.history[-1] + self.VISIT_TIME - self.now
在views.py中:
from app01.service.throttles import *
class BookViewSet(generics.ListCreateAPIView):
throttle_classes = [VisitThrottle,]
queryset = Book.objects.all()
serializer_class = BookSerializers
全局視圖throttle
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
"DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
"DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",]
}
內置throttle類
在throttles.py修改成:
class VisitThrottle(SimpleRateThrottle):
scope="visit_rate"
def get_cache_key(self, request, view):
return self.get_ident(request) #這裏的self.get_ident(request)是獲取的ip地址,把ip地址傳給頻率控制器.也能夠傳別的值,只要是這個用戶惟一的就行
settings.py設置:
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
"DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
"DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
"DEFAULT_THROTTLE_RATES":{ "visit_rate":"5/m", #可使用秒 時 分 日 月 年 5/m 一分鐘最多訪問5次
}
}
幫咱們自動生成4個url,和咱們本身寫的差很少:
from django.conf.urls import url,include
from django.contrib import admin
from app01 import views
from rest_framework import routers
router = routers.DefaultRouter()
#自動幫咱們生成四個url
router.register(r'authors', views.AuthorView)
router.register(r'books', views.BookView)
urlpatterns = [
# url(r'^books/$', views.BookView.as_view(),), #別忘了$符號結尾
# url(r'api/', include(router.urls)),
url(r'', include(router.urls)), #http://127.0.0.1:8000/books/ 也能夠這樣寫:http://1270.0..1:8000/books.json/
#登錄認證接口
url(r'^login/$', views.LoginView.as_view(),), #別忘了$符號結尾
]
可是有個前提就是,咱們用的是:ModelViewSet序列化組件。
from rest_framework.viewsets import ModelViewSet
class AuthorView(ModelViewSet):
queryset = models.Author.objects.all()
serializer_class = AuthorSerializers
class BookView(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = BookSerializers
因此,這個url註冊器其實並無那麼好用,固然啦,看需求.
簡單看看就行啦:
from rest_framework.viewsets import ModelViewSet
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
#若是咱們沒有在本身的視圖類裏面配置,那麼源碼裏面默認就用的這兩個JSONRenderer,BrowsableAPIRenderer
#BrowsableAPIRenderer 是當客戶端爲瀏覽器的時候,回覆的數據會自動給你生成一個頁面形式的數據展現,通常開發的時候,都不用頁面形式的
#JSONRenderer:回覆的是json數據
class BookView(ModelViewSet):
# renderer_classes = [JSONRenderer,] #其實默認就是這個JSONRenderer,因此通常不用在這裏配置了
queryset = models.Book.objects.all()
serializer_class = BookSerializers
簡單使用:
#引入分頁
from rest_framework.pagination import PageNumberPagination
class BookView(APIView):
# 經過源碼看,認證類的查找過程,和解析組件的查找過程是同樣的
# authentication_classes = [UserAuth,]
# throttle_classes = [VisitThrottle,]
def get(self,request):
'''
查看全部書籍
:param request:
:return:
'''
book_obj_list = models.Book.objects.all()
#建立分頁器對象,PageNumberPagination類中除了PAGE_SIZE屬性以外,還有個page屬性,這個page屬性是第幾頁,用法是http://127.0.0.1:8000/books/?page=1
pnb = PageNumberPagination()
#經過分頁器對象的paginate_queryset方法進行分頁,
paged_book_list = pnb.paginate_queryset(book_obj_list,request,)
#將分頁的數據進行序列化
s_books = BookSerializers(paged_book_list,many=True)
return Response(s_books.data)
settings配置文件:
REST_FRAMEWORK={
# "DEFAULT_THROTTLE_RATES":{
# "visit_rate":"5/m",
# },
'PAGE_SIZE':5, #這是全局的一個每頁展現多少條的配置,可是通常不用它,由於不用的數據展現可能每頁展現的數量是不一樣的
}
若是咱們不想用全局的page_size配置,咱們本身能夠寫個類來繼承分頁類組件,重寫裏面的屬性:
#引入分頁
from rest_framework.pagination import PageNumberPagination
class MyPagination(PageNumberPagination):
page_size = 3 #每頁數據顯示條數
page_query_param = 'pp' #http://127.0.0.1:8000/books/?pp=1,查詢哪一頁的數據
page_size_query_param = 'size' #若是咱們顯示的一頁數據不夠你用的,你想臨時的多看展現一些數據,能夠經過你設置的這個page_size_query_param做爲參數來訪問:http://127.0.0.1:8000/books/?pp=2&size=5 #那麼你看到的雖然是第二頁,可是能夠看到5條數據,意思是將page_size的數字臨時擴大了,每頁展現的數據就多了或者少了,看你的page_size_query_param設置的值
max_page_size = 10 #最大每頁展現多少條,即使是你前端經過page_size_query_param臨時調整了page_size的值,可是最大也不能超過咱們設置的max_page_size的值
class BookView(APIView):
def get(self,request):
'''
查看全部書籍
:param request:
:return:
'''
book_obj_list = models.Book.objects.all()
pnb = MyPagination()
paged_book_list = pnb.paginate_queryset(book_obj_list,request,)
s_books = BookSerializers(paged_book_list,many=True)
return Response(s_books.data)
還有咱們玩的繼承了ModelViewSet類的試圖類使用分頁器的寫法:
from rest_framework.viewsets import ModelViewSet
from rest_framework.pagination import PageNumberPagination
class MyPagination(PageNumberPagination):
page_size = 3
page_query_param = 'pp'
page_size_query_param = 'size'
max_page_size = 10
class BookView(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = BookSerializers
pagination_class = MyPagination #配置咱們本身寫的分頁類
還有個偏移分頁,瞭解一下就好了
from rest_framework.pagination import LimitOffsetPagination