python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流) 昨日内容回顾 一、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 = ...
View Code

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)登录

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

2)业务请求

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

关于token的详细信息,请参考链接:

https://blog.****.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)
    user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)


# 用户信息表
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',on_delete=models.CASCADE)
View Code

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
View Code

路由

修改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
View Code

使用postman发送post登录

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看返回结果,code为0表示登录成功,并返回一个token

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看表app01_token,就会多一条记录

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

postman访问评论,它是可以任意访问的

 python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

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

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

定义一个认证类

现在有一个需求,只有登录的用户,才能对评论做修改

在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
View Code

视图级别认证

修改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
View Code

 发送一个空的post请求,返回结果如下:

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

发送一个错误的token

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

返回结果:

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

注意:这个信息是由raise AuthenticationFailed('无效的token')触发的。

如果想在MyAuth类-->authenticate方法-->代码else中触发别的信息,也同样需要定义raise

发送正确的token

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

返回结果,出现以下信息,说明已经通过了认证

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

全局级别认证

要想让每一个视图都要认证,可以在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
View Code

再次测试上面的3种请求方式,效果同上!

三、DRF权限

权限源码流程

请参考链接:

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

举例1

只有VIP用户才能看的内容。

自定义一个权限类

has_permission

注意:当返回一个对象时,才会触发

什么对象呢?json对象!why?

在CommentViewSet视图中,它会返回一个json数据

http://127.0.0.1:8000/api/comment/  它会返回一个json列表

http://127.0.0.1:8000/api/comment/1 它会返回一个json对象。

当使用了权限类后,类中有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
View Code

视图级别配置

修改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, ]  # 局部使用权限方法
View Code

发送get请求

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看Pycharm控制台输出:

我要进行自定义的权限判断啦....
<rest_framework.request.Request object at 0x000002576A780FD0>
None

发现用户为None,它没有触发has_permission

访问单个评论,返回单个json对象

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看Pycharm控制台输出:

我要进行自定义的权限判断啦....
<rest_framework.request.Request object at 0x000002576A780FD0>
这是在自定义权限类中的has_object_permission
1

它触发了has_permission,并输出了一段话

注意:json对象中,它增加一个属性user,值为null。为什么会增加呢?

因为在源码中,Request有个user方法,加 @property。它对返回结果做了在再次封装!

详情,请参考上面的权限源码流程

普通用户

发送post请求,写一个正确的token,用zhang用户的token

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看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
View Code

再次发送同样的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的值,来源于app01utilsauth.py里面的MyAuth类中的return token_obj.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):
        """
        判断该用户有没有权限
        """
        # 判断用户是不是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
View Code

修改settings.py,关闭全局级别认证

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

使用VIP用户wang登录

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

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

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看表app01_token,它现在有2个记录了

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

复制zhang的token,发送一条评论

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看返回结果,提示您没有执行此操作的权限

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

英文看不懂,没关系,定义成中文就行了

修改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
View Code

再次发送,返回结果如下:

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

VIP用户

将token改成VIP用户测试

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看返回结果

 python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

修改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
View Code

发送正确的值

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看返回结果

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看表app01_comment记录

 python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

全局级别设置

修改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, ]  # 局部使用权限方法
View Code

验证

使用普通用户测试

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

 查看返回结果

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

举例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)
View Code

使用2个命令生成表。

python manage.py makemigrations
python manage.py migrate

修改表,增加2个user_id

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

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
View Code

 使用普通用户的token发送delete类型的请求

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

查看返回结果 

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

 使用VIP用户的token发送

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

 查看返回结果,为空,表示删除成功

 python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

 查看表app01_comment,发现少了一条记录

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

四、DRF节流

节流也称之为限制

DRF节流源码分析

请参考链接:

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

自定义限制类

对IP做限制,60秒只能访问3次

在about_drfapp01utils下面创建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
View Code

代码解释:

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

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, ]  # 局部使用限制方法
View Code

使用postman发送GET请求

疯狂的点击SEND按钮,多发送几次

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

提示请求达到了限制

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

 等待十几秒,就可以访问了

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

全局使用

修改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", ]
}
View Code

修改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, ]  # 局部使用限制方法
View Code

再次测试,效果同上!

使用内置限制类

修改about_drfapp01utils hrottle.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)
View Code

注意: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次

再次测试,效果如下:

python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾
一、Token 认证的来龙去脉
二、DRF 认证
三、DRF权限
四、DRF节流

它还会返回倒计时的时间!