Token 認證的前因後果,DRF認證,DRF權限,DRF限制

上一章節內容回顧:

1.五個葫蘆娃和三行代碼
APIView(views.View)
    1.封裝了Django的request
    	- request.query_params  --> 取URL中的參數
        - request.data          --> 取POST和PUT請求中的數據

    2. 重寫了View中的dispatch方法
    	dispatch方法
通用類(generics)
	GenericAPIView
        - queryset
        - serializer_class

混合類(mixins)
	- ListModelMixin       --> list     
    - CreateModelMixin     --> create
    - RetrieveModelMixin   --> retrieve
    - DestroyModelMixin    --> destroy
    - UpdateModelMixin     --> update

    CommentView(GenericAPIView, ListModelMixin, CreateModelMixin):
        def get():
            return self.list()
            
        def post():
            return self.create()

偶數娃:
	CommentView(ListCreateAPIView):
        queryset = ...
        serializer_class = ...

奇數娃
    CommentDetail(RetrieveUpdateDestroyAPIView):
        queryset = ...
        serializer_class = ...

套娃:
    Comment(ModelViewSet):
        queryset = ...
        serializer_class = ...

APIView和ModelViewSet,該如何取捨。看需求。若是用ModelViewSet,只能按照它要求的格式來走
若是想加入一點個性化的數據,好比{"code":0,"msg":None}仍是得須要使用APIViewhtml

1、Token 認證的前因後果

摘要

Token 是在服務端產生的。若是前端使用用戶名/密碼向服務端請求認證,服務端認證成功,那麼在服務端會返回 Token 給前端。前端能夠在每次請求的時候帶上 Token 證實本身的合法地位前端

爲何要用 Token?

而要回答這個問題很簡單——由於它能解決問題!python

能夠解決哪些問題呢?ajax

  1. Token 徹底由應用管理,因此它能夠避開同源策略sql

  2. Token 能夠避免 CSRF 攻擊數據庫

  3. Token 能夠是無狀態的,能夠在多個服務間共享django

Token 是在服務端產生的。若是前端使用用戶名/密碼向服務端請求認證,服務端認證成功,那麼在服務端會返回 Token 給前端。前端能夠在每次請求的時候帶上 Token 證實本身的合法地位。若是這個 Token 在服務端持久化(好比存入數據庫),那它就是一個永久的身份令牌。後端

時序圖表示

使用 Token 的時序圖以下:服務器

1)登陸cookie

2)業務請求

關於token的詳細信息,請參考連接:

https://blog.csdn.net/maxushan001/article/details/79222271

 

2、DRF 認證

前提

仍是依然使用昨天的項目about_drf3

定義一個用戶表和一個保存用戶Token的表,models.py完整代碼下:

from django.db import models


# Create your models here.


# 文章表
class Article(models.Model):
    title = models.CharField(max_length=32, unique=True, error_messages={"unique": "文章標題不能重複"})
    # 文章發佈時間
    # auto_now每次更新的時候會把當前時間保存
    create_time = models.DateField(auto_now_add=True)
    # auto_now_add 第一次建立的時候把當前時間保存
    update_time = models.DateField(auto_now=True)
    # 文章的類型
    type = models.SmallIntegerField(
        choices=((1, "原創"), (2, "轉載")),
        default=1
    )
    # 來源
    school = models.ForeignKey(to='School', on_delete=models.CASCADE)
    # 標籤
    tag = models.ManyToManyField(to='Tag')


# 文章來源表
class School(models.Model):
    name = models.CharField(max_length=16)


# 文章標籤表
class Tag(models.Model):
    name = models.CharField(max_length=16)


# 評論表
class Comment(models.Model):
    content = models.CharField(max_length=128)
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE)


# 用戶信息表
class UserInfo(models.Model):
    username = models.CharField(max_length=16, unique=True)
    password = models.CharField(max_length=32)

    type = models.SmallIntegerField(
        choices=((1, '普通用戶'), (2, 'VIP用戶')),
        default=1
    )


# token
class Token(models.Model):
    token = models.CharField(max_length=128)
    user = models.OneToOneField(to='UserInfo')

token單獨分一個表,是由於它是在原有用戶表的功能擴展。不能對一個表無限的增長字段,不然會致使表原來越臃腫

在先後端分離的架構中,前端使用ajax請求發送給後端,它不能使用cookie/session。那麼後端怎麼知道這個用戶是否登陸了,是不是VIP用戶呢?使用token就能夠解決這個問題!

 

使用2個命令生成表。

makemigrations 將models.py的變動作記錄
migrate 將變動記錄轉換爲sql語句,並執行

python manage.py makemigrations
python manage.py migrate

增長2條記錄,使用navicast軟件打開sqlite數據庫,執行如下sql

INSERT INTO app01_userinfo ("id", "username", "password", "type") VALUES (1, 'zhang', 123, 1);
INSERT INTO app01_userinfo ("id", "username", "password", "type") VALUES (2, 'wang', 123, 2);

app01_token表用來存放token的,它永久的身份令牌。在服務器自動生成的!

 

視圖

修改views.py,完整代碼以下:

from django.shortcuts import render, HttpResponse
from app01 import models
from app01 import app01_serializers # 導入自定義的序列化
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
# Create your views here.

# 生成Token的函數
def get_token_code(username):
"""
根據用戶名和時間戳生成用戶登錄成功的隨機字符串
:param username: 字符串格式的用戶名
:return: 字符串格式的Token
"""
import time
import hashlib
timestamp = str(time.time()) # 當前時間戳
m = hashlib.md5(bytes(username, encoding='utf8'))
m.update(bytes(timestamp, encoding='utf8')) # update必須接收一個bytes
return m.hexdigest()


# 登錄視圖
class LoginView(APIView):
"""
登錄檢測視圖
1. 接收用戶發過來(POST)的用戶名和密碼數據
2. 校驗用戶名密碼是否正確
- 成功就返回登錄成功(發Token)
- 失敗就返回錯誤提示
"""

def post(self, request): # POST請求
res = {"code": 0}
# 從post裏面取數據
username = request.data.get("username")
password = request.data.get("password")
# 去數據庫查詢
user_obj = models.UserInfo.objects.filter(
username=username,
password=password,
).first()
if user_obj:
# 登錄成功
# 生成Token
token = get_token_code(username)
# 將token保存
# 用user=user_obj這個條件去Token表裏查詢
# 若是有記錄就更新defaults裏傳的參數, 沒有記錄就用defaults裏傳的參數建立一條數據
models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
# 將token返回給用戶
res["token"] = token
else:
# 登陸失敗
res["code"] = 1
res["error"] = '用戶名或密碼錯誤'
return Response(res)


class CommentViewSet(ModelViewSet):
queryset = models.Comment.objects.all()
serializer_class = app01_serializers.CommentSerializer

複製代碼
from django.shortcuts import render, HttpResponse
from app01 import models
from app01 import app01_serializers  # 導入自定義的序列化
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
# Create your views here.

# 生成Token的函數
def get_token_code(username):
    """
    根據用戶名和時間戳生成用戶登錄成功的隨機字符串
    :param username: 字符串格式的用戶名
    :return: 字符串格式的Token
    """
    import time
    import hashlib
    timestamp = str(time.time())  # 當前時間戳
    m = hashlib.md5(bytes(username, encoding='utf8'))
    m.update(bytes(timestamp, encoding='utf8'))  # update必須接收一個bytes
    return m.hexdigest()


# 登錄視圖
class LoginView(APIView):
    """
    登錄檢測視圖
    1. 接收用戶發過來(POST)的用戶名和密碼數據
    2. 校驗用戶名密碼是否正確
        - 成功就返回登錄成功(發Token)
        - 失敗就返回錯誤提示
    """

    def post(self, request):  # POST請求
        res = {"code": 0}
        # 從post裏面取數據
        username = request.data.get("username")
        password = request.data.get("password")
        # 去數據庫查詢
        user_obj = models.UserInfo.objects.filter(
            username=username,
            password=password,
        ).first()
        if user_obj:
            # 登錄成功
            # 生成Token
            token = get_token_code(username)
            # 將token保存
            # 用user=user_obj這個條件去Token表裏查詢
            # 若是有記錄就更新defaults裏傳的參數, 沒有記錄就用defaults裏傳的參數建立一條數據
            models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
            # 將token返回給用戶
            res["token"] = token
        else:
            # 登陸失敗
            res["code"] = 1
            res["error"] = '用戶名或密碼錯誤'
        return Response(res)


class CommentViewSet(ModelViewSet):
    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
複製代碼

路由

修改app01_urls.py,刪除多餘的代碼

from django.conf.urls import url
from app01 import views

urlpatterns = [
url(r'login/$', views.LoginView.as_view()),
]

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
# 註冊路由,表示路徑comment對應視圖函數CommentViewSet
router.register(r'comment', views.CommentViewSet)
urlpatterns += router.urls

複製代碼
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'login/$', views.LoginView.as_view()),
]

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
# 註冊路由,表示路徑comment對應視圖函數CommentViewSet
router.register(r'comment', views.CommentViewSet)
urlpatterns += router.urls
複製代碼

使用postman發送post登陸

查看返回結果,code爲0表示登陸成功,並返回一個token

查看錶app01_token,就會多一條記錄

 

postman訪問評論,它是能夠任意訪問的

 

DRF認證源碼流程

DRF認證源碼流程,請參考連接:

http://www.javashuo.com/article/p-cayevrye-x.html  (後半段沒有寫)

http://www.javashuo.com/article/p-ahuarxov-t.html  (後半段寫了)

 

執行流程圖解

圖片來源: http://www.javashuo.com/article/p-vxyqruvw-x.html

 

定義一個認證類

如今有一個需求,只有登陸的用戶,才能對評論作修改

在app01(應用名)目錄下建立目錄utils,在此目錄下建立auth.py

"""
自定義的認證類都放在這裏
"""
from rest_framework.authentication import BaseAuthentication
from app01 import models
from rest_framework.exceptions import AuthenticationFailed


class MyAuth(BaseAuthentication):

    def authenticate(self, request):  # 必需要實現此方法
        if request.method in ['POST', 'PUT', 'DELETE']:
            token = request.data.get("token")
            # 去數據庫查詢有沒有這個token
            token_obj = models.Token.objects.filter(token=token).first()
            if token_obj:
                # token_obj有2個屬性,詳見models.py中的Token。
                # return後面的代碼,至關於分別賦值。例如a=1,b=2等同於a,b=1,2
                # return多個值,返回一個元組
                #在rest framework內部會將這兩個字段賦值給request,以供後續操做使用
                return token_obj.user, token  # self.user, self.token = token_obj.user, token
            else:
                raise AuthenticationFailed('無效的token')
        else:
            return None, None

視圖級別認證

修改views.py,完整代碼以下:

from django.shortcuts import render, HttpResponse
from app01 import models
from app01 import app01_serializers # 導入自定義的序列化
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.utils.auth import MyAuth # app01.utils.auth表示app01目錄下的utils下的auth.py

# Create your views here.

# 生成Token的函數
def get_token_code(username):
"""
根據用戶名和時間戳生成用戶登錄成功的隨機字符串
:param username: 字符串格式的用戶名
:return: 字符串格式的Token
"""
import time
import hashlib
timestamp = str(time.time()) # 當前時間戳
m = hashlib.md5(bytes(username, encoding='utf8'))
m.update(bytes(timestamp, encoding='utf8')) # update必須接收一個bytes
return m.hexdigest()


# 登錄視圖
class LoginView(APIView):
"""
登錄檢測視圖
1. 接收用戶發過來(POST)的用戶名和密碼數據
2. 校驗用戶名密碼是否正確
- 成功就返回登錄成功(發Token)
- 失敗就返回錯誤提示
"""

def post(self, request): # POST請求
res = {"code": 0}
# 從post裏面取數據
username = request.data.get("username")
password = request.data.get("password")
# 去數據庫查詢
user_obj = models.UserInfo.objects.filter(
username=username,
password=password,
).first()
if user_obj:
# 登錄成功
# 生成Token
token = get_token_code(username)
# 將token保存
# 用user=user_obj這個條件去Token表裏查詢
# 若是有記錄就更新defaults裏傳的參數, 沒有記錄就用defaults裏傳的參數建立一條數據
models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
# 將token返回給用戶
res["token"] = token
else:
# 登陸失敗
res["code"] = 1
res["error"] = '用戶名或密碼錯誤'
return Response(res)


class CommentViewSet(ModelViewSet):
queryset = models.Comment.objects.all()
serializer_class = app01_serializers.CommentSerializer
authentication_classes = [MyAuth, ] # 局部使用認證方法MyAuth

複製代碼
from django.shortcuts import render, HttpResponse
from app01 import models
from app01 import app01_serializers  # 導入自定義的序列化
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.utils.auth import MyAuth  # app01.utils.auth表示app01目錄下的utils下的auth.py

# Create your views here.

# 生成Token的函數
def get_token_code(username):
    """
    根據用戶名和時間戳生成用戶登錄成功的隨機字符串
    :param username: 字符串格式的用戶名
    :return: 字符串格式的Token
    """
    import time
    import hashlib
    timestamp = str(time.time())  # 當前時間戳
    m = hashlib.md5(bytes(username, encoding='utf8'))
    m.update(bytes(timestamp, encoding='utf8'))  # update必須接收一個bytes
    return m.hexdigest()


# 登錄視圖
class LoginView(APIView):
    """
    登錄檢測視圖
    1. 接收用戶發過來(POST)的用戶名和密碼數據
    2. 校驗用戶名密碼是否正確
        - 成功就返回登錄成功(發Token)
        - 失敗就返回錯誤提示
    """

    def post(self, request):  # POST請求
        res = {"code": 0}
        # 從post裏面取數據
        username = request.data.get("username")
        password = request.data.get("password")
        # 去數據庫查詢
        user_obj = models.UserInfo.objects.filter(
            username=username,
            password=password,
        ).first()
        if user_obj:
            # 登錄成功
            # 生成Token
            token = get_token_code(username)
            # 將token保存
            # 用user=user_obj這個條件去Token表裏查詢
            # 若是有記錄就更新defaults裏傳的參數, 沒有記錄就用defaults裏傳的參數建立一條數據
            models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
            # 將token返回給用戶
            res["token"] = token
        else:
            # 登陸失敗
            res["code"] = 1
            res["error"] = '用戶名或密碼錯誤'
        return Response(res)


class CommentViewSet(ModelViewSet):
    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    authentication_classes = [MyAuth, ]  # 局部使用認證方法MyAuth
複製代碼

 發送一個空的post請求,返回結果以下:

發送一個錯誤的token

返回結果:

發送正確的token

返回結果,出現如下信息,說明已經經過了認證

全局級別認證

要想讓每個視圖都要認證,能夠在settings.py中配置

REST_FRAMEWORK = {
    # 表示app01-->utils下的auth.py裏面的MyAuth類
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ]
}

修改views.py,註釋掉CommentViewSet中的authentication_classes

class CommentViewSet(ModelViewSet):
    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    # authentication_classes = [MyAuth, ]  # 局部使用認證方法MyAuth

再次測試上面的3種請求方式,效果同上!

2、DRF權限

權限源碼流程

請參考連接:

http://www.cnblogs.com/derek1184405959/p/8722212.html

 

舉例1

只有VIP用戶才能看的內容。

 

自定義一個權限類

has_permission

在目錄app01-->utils下面新建文件permission.py

"""
自定義的權限類
"""
from rest_framework.permissions import BasePermission


class MyPermission(BasePermission):
    def has_permission(self, request, view):
        """
        判斷該用戶有沒有權限
        """
        # 判斷用戶是否是VIP用戶
        # 若是是VIP用戶就返回True
        # 若是是普通用戶就返回False
        print('我要進行自定義的權限判斷啦....')
        print(request)
        print(request.user)
        return True

視圖級別配置

修改views.py,指定permission_classes

from django.shortcuts import render, HttpResponse
from app01 import models
from app01 import app01_serializers  # 導入自定義的序列化
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.utils.auth import MyAuth  # app01.utils.auth表示app01目錄下的utils下的auth.py
from app01.utils.permission import MyPermission

# Create your views here.

# 生成Token的函數
def get_token_code(username):
    """
    根據用戶名和時間戳生成用戶登錄成功的隨機字符串
    :param username: 字符串格式的用戶名
    :return: 字符串格式的Token
    """
    import time
    import hashlib
    timestamp = str(time.time())  # 當前時間戳
    m = hashlib.md5(bytes(username, encoding='utf8'))
    m.update(bytes(timestamp, encoding='utf8'))  # update必須接收一個bytes
    return m.hexdigest()


# 登錄視圖
class LoginView(APIView):
    """
    登錄檢測視圖
    1. 接收用戶發過來(POST)的用戶名和密碼數據
    2. 校驗用戶名密碼是否正確
        - 成功就返回登錄成功(發Token)
        - 失敗就返回錯誤提示
    """

    def post(self, request):  # POST請求
        res = {"code": 0}
        # 從post裏面取數據
        username = request.data.get("username")
        password = request.data.get("password")
        # 去數據庫查詢
        user_obj = models.UserInfo.objects.filter(
            username=username,
            password=password,
        ).first()
        if user_obj:
            # 登錄成功
            # 生成Token
            token = get_token_code(username)
            # 將token保存
            # 用user=user_obj這個條件去Token表裏查詢
            # 若是有記錄就更新defaults裏傳的參數, 沒有記錄就用defaults裏傳的參數建立一條數據
            models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
            # 將token返回給用戶
            res["token"] = token
        else:
            # 登陸失敗
            res["code"] = 1
            res["error"] = '用戶名或密碼錯誤'
        return Response(res)


class CommentViewSet(ModelViewSet):
    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    # authentication_classes = [MyAuth, ]  # 局部使用認證方法MyAuth
    permission_classes = [MyPermission, ]  # 局部使用權限方法

發送get請求

查看Pycharm控制檯輸出:

我要進行自定義的權限判斷啦....
<rest_framework.request.Request object at 0x000002576A780FD0>
None

發現用戶爲None

 

普通用戶

發送post請求,寫一個正確的token,用zhang用戶的token

查看Pycharm控制檯輸出:

我要進行自定義的權限判斷啦....
<rest_framework.request.Request object at 0x000002576A9852B0>
UserInfo object

此時獲得了一個用戶對象

修改permission.py,獲取用戶名以及用戶類型

"""
自定義的權限類
"""
from rest_framework.permissions import BasePermission


class MyPermission(BasePermission):
    def has_permission(self, request, view):
        """
        判斷該用戶有沒有權限
        """
        # 判斷用戶是否是VIP用戶
        # 若是是VIP用戶就返回True
        # 若是是普通用戶就返回False
        print('我要進行自定義的權限判斷啦....')
        print(request)
        print(request.user.username)
        print(request.user.type)
        return True

再次發送一樣的post請求,再次查看pycharm控制檯輸出

<rest_framework.request.Request object at 0x000001D893AC4048>
zhang
1

竟然獲得了zhang和1。爲何呢?爲何request.user.username就能獲得用戶名呢?

我來大概解釋一下,先打開這篇文章:

http://www.javashuo.com/article/p-ahuarxov-t.html

我引用裏面幾句話

Request有個user方法,加 @property 表示調用user方法的時候不須要加括號「user()」,能夠直接調用:request.user

在rest framework內部會將這兩個字段賦值給request,以供後續操做使用

return (token_obj.user,token_obj)

上面的return的值,來源於app01\utils\auth.py 裏面的MyAuth類中的return token_onj.user , token 簡單來講,經過認證以後,它會request進行再次封裝,因此調用request.user時,獲得了一個對象,這個對象就是執行models.Token.objects.filter(token=token).first()結果

若是ORM沒有查詢出結果,它就是一個匿名用戶!

 

修改permission.py ,若是VIP返回True,不然返回False

"""
自定義的權限類
"""
from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):
    def has_permission(self, request, view):
        """
        判斷用戶有沒有權限
        :param request:
        :param view:
        :return:
        """
        #判斷用戶是否是VIP用戶
        #若是是VIP用戶就返回True
        #若是是普通用戶就返回False
        print('我要進行自定義的權限判斷....')
        print(request.user.username)
        print(request.user.type)
        
        if request.user.type == 2:  #是VIP用戶
            return True
        else:
            return False

 修改settings.py,關閉全局級別認證

 

REST_FRAMEWORK = {
    # 表示app01-->utils下的auth.py裏面的MyAuth類
    # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ]
}
REST_FRAMEWORK = {
    # 表示app01-->utils下的auth.py裏面的MyAuth類
    # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ]
}

 

使用VIP用戶wang登陸

使用VIP用戶wang登陸

查看返回結果,它返回wang的token

查看錶app01_token,它如今有2個記錄了

 

複製zhang的token,發送一條評論

查看返回結果,提示您沒有執行此操做的權限

英文看不懂,不要緊,定義成中文就好了

修改permission.py,定義message

"""
自定義的權限類
"""
from rest_framework.permissions import BasePermission


class MyPermission(BasePermission):
    message = '您沒有執行此操做的權限!'
    def has_permission(self, request, view):
        """
        判斷該用戶有沒有權限
        """
        # 判斷用戶是否是VIP用戶
        # 若是是VIP用戶就返回True
        # 若是是普通用戶就返回False
        print('我要進行自定義的權限判斷啦....')
        # print(request)
        print(request.user.username)
        print(request.user.type)
        if request.user.type == 2:  # 是VIP用戶
            return True
        else:
            return False

VIP用戶

將token改爲VIP用戶測試

查看返回結果

 

修改permission.py,定義發送類型

修改permission.py,定義發送類型

"""
自定義的權限類
"""
from rest_framework.permissions import BasePermission


class MyPermission(BasePermission):
message = '您沒有執行此操做的權限!'
def has_permission(self, request, view):
"""
判斷該用戶有沒有權限
"""
# 判斷用戶是否是VIP用戶
# 若是是VIP用戶就返回True
# 若是是普通用戶就返回False
print('我要進行自定義的權限判斷啦....')

if request.method in ['POST', 'PUT', 'DELETE']:
print(request.user.username)
print(request.user.type)
if request.user.type == 2: # 是VIP用戶
return True
else:
return False
else:
return True

複製代碼
"""
自定義的權限類
"""
from rest_framework.permissions import BasePermission


class MyPermission(BasePermission):
    message = '您沒有執行此操做的權限!'
    def has_permission(self, request, view):
        """
        判斷該用戶有沒有權限
        """
        # 判斷用戶是否是VIP用戶
        # 若是是VIP用戶就返回True
        # 若是是普通用戶就返回False
        print('我要進行自定義的權限判斷啦....')

        if request.method in ['POST', 'PUT', 'DELETE']:
            print(request.user.username)
            print(request.user.type)
            if request.user.type == 2:  # 是VIP用戶
                return True
            else:
                return False
        else:
            return True
複製代碼

發送正確的值

查看返回結果

查看錶app01_comment記錄

 

全局級別設置

修改settings.py,增長一行

REST_FRAMEWORK = {
    # 表示app01-->utils下的auth.py裏面的MyAuth類
    # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ]
    "DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.MyPermission", ]
}

修改views.py,註釋局部的

from django.shortcuts import render, HttpResponse
from app01 import models
from app01 import app01_serializers  # 導入自定義的序列化
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.utils.auth import MyAuth  # app01.utils.auth表示app01目錄下的utils下的auth.py
from app01.utils.permission import MyPermission

# Create your views here.

# 生成Token的函數
def get_token_code(username):
    """
    根據用戶名和時間戳生成用戶登錄成功的隨機字符串
    :param username: 字符串格式的用戶名
    :return: 字符串格式的Token
    """
    import time
    import hashlib
    timestamp = str(time.time())  # 當前時間戳
    m = hashlib.md5(bytes(username, encoding='utf8'))
    m.update(bytes(timestamp, encoding='utf8'))  # update必須接收一個bytes
    return m.hexdigest()


# 登錄視圖
class LoginView(APIView):
    """
    登錄檢測視圖
    1. 接收用戶發過來(POST)的用戶名和密碼數據
    2. 校驗用戶名密碼是否正確
        - 成功就返回登錄成功(發Token)
        - 失敗就返回錯誤提示
    """

    def post(self, request):  # POST請求
        res = {"code": 0}
        # 從post裏面取數據
        username = request.data.get("username")
        password = request.data.get("password")
        # 去數據庫查詢
        user_obj = models.UserInfo.objects.filter(
            username=username,
            password=password,
        ).first()
        if user_obj:
            # 登錄成功
            # 生成Token
            token = get_token_code(username)
            # 將token保存
            # 用user=user_obj這個條件去Token表裏查詢
            # 若是有記錄就更新defaults裏傳的參數, 沒有記錄就用defaults裏傳的參數建立一條數據
            models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
            # 將token返回給用戶
            res["token"] = token
        else:
            # 登陸失敗
            res["code"] = 1
            res["error"] = '用戶名或密碼錯誤'
        return Response(res)


class CommentViewSet(ModelViewSet):
    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    authentication_classes = [MyAuth, ]  # 局部使用認證方法MyAuth
    # permission_classes = [MyPermission, ]  # 局部使用權限方法

 

驗證

使用普通用戶測試

 查看返回結果

 

舉例2

只要評論的做者是本身,就能夠刪除,不然不行!

只要評論的做者是本身,就能夠刪除,不然不行!

表結構

修改models.py,在評論表中,增長一個字段user

class Comment(models.Model):
    content = models.CharField(max_length=128)
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
    user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)
class Comment(models.Model):
    content = models.CharField(max_length=128)
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
    user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)

使用2個命令生成表。

python manage.py makemigrations
python manage.py migrate

修改表,增長2個user_id

has_object_permission

修改permission.py,增長has_object_permission

它比上面的has_permission方法多了一個obj
它是操做的對象,好比評論對象

"""
自定義的權限類
"""
from rest_framework.permissions import BasePermission


class MyPermission(BasePermission):
message = '您沒有執行此操做的權限!'
def has_permission(self, request, view):
"""
判斷該用戶有沒有權限
"""
# 判斷用戶是否是VIP用戶
# 若是是VIP用戶就返回True
# 若是是普通用戶就返回False
print('我要進行自定義的權限判斷啦....')
return True
# if request.method in ['POST', 'PUT', 'DELETE']:
# print(request.user.username)
# print(request.user.type)
# if request.user.type == 2: # 是VIP用戶
# return True
# else:
# return False
# else:
# return True

def has_object_permission(self, request, view, obj):
"""
判斷當前評論用戶的做者是否是你當前的用戶
只有評論的做者才能刪除本身的評論
"""
print('這是在自定義權限類中的has_object_permission')
print(obj.id)
if request.method in ['PUT', 'DELETE']:
if obj.user == request.user:
# 當前要刪除的評論的做者就是當前登錄的用戶
return True
else:
return False
else:
return True

複製代碼
"""
自定義的權限類
"""
from rest_framework.permissions import BasePermission


class MyPermission(BasePermission):
    message = '您沒有執行此操做的權限!'
    def has_permission(self, request, view):
        """
        判斷該用戶有沒有權限
        """
        # 判斷用戶是否是VIP用戶
        # 若是是VIP用戶就返回True
        # 若是是普通用戶就返回False
        print('我要進行自定義的權限判斷啦....')
        return True
        # if request.method in ['POST', 'PUT', 'DELETE']:
        #     print(request.user.username)
        #     print(request.user.type)
        #     if request.user.type == 2:  # 是VIP用戶
        #         return True
        #     else:
        #         return False
        # else:
        #     return True

    def has_object_permission(self, request, view, obj):
        """
        判斷當前評論用戶的做者是否是你當前的用戶
        只有評論的做者才能刪除本身的評論
        """
        print('這是在自定義權限類中的has_object_permission')
        print(obj.id)
        if request.method in ['PUT', 'DELETE']:
            if obj.user == request.user:
                # 當前要刪除的評論的做者就是當前登錄的用戶
                return True
            else:
                return False
        else:
            return True
複製代碼

 使用普通用戶的token發送delete類型的請求

查看返回結果 

 使用VIP用戶的token發送

 查看返回結果,爲空,表示刪除成功

 

 查看錶app01_comment,發現少了一條記錄

 

4、DRF限制

限制也稱之爲節流

DRF節流源碼分析

請參考連接:

http://www.cnblogs.com/derek1184405959/p/8722638.html

自定義限制類

對IP作限制,60秒只能訪問3次

 

在about_drf\app01\utils下面建立throttle.py

"""
自定義的訪問限制類
"""
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
import time

D = {} # {'127.0.0.1': [1533302442, 1533302439,...]}


class MyThrottle(BaseThrottle):

def allow_request(self, request, view):
"""
返回True就放行,返回False表示被限制了...
"""
# 1. 獲取當前訪問的IP
ip = request.META.get("REMOTE_ADDR")
print('這是自定義限制類中的allow_request')
print(ip)
# 2. 獲取當前的時間
now = time.time()
# 判斷當前ip是否有訪問記錄
if ip not in D:
D[ip] = [] # 初始化一個空的訪問歷史列表
# 高端騷操做
history = D[ip]
while history and now - history[-1] > 10:
history.pop()
# 判斷最近一分鐘的訪問次數是否超過了閾值(3次)
if len(history) >= 3:
return False
else:
# 把這一次的訪問時間加到訪問歷史列表的第一位
D[ip].insert(0, now)
return True

複製代碼
"""
自定義的訪問限制類
"""
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
import time

D = {}  # {'127.0.0.1': [1533302442, 1533302439,...]}


class MyThrottle(BaseThrottle):

    def allow_request(self, request, view):
        """
        返回True就放行,返回False表示被限制了...
        """
        # 1. 獲取當前訪問的IP
        ip = request.META.get("REMOTE_ADDR")
        print('這是自定義限制類中的allow_request')
        print(ip)
        # 2. 獲取當前的時間
        now = time.time()
        # 判斷當前ip是否有訪問記錄
        if ip not in D:
            D[ip] = []  # 初始化一個空的訪問歷史列表
        # 高端騷操做
        history = D[ip]
        while history and now - history[-1] > 10:
            history.pop()
        # 判斷最近一分鐘的訪問次數是否超過了閾值(3次)
        if len(history) >= 3:
            return False
        else:
            # 把這一次的訪問時間加到訪問歷史列表的第一位
            D[ip].insert(0, now)
            return True
複製代碼

代碼解釋:

request.META.get("REMOTE_ADDR")  獲取遠程IP

D  存儲的值,相似於

"192.168.1.2":["17:06:45","12:04:03","12:04:01"]

最後一個元素,就是最早開始的時間

for循環列表,不能對列表作更改操做!因此使用while循環

while history and now - history[-1] > 10:
    history.pop()

history是歷史列表,history[-1] 表示列表最後一個元素

history and now - history[-1] > 10 表示當歷史列表中有元素,而且當前時間戳減去最後一個元素的時間戳大於10的時候,執行history.pop(),表示刪除最後一個元素

當歷史列表爲空時,或者小於差值小於10的時候,結束循環。

 

視圖使用

修改views.py

修改views.py

from django.shortcuts import render, HttpResponse
from app01 import models
from app01 import app01_serializers # 導入自定義的序列化
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.utils.auth import MyAuth # app01.utils.auth表示app01目錄下的utils下的auth.py
from app01.utils.permission import MyPermission
from app01.utils.throttle import MyThrottle

# Create your views here.

# 生成Token的函數
def get_token_code(username):
"""
根據用戶名和時間戳生成用戶登錄成功的隨機字符串
:param username: 字符串格式的用戶名
:return: 字符串格式的Token
"""
import time
import hashlib
timestamp = str(time.time()) # 當前時間戳
m = hashlib.md5(bytes(username, encoding='utf8'))
m.update(bytes(timestamp, encoding='utf8')) # update必須接收一個bytes
return m.hexdigest()


# 登錄視圖
class LoginView(APIView):
"""
登錄檢測視圖
1. 接收用戶發過來(POST)的用戶名和密碼數據
2. 校驗用戶名密碼是否正確
- 成功就返回登錄成功(發Token)
- 失敗就返回錯誤提示
"""

def post(self, request): # POST請求
res = {"code": 0}
# 從post裏面取數據
username = request.data.get("username")
password = request.data.get("password")
# 去數據庫查詢
user_obj = models.UserInfo.objects.filter(
username=username,
password=password,
).first()
if user_obj:
# 登錄成功
# 生成Token
token = get_token_code(username)
# 將token保存
# 用user=user_obj這個條件去Token表裏查詢
# 若是有記錄就更新defaults裏傳的參數, 沒有記錄就用defaults裏傳的參數建立一條數據
models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
# 將token返回給用戶
res["token"] = token
else:
# 登陸失敗
res["code"] = 1
res["error"] = '用戶名或密碼錯誤'
return Response(res)


class CommentViewSet(ModelViewSet):
queryset = models.Comment.objects.all()
serializer_class = app01_serializers.CommentSerializer
authentication_classes = [MyAuth, ] # 局部使用認證方法MyAuth
# permission_classes = [MyPermission, ] # 局部使用權限方法
throttle_classes = [MyThrottle, ] # 局部使用限制方法

複製代碼
from django.shortcuts import render, HttpResponse
from app01 import models
from app01 import app01_serializers  # 導入自定義的序列化
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.utils.auth import MyAuth  # app01.utils.auth表示app01目錄下的utils下的auth.py
from app01.utils.permission import MyPermission
from app01.utils.throttle import MyThrottle

# Create your views here.

# 生成Token的函數
def get_token_code(username):
    """
    根據用戶名和時間戳生成用戶登錄成功的隨機字符串
    :param username: 字符串格式的用戶名
    :return: 字符串格式的Token
    """
    import time
    import hashlib
    timestamp = str(time.time())  # 當前時間戳
    m = hashlib.md5(bytes(username, encoding='utf8'))
    m.update(bytes(timestamp, encoding='utf8'))  # update必須接收一個bytes
    return m.hexdigest()


# 登錄視圖
class LoginView(APIView):
    """
    登錄檢測視圖
    1. 接收用戶發過來(POST)的用戶名和密碼數據
    2. 校驗用戶名密碼是否正確
        - 成功就返回登錄成功(發Token)
        - 失敗就返回錯誤提示
    """

    def post(self, request):  # POST請求
        res = {"code": 0}
        # 從post裏面取數據
        username = request.data.get("username")
        password = request.data.get("password")
        # 去數據庫查詢
        user_obj = models.UserInfo.objects.filter(
            username=username,
            password=password,
        ).first()
        if user_obj:
            # 登錄成功
            # 生成Token
            token = get_token_code(username)
            # 將token保存
            # 用user=user_obj這個條件去Token表裏查詢
            # 若是有記錄就更新defaults裏傳的參數, 沒有記錄就用defaults裏傳的參數建立一條數據
            models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
            # 將token返回給用戶
            res["token"] = token
        else:
            # 登陸失敗
            res["code"] = 1
            res["error"] = '用戶名或密碼錯誤'
        return Response(res)


class CommentViewSet(ModelViewSet):
    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    authentication_classes = [MyAuth, ]  # 局部使用認證方法MyAuth
    # permission_classes = [MyPermission, ]  # 局部使用權限方法
    throttle_classes = [MyThrottle, ]  # 局部使用限制方法
複製代碼

使用postman發送GET請求

瘋狂的點擊SEND按鈕,多發送幾回

提示請求達到了限制

 等待十幾秒,就能夠訪問了

全局使用

修改settings.py

REST_FRAMEWORK = {
    # 表示app01-->utils下的auth.py裏面的MyAuth類
    # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ],
    #"DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.MyPermission", ],
    "DEFAULT_THROTTLE_CLASSES": ["app01.utils.throttle.MyThrottle", ]
}
複製代碼
REST_FRAMEWORK = {
    # 表示app01-->utils下的auth.py裏面的MyAuth類
    # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ],
    #"DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.MyPermission", ],
    "DEFAULT_THROTTLE_CLASSES": ["app01.utils.throttle.MyThrottle", ]
}
複製代碼

修改views.py,註釋掉代碼

class CommentViewSet(ModelViewSet):
    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    authentication_classes = [MyAuth, ]  # 局部使用認證方法MyAuth
    permission_classes = [MyPermission, ]  # 局部使用權限方法
    # throttle_classes = [MyThrottle, ]  # 局部使用限制方法
複製代碼
class CommentViewSet(ModelViewSet):
    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    authentication_classes = [MyAuth, ]  # 局部使用認證方法MyAuth
    permission_classes = [MyPermission, ]  # 局部使用權限方法
    # throttle_classes = [MyThrottle, ]  # 局部使用限制方法
複製代碼

再次測試,效果同上!

 

使用內置限制類

修改about_drf\app01\utils\throttle.py

"""
自定義的訪問限制類
"""
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
# import time
#
# D = {} # {'127.0.0.1': [1533302442, 1533302439,...]}
#
#
# class MyThrottle(BaseThrottle):
#
# def allow_request(self, request, view):
#
# """
# 返回True就放行,返回False表示被限制了...
# """
# # 1. 獲取當前訪問的IP
# ip = request.META.get("REMOTE_ADDR")
# print('這是自定義限制類中的allow_request')
# print(ip)
# # 2. 獲取當前的時間
# now = time.time()
# # 判斷當前ip是否有訪問記錄
# if ip not in D:
# D[ip] = [] # 初始化一個空的訪問歷史列表
# # 高端騷操做
# history = D[ip]
# while history and now - history[-1] > 10:
# history.pop()
# # 判斷最近一分鐘的訪問次數是否超過了閾值(3次)
# if len(history) >= 3:
# return False
# else:
# # 把這一次的訪問時間加到訪問歷史列表的第一位
# D[ip].insert(0, now)
# return True

class MyThrottle(SimpleRateThrottle):

scope = "rate" # rate是名字,能夠隨便定義!

def get_cache_key(self, request, view):
return self.get_ident(request)

複製代碼
"""
自定義的訪問限制類
"""
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
# import time
#
# D = {}  # {'127.0.0.1': [1533302442, 1533302439,...]}
#
#
# class MyThrottle(BaseThrottle):
#
#     def allow_request(self, request, view):
#
#         """
#         返回True就放行,返回False表示被限制了...
#         """
#         # 1. 獲取當前訪問的IP
#         ip = request.META.get("REMOTE_ADDR")
#         print('這是自定義限制類中的allow_request')
#         print(ip)
#         # 2. 獲取當前的時間
#         now = time.time()
#         # 判斷當前ip是否有訪問記錄
#         if ip not in D:
#             D[ip] = []  # 初始化一個空的訪問歷史列表
#         # 高端騷操做
#         history = D[ip]
#         while history and now - history[-1] > 10:
#             history.pop()
#         # 判斷最近一分鐘的訪問次數是否超過了閾值(3次)
#         if len(history) >= 3:
#             return False
#         else:
#             # 把這一次的訪問時間加到訪問歷史列表的第一位
#             D[ip].insert(0, now)
#             return True

class MyThrottle(SimpleRateThrottle):

    scope = "rate"  # rate是名字,能夠隨便定義!

    def get_cache_key(self, request, view):
        return self.get_ident(request)
複製代碼

注意:scope是關鍵字參數

get_cache_key 的名字不能變更

self.get_ident(request)  表示遠程IP地址

 

全局配置

修改settings.py

複製代碼
REST_FRAMEWORK = {
    # 表示app01-->utils下的auth.py裏面的MyAuth類
    # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ]
    "DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.MyPermission", ],
    "DEFAULT_THROTTLE_CLASSES": ["app01.utils.throttle.MyThrottle", ],
    "DEFAULT_THROTTLE_RATES": {
        "rate": "3/m",
    }
}
複製代碼

注意:rate對應的是throttle.py裏面MyThrottle定義的scope屬性的值

3/m 表示1分鐘3次

 

再次測試,效果以下:

它還會返回倒計時的時間!

相關文章
相關標籤/搜索