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}還是得需要使用APIView

一、Token 認證的來龍去脈

摘要

Token 是在服務端產生的。如果前端使用用戶名/密碼向服務端請求認證,服務端認證成功,那么在服務端會返回 Token 給前端。前端可以在每次請求的時候帶上 Token 證明自己的合法地位

為什么要用 Token?

而要回答這個問題很簡單——因為它能解決問題!

可以解決哪些問題呢?

  1. Token 完全由應用管理,所以它可以避開同源策略

  2. Token 可以避免 CSRF 攻擊

  3. Token 可以是無狀態的,可以在多個服務間共享

Token 是在服務端產生的。如果前端使用用戶名/密碼向服務端請求認證,服務端認證成功,那么在服務端會返回 Token 給前端。前端可以在每次請求的時候帶上 Token 證明自己的合法地位。如果這個 Token 在服務端持久化(比如存入數據庫),那它就是一個永久的身份令牌。

時序圖表示

使用 Token 的時序圖如下:

1)登錄

2)業務請求

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

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

 

二、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認證源碼流程,請參考鏈接:

https://www.cnblogs.com/haiyan123/p/8419872.html  (后半段沒有寫)

https://www.cnblogs.com/derek1184405959/p/8712206.html  (后半段寫了)

 

執行流程圖解

圖片來源: https://www.cnblogs.com/renpingsheng/p/7897192.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種請求方式,效果同上!

二、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就能得到用戶名呢?

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

https://www.cnblogs.com/derek1184405959/p/8712206.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,發現少了一條記錄

 

四、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次

 

再次測試,效果如下:

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

  

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM