• 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的值,来源于app01utilsauth.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_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

    复制代码
    """
    自定义的访问限制类
    """
    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_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)

    复制代码
    """
    自定义的访问限制类
    """
    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次

    再次测试,效果如下:

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

      

      

      

  • 相关阅读:
    js 去掉文本域中的空格
    网站开发步骤
    获取客户端、服务器、本地IP地址
    c#用反射原理递归遍历复杂实体对象
    jquery1.8在ie8下not无效?
    状态模式
    虚函数和抽象函数的区别
    HashTable、HashSet和Dictionary的区别
    sql视图学习笔记--视图
    html背景为灰色 不能操作,中间div可以操作
  • 原文地址:https://www.cnblogs.com/haowen980/p/9416531.html
Copyright © 2020-2023  润新知