python 全栈开发,Day97(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?
而要回答这个问题很简单——因为它能解决问题!
可以解决哪些问题呢?
Token 完全由应用管理,所以它可以避开同源策略
Token 可以避免 CSRF 攻击
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)
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)
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
路由
修改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
使用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
发送一个空的post请求,返回结果如下:
发送一个错误的token
返回结果:
注意:这个信息是由raise AuthenticationFailed('无效的token')触发的。
如果想在MyAuth类-->authenticate方法-->代码else中触发别的信息,也同样需要定义raise
发送正确的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
注意:当返回一个对象时,才会触发
什么对象呢?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
视图级别配置
修改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,它没有触发has_permission
访问单个评论,返回单个json对象
查看Pycharm控制台输出:
我要进行自定义的权限判断啦....
<rest_framework.request.Request object at 0x000002576A780FD0>
这是在自定义权限类中的has_object_permission
1
它触发了has_permission,并输出了一段话
注意:json对象中,它增加一个属性user,值为null。为什么会增加呢?
因为在源码中,Request有个user方法,加 @property。它对返回结果做了在再次封装!
详情,请参考上面的权限源码流程
普通用户
发送post请求,写一个正确的token,用zhang用户的token
查看Pycharm控制台输出:
我要进行自定义的权限判断啦....
<rest_framework.request.Request object at 0x000002576A9852B0>
UserInfo object
此时得到了一个用户对象
修改permission.py,获取用户名以及用户类型
"""
自定义的权限类
"""
from rest_framework.permissions import BasePermission class MyPermission(BasePermission):
def has_permission(self, request, view):
"""
判断该用户有没有权限
"""
# 判断用户是不是VIP用户
# 如果是VIP用户就返回True
# 如果是普通用户就返回False
print('我要进行自定义的权限判断啦....')
print(request)
print(request.user.username)
print(request.user.type)
return True
再次发送同样的post请求,再次查看Pycharm控制台输出
<rest_framework.request.Request object at 0x000001D893AC4048>
zhang
1
居然得到了zhang和1。为什么呢?为什么request.user.username就能得到用户名呢?
我来大概解释一下,先打开这篇文章:
https://www.cnblogs.com/derek1184405959/p/8712206.html
我引用里面几句话
Request有个user方法,加 @property 表示调用user方法的时候不需要加括号“user()”,可以直接调用:request.user
在rest framework内部会将这两个字段赋值给request,以供后续操作使用
return (token_obj.user,token_obj)
上面的return的值,来源于app01\utils\auth.py里面的MyAuth类中的return token_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
修改settings.py,关闭全局级别认证
REST_FRAMEWORK = {
# 表示app01-->utils下的auth.py里面的MyAuth类
# "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.auth.MyAuth", ]
}
使用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,定义发送类型
"""
自定义的权限类
"""
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)
使用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
使用普通用户的token发送delete类型的请求
查看返回结果
使用VIP用户的token发送
查看返回结果,为空,表示删除成功
查看表app01_comment,发现少了一条记录
四、DRF节流
节流也称之为限制
DRF节流源码分析
请参考链接:
http://www.cnblogs.com/derek1184405959/p/8722638.html
自定义限制类
对IP做限制,60秒只能访问3次
在about_drf\app01\utils下面创建throttle.py
"""
自定义的访问限制类
"""
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
import time D = {} # {'127.0.0.1': [1533302442, 1533302439,...]} class MyThrottle(BaseThrottle): def allow_request(self, request, view):
"""
返回True就放行,返回False表示被限制了...
"""
# 1. 获取当前访问的IP
ip = request.META.get("REMOTE_ADDR")
print('这是自定义限制类中的allow_request')
print(ip)
# 2. 获取当前的时间
now = time.time()
# 判断当前ip是否有访问记录
if ip not in D:
D[ip] = [] # 初始化一个空的访问历史列表
# 高端骚操作
history = D[ip]
while history and now - history[-1] > 10:
history.pop()
# 判断最近一分钟的访问次数是否超过了阈值(3次)
if len(history) >= 3:
return False
else:
# 把这一次的访问时间加到访问历史列表的第一位
D[ip].insert(0, now)
return True
代码解释:
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, ] # 局部使用限制方法
使用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", ]
}
修改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, ] # 局部使用限制方法
再次测试,效果同上!
使用内置限制类
修改about_drf\app01\utils\throttle.py
"""
自定义的访问限制类
"""
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
# import time
#
# D = {} # {'127.0.0.1': [1533302442, 1533302439,...]}
#
#
# class MyThrottle(BaseThrottle):
#
# def allow_request(self, request, view):
#
# """
# 返回True就放行,返回False表示被限制了...
# """
# # 1. 获取当前访问的IP
# ip = request.META.get("REMOTE_ADDR")
# print('这是自定义限制类中的allow_request')
# print(ip)
# # 2. 获取当前的时间
# now = time.time()
# # 判断当前ip是否有访问记录
# if ip not in D:
# D[ip] = [] # 初始化一个空的访问历史列表
# # 高端骚操作
# history = D[ip]
# while history and now - history[-1] > 10:
# history.pop()
# # 判断最近一分钟的访问次数是否超过了阈值(3次)
# if len(history) >= 3:
# return False
# else:
# # 把这一次的访问时间加到访问历史列表的第一位
# D[ip].insert(0, now)
# return True class MyThrottle(SimpleRateThrottle): scope = "rate" # rate是名字,可以随便定义! def get_cache_key(self, request, view):
return self.get_ident(request)
注意: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节流)的更多相关文章
- python 全栈开发,Day110(django ModelForm,客户管理之 编辑权限(一))
昨日内容回顾 1. 简述权限管理的实现原理. 粒度控制到按钮级别的权限控制 - 用户登陆成功之后,将权限和菜单信息放入session - 每次请求时,在中间件中做权限校验 - inclusion_ta ...
- python全栈开发-Day7 字符编码总结
python全栈开发-Day7 字符编码总结 一.字符编码总结 1.什么是字符编码 人类的字符--------->翻译--------->数字 翻译的过程遵循的标准即字符编码(就是一个字符 ...
- python全栈开发-Day2 布尔、流程控制、循环
python全栈开发-Day2 布尔 流程控制 循环 一.布尔 1.概述 #布尔值,一个True一个False #计算机俗称电脑,即我们编写程序让计算机运行时,应该是让计算机无限接近人脑,或者说人 ...
- python全栈开发中级班全程笔记(第二模块、第四章)(常用模块导入)
python全栈开发笔记第二模块 第四章 :常用模块(第二部分) 一.os 模块的 详解 1.os.getcwd() :得到当前工作目录,即当前python解释器所在目录路径 impor ...
- python 全栈开发,Day99(作业讲解,DRF版本,DRF分页,DRF序列化进阶)
昨日内容回顾 1. 为什么要做前后端分离? - 前后端交给不同的人来编写,职责划分明确. - API (IOS,安卓,PC,微信小程序...) - vue.js等框架编写前端时,会比之前写jQuery ...
- Python全栈开发【面向对象进阶】
Python全栈开发[面向对象进阶] 本节内容: isinstance(obj,cls)和issubclass(sub,super) 反射 __setattr__,__delattr__,__geta ...
- Python全栈开发【面向对象】
Python全栈开发[面向对象] 本节内容: 三大编程范式 面向对象设计与面向对象编程 类和对象 静态属性.类方法.静态方法 类组合 继承 多态 封装 三大编程范式 三大编程范式: 1.面向过程编程 ...
- Python全栈开发【模块】
Python全栈开发[模块] 本节内容: 模块介绍 time random os sys json & picle shelve XML hashlib ConfigParser loggin ...
- Python全栈开发【基础四】
Python全栈开发[基础四] 本节内容: 匿名函数(lambda) 函数式编程(map,filter,reduce) 文件处理 迭代器 三元表达式 列表解析与生成器表达式 生成器 匿名函数 lamb ...
- Python全栈开发【基础三】
Python全栈开发[基础三] 本节内容: 函数(全局与局部变量) 递归 内置函数 函数 一.定义和使用 函数最重要的是减少代码的重用性和增强代码可读性 def 函数名(参数): ... 函数体 . ...
随机推荐
- P3594 [POI2015]WIL-Wilcze doły
P3594 [POI2015]WIL-Wilcze doły 题目描述 给定一个长度为n的序列,你有一次机会选中一段连续的长度不超过d的区间,将里面所有数字全部修改为0.请找到最长的一段连续区间,使得 ...
- SpringJMS解析--监听器
消息监听器容器是一个用于查看JMS目标等待消息到达的特殊bean,一旦消息到达它就可以获取到消息,并通过调用onMessage()方法将消息传递给一个MessageListener实现.Spring中 ...
- 机器学习课程-第7周-支持向量机(Support Vector Machines)
1. 优化目标 在监督学习中,许多学习算法的性能都非常类似,因此,重要的不是你该选择使用学习算法A还是学习算法B,而更重要的是,应用这些算法时,所创建的大量数据在应用这些算法时,表现情况通常依赖于你的 ...
- u-boot移植(三)---修改前工作:代码流程分析2
一.vectors.S 1.1 代码地址 vectors.S (arch\arm\lib) 1.2 流程跳转 跳转符号 B 为 start.S 中的 reset 执行代码,暂且先不看,先看看 vect ...
- java 多线程下载功能
import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; impo ...
- centos6 python 安装 sqlite 解决 No module named ‘_sqlite3′
原文连接: http://blog.csdn.net/jaket5219999/article/details/53512071 系统red hat6.7 也即centos6.7 python3.5. ...
- adb不识别设备(手机)的若干情形及解决方法
1.执行adb root 提示adb: unable to connect for root: no devices/emulators found:执行adb devices ,List下无设备 ...
- SpringBoot注解把配置文件自动映射到属性和实体类实战
SpringBoot注解把配置文件自动映射到属性和实体类实战 简介:讲解使用@value注解配置文件自动映射到属性和实体类 1.配置文件加载 方式一 1.Controller上面配置 @Propert ...
- Debian ifconfig 命令找不到
如何配置让 Debian 非特权用户也可以使用 ifconfig . ifconfig 在 /sbin 目录下,新建一个用户时, Debian 默认从 /etc/skel/ 复制配置文件, /sbin ...
- JavaScript对象复制(二)
<script> function copy(a) { ret = {}; for (sth in a) { temp = a[sth]; if (temp instanceof Arra ...