昨日内容回顾

  1. 1. 五个葫芦娃和三行代码
  2. APIView(views.View)
  3. 1. 封装了Djangorequest
  4. - request.query_params --> URL中的参数
  5. - request.data --> POSTPUT请求中的数据
  6. 2. 重写了View中的dispatch方法
  7. dispatch方法
  8. 通用类(generics)
  9. GenericAPIView
  10. - queryset
  11. - serializer_class
  12. 混合类(mixins
  13. - ListModelMixin --> list
  14. - CreateModelMixin --> create
  15. - RetrieveModelMixin --> retrieve
  16. - DestroyModelMixin --> destroy
  17. - UpdateModelMixin --> update
  18. CommentView(GenericAPIView, ListModelMixin, CreateModelMixin):
  19. def get():
  20. return self.list()
  21. def post():
  22. return self.create()
  23. 偶数娃:
  24. CommentView(ListCreateAPIView):
  25. queryset = ...
  26. serializer_class = ...
  27. 奇数娃
  28. CommentDetail(RetrieveUpdateDestroyAPIView):
  29. queryset = ...
  30. serializer_class = ...
  31. 套娃:
  32. Comment(ModelViewSet):
  33. queryset = ...
  34. 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)登录

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图1

2)业务请求

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图2

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

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

二、DRF 认证

前提

还是依然使用昨天的项目about_drf3

定义一个用户表和一个保存用户Token的表,models.py完整代码下:

  1. from django.db import models
  2. # Create your models here.
  3. # 文章表
  4. class Article(models.Model):
  5. title = models.CharField(max_length=32, unique=True, error_messages={"unique": "文章标题不能重复"})
  6. # 文章发布时间
  7. # auto_now每次更新的时候会把当前时间保存
  8. create_time = models.DateField(auto_now_add=True)
  9. # auto_now_add 第一次创建的时候把当前时间保存
  10. update_time = models.DateField(auto_now=True)
  11. # 文章的类型
  12. type = models.SmallIntegerField(
  13. choices=((1, "原创"), (2, "转载")),
  14. default=1
  15. )
  16. # 来源
  17. school = models.ForeignKey(to='School', on_delete=models.CASCADE)
  18. # 标签
  19. tag = models.ManyToManyField(to='Tag')
  20. # 文章来源表
  21. class School(models.Model):
  22. name = models.CharField(max_length=16)
  23. # 文章标签表
  24. class Tag(models.Model):
  25. name = models.CharField(max_length=16)
  26. # 评论表
  27. class Comment(models.Model):
  28. content = models.CharField(max_length=128)
  29. article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
  30. user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)
  31. # 用户信息表
  32. class UserInfo(models.Model):
  33. username = models.CharField(max_length=16, unique=True)
  34. password = models.CharField(max_length=32)
  35. type = models.SmallIntegerField(
  36. choices=((1, '普通用户'), (2, 'VIP用户')),
  37. default=1
  38. )
  39. # token
  40. class Token(models.Model):
  41. token = models.CharField(max_length=128)
  42. user = models.OneToOneField(to='UserInfo',on_delete=models.CASCADE)

token单独分一个表,是因为它是在原有用户表的功能扩展。不能对一个表无限的增加字段,否则会导致表原来越臃肿

在前后端分离的架构中,前端使用ajax请求发送给后端,它不能使用cookie/session。那么后端怎么知道这个用户是否登录了,是否是VIP用户呢?使用token就可以解决这个问题!

使用2个命令生成表。

makemigrations 将models.py的变更做记录

migrate 将变更记录转换为sql语句,并执行

  1. python manage.py makemigrations
  2. python manage.py migrate

增加2条记录,使用navicast软件打开sqlite数据库,执行以下sql

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

app01_token表用来存放token的,它永久的身份令牌。在服务器自动生成的!

视图

修改views.py,完整代码如下:

  1. from django.shortcuts import render, HttpResponse
  2. from app01 import models
  3. from app01 import app01_serializers # 导入自定义的序列化
  4. from rest_framework.viewsets import ModelViewSet
  5. from rest_framework.views import APIView
  6. from rest_framework.response import Response
  7. # Create your views here.
  8. # 生成Token的函数
  9. def get_token_code(username):
  10. """
  11. 根据用户名和时间戳生成用户登陆成功的随机字符串
  12. :param username: 字符串格式的用户名
  13. :return: 字符串格式的Token
  14. """
  15. import time
  16. import hashlib
  17. timestamp = str(time.time()) # 当前时间戳
  18. m = hashlib.md5(bytes(username, encoding='utf8'))
  19. m.update(bytes(timestamp, encoding='utf8')) # update必须接收一个bytes
  20. return m.hexdigest()
  21. # 登陆视图
  22. class LoginView(APIView):
  23. """
  24. 登陆检测视图
  25. 1. 接收用户发过来(POST)的用户名和密码数据
  26. 2. 校验用户名密码是否正确
  27. - 成功就返回登陆成功(发Token)
  28. - 失败就返回错误提示
  29. """
  30. def post(self, request): # POST请求
  31. res = {"code": 0}
  32. # 从post里面取数据
  33. username = request.data.get("username")
  34. password = request.data.get("password")
  35. # 去数据库查询
  36. user_obj = models.UserInfo.objects.filter(
  37. username=username,
  38. password=password,
  39. ).first()
  40. if user_obj:
  41. # 登陆成功
  42. # 生成Token
  43. token = get_token_code(username)
  44. # 将token保存
  45. # 用user=user_obj这个条件去Token表里查询
  46. # 如果有记录就更新defaults里传的参数, 没有记录就用defaults里传的参数创建一条数据
  47. models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
  48. # 将token返回给用户
  49. res["token"] = token
  50. else:
  51. # 登录失败
  52. res["code"] = 1
  53. res["error"] = '用户名或密码错误'
  54. return Response(res)
  55. class CommentViewSet(ModelViewSet):
  56. queryset = models.Comment.objects.all()
  57. serializer_class = app01_serializers.CommentSerializer

路由

修改app01_urls.py,删除多余的代码

  1. from django.conf.urls import url
  2. from app01 import views
  3. urlpatterns = [
  4. url(r'login/$', views.LoginView.as_view()),
  5. ]
  6. from rest_framework.routers import DefaultRouter
  7. router = DefaultRouter()
  8. # 注册路由,表示路径comment对应视图函数CommentViewSet
  9. router.register(r'comment', views.CommentViewSet)
  10. urlpatterns += router.urls

使用postman发送post登录

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图3

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图4

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图5

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图6

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图7

定义一个认证类

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

在app01(应用名)目录下创建目录utils,在此目录下创建auth.py

  1. """
  2. 自定义的认证类都放在这里
  3. """
  4. from rest_framework.authentication import BaseAuthentication
  5. from app01 import models
  6. from rest_framework.exceptions import AuthenticationFailed
  7. class MyAuth(BaseAuthentication):
  8. def authenticate(self, request): # 必须要实现此方法
  9. if request.method in ['POST', 'PUT', 'DELETE']:
  10. token = request.data.get("token")
  11. # 去数据库查询有没有这个token
  12. token_obj = models.Token.objects.filter(token=token).first()
  13. if token_obj:
  14. # token_obj有2个属性,详见models.py中的Token。
  15. # return后面的代码,相当于分别赋值。例如a=1,b=2等同于a,b=1,2
  16. # return多个值,返回一个元组
  17. #在rest framework内部会将这两个字段赋值给request,以供后续操作使用
  18. return token_obj.user, token # self.user, self.token = token_obj.user, token
  19. else:
  20. raise AuthenticationFailed('无效的token')
  21. else:
  22. return None, None

视图级别认证

修改views.py,完整代码如下:

  1. from django.shortcuts import render, HttpResponse
  2. from app01 import models
  3. from app01 import app01_serializers # 导入自定义的序列化
  4. from rest_framework.viewsets import ModelViewSet
  5. from rest_framework.views import APIView
  6. from rest_framework.response import Response
  7. from app01.utils.auth import MyAuth # app01.utils.auth表示app01目录下的utils下的auth.py
  8. # Create your views here.
  9. # 生成Token的函数
  10. def get_token_code(username):
  11. """
  12. 根据用户名和时间戳生成用户登陆成功的随机字符串
  13. :param username: 字符串格式的用户名
  14. :return: 字符串格式的Token
  15. """
  16. import time
  17. import hashlib
  18. timestamp = str(time.time()) # 当前时间戳
  19. m = hashlib.md5(bytes(username, encoding='utf8'))
  20. m.update(bytes(timestamp, encoding='utf8')) # update必须接收一个bytes
  21. return m.hexdigest()
  22. # 登陆视图
  23. class LoginView(APIView):
  24. """
  25. 登陆检测视图
  26. 1. 接收用户发过来(POST)的用户名和密码数据
  27. 2. 校验用户名密码是否正确
  28. - 成功就返回登陆成功(发Token)
  29. - 失败就返回错误提示
  30. """
  31. def post(self, request): # POST请求
  32. res = {"code": 0}
  33. # 从post里面取数据
  34. username = request.data.get("username")
  35. password = request.data.get("password")
  36. # 去数据库查询
  37. user_obj = models.UserInfo.objects.filter(
  38. username=username,
  39. password=password,
  40. ).first()
  41. if user_obj:
  42. # 登陆成功
  43. # 生成Token
  44. token = get_token_code(username)
  45. # 将token保存
  46. # 用user=user_obj这个条件去Token表里查询
  47. # 如果有记录就更新defaults里传的参数, 没有记录就用defaults里传的参数创建一条数据
  48. models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
  49. # 将token返回给用户
  50. res["token"] = token
  51. else:
  52. # 登录失败
  53. res["code"] = 1
  54. res["error"] = '用户名或密码错误'
  55. return Response(res)
  56. class CommentViewSet(ModelViewSet):
  57. queryset = models.Comment.objects.all()
  58. serializer_class = app01_serializers.CommentSerializer
  59. authentication_classes = [MyAuth, ] # 局部使用认证方法MyAuth

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图8

发送一个错误的token

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图9

返回结果:

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图10

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

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

发送正确的token

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图11

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图12

全局级别认证

要想让每一个视图都要认证,可以在settings.py中配置

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

修改views.py,注释掉CommentViewSet中的authentication_classes

  1. class CommentViewSet(ModelViewSet):
  2. queryset = models.Comment.objects.all()
  3. serializer_class = app01_serializers.CommentSerializer
  4. # authentication_classes = [MyAuth, ] # 局部使用认证方法MyAuth

再次测试上面的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

  1. """
  2. 自定义的权限类
  3. """
  4. from rest_framework.permissions import BasePermission
  5. class MyPermission(BasePermission):
  6. def has_permission(self, request, view):
  7. """
  8. 判断该用户有没有权限
  9. """
  10. # 判断用户是不是VIP用户
  11. # 如果是VIP用户就返回True
  12. # 如果是普通用户就返回False
  13. print('我要进行自定义的权限判断啦....')
  14. print(request)
  15. print(request.user)
  16. return True

视图级别配置

修改views.py,指定permission_classes

  1. from django.shortcuts import render, HttpResponse
  2. from app01 import models
  3. from app01 import app01_serializers # 导入自定义的序列化
  4. from rest_framework.viewsets import ModelViewSet
  5. from rest_framework.views import APIView
  6. from rest_framework.response import Response
  7. from app01.utils.auth import MyAuth # app01.utils.auth表示app01目录下的utils下的auth.py
  8. from app01.utils.permission import MyPermission
  9. # Create your views here.
  10. # 生成Token的函数
  11. def get_token_code(username):
  12. """
  13. 根据用户名和时间戳生成用户登陆成功的随机字符串
  14. :param username: 字符串格式的用户名
  15. :return: 字符串格式的Token
  16. """
  17. import time
  18. import hashlib
  19. timestamp = str(time.time()) # 当前时间戳
  20. m = hashlib.md5(bytes(username, encoding='utf8'))
  21. m.update(bytes(timestamp, encoding='utf8')) # update必须接收一个bytes
  22. return m.hexdigest()
  23. # 登陆视图
  24. class LoginView(APIView):
  25. """
  26. 登陆检测视图
  27. 1. 接收用户发过来(POST)的用户名和密码数据
  28. 2. 校验用户名密码是否正确
  29. - 成功就返回登陆成功(发Token)
  30. - 失败就返回错误提示
  31. """
  32. def post(self, request): # POST请求
  33. res = {"code": 0}
  34. # 从post里面取数据
  35. username = request.data.get("username")
  36. password = request.data.get("password")
  37. # 去数据库查询
  38. user_obj = models.UserInfo.objects.filter(
  39. username=username,
  40. password=password,
  41. ).first()
  42. if user_obj:
  43. # 登陆成功
  44. # 生成Token
  45. token = get_token_code(username)
  46. # 将token保存
  47. # 用user=user_obj这个条件去Token表里查询
  48. # 如果有记录就更新defaults里传的参数, 没有记录就用defaults里传的参数创建一条数据
  49. models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
  50. # 将token返回给用户
  51. res["token"] = token
  52. else:
  53. # 登录失败
  54. res["code"] = 1
  55. res["error"] = '用户名或密码错误'
  56. return Response(res)
  57. class CommentViewSet(ModelViewSet):
  58. queryset = models.Comment.objects.all()
  59. serializer_class = app01_serializers.CommentSerializer
  60. # authentication_classes = [MyAuth, ] # 局部使用认证方法MyAuth
  61. permission_classes = [MyPermission, ] # 局部使用权限方法

发送get请求
Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图13

查看Pycharm控制台输出:

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

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

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图14

查看Pycharm控制台输出:

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

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

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

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

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

普通用户

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图15

查看Pycharm控制台输出:

  1. 我要进行自定义的权限判断啦....
  2. <rest_framework.request.Request object at 0x000002576A9852B0>
  3. UserInfo object

此时得到了一个用户对象

修改permission.py,获取用户名以及用户类型

  1. """
  2. 自定义的权限类
  3. """
  4. from rest_framework.permissions import BasePermission
  5. class MyPermission(BasePermission):
  6. def has_permission(self, request, view):
  7. """
  8. 判断该用户有没有权限
  9. """
  10. # 判断用户是不是VIP用户
  11. # 如果是VIP用户就返回True
  12. # 如果是普通用户就返回False
  13. print('我要进行自定义的权限判断啦....')
  14. print(request)
  15. print(request.user.username)
  16. print(request.user.type)
  17. return True

再次发送同样的post请求,再次查看Pycharm控制台输出

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

居然得到了zhang和1。为什么呢?为什么request.user.username就能得到用户名呢?

我来大概解释一下,先打开这篇文章:

https://www.cnblogs.com/derek1184405959/p/8712206.html

我引用里面几句话

Request有个user方法,加 @property 表示调用user方法的时候不需要加括号“user()”,可以直接调用:request.user

在rest framework内部会将这两个字段赋值给request,以供后续操作使用

  1. return (token_obj.user,token_obj)

上面的return的值,来源于app01\utils\auth.py里面的MyAuth类中的return token_obj.user, token

简单来说,通过认证之后,它会request进行再次封装,所以调用request.user时,得到了一个对象

这个对象就是执行models.Token.objects.filter(token=token).first()的结果

如果ORM没有查询出结果,它就一个匿名用户!

修改permission.py,如果是VIP返回True,否则返回False

  1. """
  2. 自定义的权限类
  3. """
  4. from rest_framework.permissions import BasePermission
  5. class MyPermission(BasePermission):
  6. def has_permission(self, request, view):
  7. """
  8. 判断该用户有没有权限
  9. """
  10. # 判断用户是不是VIP用户
  11. # 如果是VIP用户就返回True
  12. # 如果是普通用户就返回False
  13. print('我要进行自定义的权限判断啦....')
  14. # print(request)
  15. print(request.user.username)
  16. print(request.user.type)
  17. if request.user.type == 2: # 是VIP用户
  18. return True
  19. else:
  20. return False

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

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

使用VIP用户wang登录

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图16

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图17

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图18

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图19

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图20

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

修改permission.py,定义message

  1. """
  2. 自定义的权限类
  3. """
  4. from rest_framework.permissions import BasePermission
  5. class MyPermission(BasePermission):
  6. message = '您没有执行此操作的权限!'
  7. def has_permission(self, request, view):
  8. """
  9. 判断该用户有没有权限
  10. """
  11. # 判断用户是不是VIP用户
  12. # 如果是VIP用户就返回True
  13. # 如果是普通用户就返回False
  14. print('我要进行自定义的权限判断啦....')
  15. # print(request)
  16. print(request.user.username)
  17. print(request.user.type)
  18. if request.user.type == 2: # 是VIP用户
  19. return True
  20. else:
  21. return False

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图21

VIP用户

将token改成VIP用户测试

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图22

查看返回结果

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图23

修改permission.py,定义发送类型

  1. """
  2. 自定义的权限类
  3. """
  4. from rest_framework.permissions import BasePermission
  5. class MyPermission(BasePermission):
  6. message = '您没有执行此操作的权限!'
  7. def has_permission(self, request, view):
  8. """
  9. 判断该用户有没有权限
  10. """
  11. # 判断用户是不是VIP用户
  12. # 如果是VIP用户就返回True
  13. # 如果是普通用户就返回False
  14. print('我要进行自定义的权限判断啦....')
  15. if request.method in ['POST', 'PUT', 'DELETE']:
  16. print(request.user.username)
  17. print(request.user.type)
  18. if request.user.type == 2: # 是VIP用户
  19. return True
  20. else:
  21. return False
  22. else:
  23. return True

发送正确的值

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图24

查看返回结果

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图25

查看表app01_comment记录

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图26

全局级别设置

修改settings.py,增加一行

  1. REST_FRAMEWORK = {
  2. # 表示app01-->utils下的auth.py里面的MyAuth类
  3. # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ]
  4. "DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.MyPermission", ]
  5. }

修改views.py,注释局部的

  1. from django.shortcuts import render, HttpResponse
  2. from app01 import models
  3. from app01 import app01_serializers # 导入自定义的序列化
  4. from rest_framework.viewsets import ModelViewSet
  5. from rest_framework.views import APIView
  6. from rest_framework.response import Response
  7. from app01.utils.auth import MyAuth # app01.utils.auth表示app01目录下的utils下的auth.py
  8. from app01.utils.permission import MyPermission
  9. # Create your views here.
  10. # 生成Token的函数
  11. def get_token_code(username):
  12. """
  13. 根据用户名和时间戳生成用户登陆成功的随机字符串
  14. :param username: 字符串格式的用户名
  15. :return: 字符串格式的Token
  16. """
  17. import time
  18. import hashlib
  19. timestamp = str(time.time()) # 当前时间戳
  20. m = hashlib.md5(bytes(username, encoding='utf8'))
  21. m.update(bytes(timestamp, encoding='utf8')) # update必须接收一个bytes
  22. return m.hexdigest()
  23. # 登陆视图
  24. class LoginView(APIView):
  25. """
  26. 登陆检测视图
  27. 1. 接收用户发过来(POST)的用户名和密码数据
  28. 2. 校验用户名密码是否正确
  29. - 成功就返回登陆成功(发Token)
  30. - 失败就返回错误提示
  31. """
  32. def post(self, request): # POST请求
  33. res = {"code": 0}
  34. # 从post里面取数据
  35. username = request.data.get("username")
  36. password = request.data.get("password")
  37. # 去数据库查询
  38. user_obj = models.UserInfo.objects.filter(
  39. username=username,
  40. password=password,
  41. ).first()
  42. if user_obj:
  43. # 登陆成功
  44. # 生成Token
  45. token = get_token_code(username)
  46. # 将token保存
  47. # 用user=user_obj这个条件去Token表里查询
  48. # 如果有记录就更新defaults里传的参数, 没有记录就用defaults里传的参数创建一条数据
  49. models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
  50. # 将token返回给用户
  51. res["token"] = token
  52. else:
  53. # 登录失败
  54. res["code"] = 1
  55. res["error"] = '用户名或密码错误'
  56. return Response(res)
  57. class CommentViewSet(ModelViewSet):
  58. queryset = models.Comment.objects.all()
  59. serializer_class = app01_serializers.CommentSerializer
  60. authentication_classes = [MyAuth, ] # 局部使用认证方法MyAuth
  61. # permission_classes = [MyPermission, ] # 局部使用权限方法

验证

使用普通用户测试

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图27

查看返回结果

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图28

举例2

只要评论的作者是自己,就可以删除,否则不行!

表结构

修改models.py,在评论表中,增加一个字段user

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

使用2个命令生成表。

  1. python manage.py makemigrations
  2. python manage.py migrate

修改表,增加2个user_id

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图29

has_object_permission

修改permission.py,增加has_object_permission

它比上面的has_permission方法多了一个obj

它是操作的对象,比如评论对象

  1. """
  2. 自定义的权限类
  3. """
  4. from rest_framework.permissions import BasePermission
  5. class MyPermission(BasePermission):
  6. message = '您没有执行此操作的权限!'
  7. def has_permission(self, request, view):
  8. """
  9. 判断该用户有没有权限
  10. """
  11. # 判断用户是不是VIP用户
  12. # 如果是VIP用户就返回True
  13. # 如果是普通用户就返回False
  14. print('我要进行自定义的权限判断啦....')
  15. return True
  16. # if request.method in ['POST', 'PUT', 'DELETE']:
  17. # print(request.user.username)
  18. # print(request.user.type)
  19. # if request.user.type == 2: # 是VIP用户
  20. # return True
  21. # else:
  22. # return False
  23. # else:
  24. # return True
  25. def has_object_permission(self, request, view, obj):
  26. """
  27. 判断当前评论用户的作者是不是你当前的用户
  28. 只有评论的作者才能删除自己的评论
  29. """
  30. print('这是在自定义权限类中的has_object_permission')
  31. print(obj.id)
  32. if request.method in ['PUT', 'DELETE']:
  33. if obj.user == request.user:
  34. # 当前要删除的评论的作者就是当前登陆的用户
  35. return True
  36. else:
  37. return False
  38. else:
  39. return True

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图30

查看返回结果

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图31

使用VIP用户的token发送

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图32

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图33

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图34

四、DRF节流

节流也称之为限制

DRF节流源码分析

请参考链接:

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

自定义限制类

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

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

  1. """
  2. 自定义的访问限制类
  3. """
  4. from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
  5. import time
  6. D = {} # {'127.0.0.1': [1533302442, 1533302439,...]}
  7. class MyThrottle(BaseThrottle):
  8. def allow_request(self, request, view):
  9. """
  10. 返回True就放行,返回False表示被限制了...
  11. """
  12. # 1. 获取当前访问的IP
  13. ip = request.META.get("REMOTE_ADDR")
  14. print('这是自定义限制类中的allow_request')
  15. print(ip)
  16. # 2. 获取当前的时间
  17. now = time.time()
  18. # 判断当前ip是否有访问记录
  19. if ip not in D:
  20. D[ip] = [] # 初始化一个空的访问历史列表
  21. # 高端骚操作
  22. history = D[ip]
  23. while history and now - history[-1] > 10:
  24. history.pop()
  25. # 判断最近一分钟的访问次数是否超过了阈值(3次)
  26. if len(history) >= 3:
  27. return False
  28. else:
  29. # 把这一次的访问时间加到访问历史列表的第一位
  30. D[ip].insert(0, now)
  31. return True

代码解释:

request.META.get(“REMOTE_ADDR”) 获取远程IP

D 存储的值,类似于

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

最后一个元素,就是最先开始的时间

for循环列表,不能对列表做更改操作!所以使用while循环

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

history是历史列表,history[-1] 表示列表最后一个元素

history and now - history[-1] > 10 表示当历史列表中有元素,并且当前时间戳减去最后一个元素的时间戳大于10的时候,执行history.pop(),表示删除最后一个元素

当历史列表为空时,或者小于差值小于10的时候,结束循环。

视图使用

修改views.py

  1. from django.shortcuts import render, HttpResponse
  2. from app01 import models
  3. from app01 import app01_serializers # 导入自定义的序列化
  4. from rest_framework.viewsets import ModelViewSet
  5. from rest_framework.views import APIView
  6. from rest_framework.response import Response
  7. from app01.utils.auth import MyAuth # app01.utils.auth表示app01目录下的utils下的auth.py
  8. from app01.utils.permission import MyPermission
  9. from app01.utils.throttle import MyThrottle
  10. # Create your views here.
  11. # 生成Token的函数
  12. def get_token_code(username):
  13. """
  14. 根据用户名和时间戳生成用户登陆成功的随机字符串
  15. :param username: 字符串格式的用户名
  16. :return: 字符串格式的Token
  17. """
  18. import time
  19. import hashlib
  20. timestamp = str(time.time()) # 当前时间戳
  21. m = hashlib.md5(bytes(username, encoding='utf8'))
  22. m.update(bytes(timestamp, encoding='utf8')) # update必须接收一个bytes
  23. return m.hexdigest()
  24. # 登陆视图
  25. class LoginView(APIView):
  26. """
  27. 登陆检测视图
  28. 1. 接收用户发过来(POST)的用户名和密码数据
  29. 2. 校验用户名密码是否正确
  30. - 成功就返回登陆成功(发Token)
  31. - 失败就返回错误提示
  32. """
  33. def post(self, request): # POST请求
  34. res = {"code": 0}
  35. # 从post里面取数据
  36. username = request.data.get("username")
  37. password = request.data.get("password")
  38. # 去数据库查询
  39. user_obj = models.UserInfo.objects.filter(
  40. username=username,
  41. password=password,
  42. ).first()
  43. if user_obj:
  44. # 登陆成功
  45. # 生成Token
  46. token = get_token_code(username)
  47. # 将token保存
  48. # 用user=user_obj这个条件去Token表里查询
  49. # 如果有记录就更新defaults里传的参数, 没有记录就用defaults里传的参数创建一条数据
  50. models.Token.objects.update_or_create(defaults={"token": token}, user=user_obj)
  51. # 将token返回给用户
  52. res["token"] = token
  53. else:
  54. # 登录失败
  55. res["code"] = 1
  56. res["error"] = '用户名或密码错误'
  57. return Response(res)
  58. class CommentViewSet(ModelViewSet):
  59. queryset = models.Comment.objects.all()
  60. serializer_class = app01_serializers.CommentSerializer
  61. authentication_classes = [MyAuth, ] # 局部使用认证方法MyAuth
  62. # permission_classes = [MyPermission, ] # 局部使用权限方法
  63. throttle_classes = [MyThrottle, ] # 局部使用限制方法

使用postman发送GET请求

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图35

提示请求达到了限制

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图36

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

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图37

全局使用

修改settings.py

  1. REST_FRAMEWORK = {
  2. # 表示app01-->utils下的auth.py里面的MyAuth类
  3. # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ],
  4. #"DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.MyPermission", ],
  5. "DEFAULT_THROTTLE_CLASSES": ["app01.utils.throttle.MyThrottle", ]
  6. }

修改views.py,注释掉代码

  1. class CommentViewSet(ModelViewSet):
  2. queryset = models.Comment.objects.all()
  3. serializer_class = app01_serializers.CommentSerializer
  4. authentication_classes = [MyAuth, ] # 局部使用认证方法MyAuth
  5. permission_classes = [MyPermission, ] # 局部使用权限方法
  6. # throttle_classes = [MyThrottle, ] # 局部使用限制方法

再次测试,效果同上!

使用内置限制类

修改about_drf\app01\utils\throttle.py

  1. """
  2. 自定义的访问限制类
  3. """
  4. from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
  5. # import time
  6. #
  7. # D = {} # {'127.0.0.1': [1533302442, 1533302439,...]}
  8. #
  9. #
  10. # class MyThrottle(BaseThrottle):
  11. #
  12. # def allow_request(self, request, view):
  13. #
  14. # """
  15. # 返回True就放行,返回False表示被限制了...
  16. # """
  17. # # 1. 获取当前访问的IP
  18. # ip = request.META.get("REMOTE_ADDR")
  19. # print('这是自定义限制类中的allow_request')
  20. # print(ip)
  21. # # 2. 获取当前的时间
  22. # now = time.time()
  23. # # 判断当前ip是否有访问记录
  24. # if ip not in D:
  25. # D[ip] = [] # 初始化一个空的访问历史列表
  26. # # 高端骚操作
  27. # history = D[ip]
  28. # while history and now - history[-1] > 10:
  29. # history.pop()
  30. # # 判断最近一分钟的访问次数是否超过了阈值(3次)
  31. # if len(history) >= 3:
  32. # return False
  33. # else:
  34. # # 把这一次的访问时间加到访问历史列表的第一位
  35. # D[ip].insert(0, now)
  36. # return True
  37. class MyThrottle(SimpleRateThrottle):
  38. scope = "rate" # rate是名字,可以随便定义!
  39. def get_cache_key(self, request, view):
  40. return self.get_ident(request)

注意:scope是关键字参数

get_cache_key 的名字不能变动

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

全局配置

修改settings.py

  1. REST_FRAMEWORK = {
  2. # 表示app01-->utils下的auth.py里面的MyAuth类
  3. # "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ]
  4. "DEFAULT_PERMISSION_CLASSES": ["app01.utils.permission.MyPermission", ],
  5. "DEFAULT_THROTTLE_CLASSES": ["app01.utils.throttle.MyThrottle", ],
  6. "DEFAULT_THROTTLE_RATES": {
  7. "rate": "3/m",
  8. }
  9. }

注意:rate对应的是throttle.py里面MyThrottle定义的scope属性的值

3/m 表示1分钟3次

再次测试,效果如下:

Day97 Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流 - 图38

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