上一章节内容回顾: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     --> updateCommentView(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 timeimport hashlibtimestamp = str(time.time())  # 当前时间戳m = hashlib.md5(bytes(username, encoding='utf8'))m.update(bytes(timestamp, encoding='utf8'))  # update必须接收一个bytesreturn 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:# 登陆成功# 生成Tokentoken = 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"] = tokenelse:# 登录失败res["code"] = 1res["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 viewsurlpatterns = [url(r'login/$', views.LoginView.as_view()),
]from rest_framework.routers import DefaultRouterrouter = 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 AuthenticationFailedclass MyAuth(BaseAuthentication):def authenticate(self, request):  # 必须要实现此方法if request.method in ['POST', 'PUT', 'DELETE']:token = request.data.get("token")# 去数据库查询有没有这个tokentoken_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, tokenelse: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 timeimport hashlibtimestamp = str(time.time())  # 当前时间戳m = hashlib.md5(bytes(username, encoding='utf8'))m.update(bytes(timestamp, encoding='utf8'))  # update必须接收一个bytesreturn 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:# 登陆成功# 生成Tokentoken = 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"] = tokenelse:# 登录失败res["code"] = 1res["error"] = '用户名或密码错误'return Response(res)class CommentViewSet(ModelViewSet):queryset = models.Comment.objects.all()serializer_class = app01_serializers.CommentSerializerauthentication_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 BasePermissionclass MyPermission(BasePermission):def has_permission(self, request, view):"""判断该用户有没有权限"""# 判断用户是不是VIP用户# 如果是VIP用户就返回True# 如果是普通用户就返回Falseprint('我要进行自定义的权限判断啦....')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 timeimport hashlibtimestamp = str(time.time())  # 当前时间戳m = hashlib.md5(bytes(username, encoding='utf8'))m.update(bytes(timestamp, encoding='utf8'))  # update必须接收一个bytesreturn 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:# 登陆成功# 生成Tokentoken = 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"] = tokenelse:# 登录失败res["code"] = 1res["error"] = '用户名或密码错误'return Response(res)class CommentViewSet(ModelViewSet):queryset = models.Comment.objects.all()serializer_class = app01_serializers.CommentSerializer# authentication_classes = [MyAuth, ]  # 局部使用认证方法MyAuthpermission_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 BasePermissionclass MyPermission(BasePermission):def has_permission(self, request, view):"""判断该用户有没有权限"""# 判断用户是不是VIP用户# 如果是VIP用户就返回True# 如果是普通用户就返回Falseprint('我要进行自定义的权限判断啦....')print(request)print(request.user.username)print(request.user.type)return True

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

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

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

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

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

我引用里面几句话

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

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

return (token_obj.user,token_obj)

上面的return的值,来源于app01\utils\auth.py 里面的MyAuth类中的return token_onj.user , token 简单来说,通过认证之后,它会request进行再次封装,所以调用request.user时,得到了一个对象,这个对象就是执行models.Token.objects.filter(token=token).first()结果

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

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

"""
自定义的权限类
"""
from rest_framework.permissions import BasePermissionclass MyPermission(BasePermission):def has_permission(self, request, view):"""判断用户有没有权限:param request::param view::return:"""#判断用户是不是VIP用户#如果是VIP用户就返回True#如果是普通用户就返回Falseprint('我要进行自定义的权限判断....')print(request.user.username)print(request.user.type)if request.user.type == 2:  #是VIP用户return Trueelse: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 BasePermissionclass MyPermission(BasePermission):message = '您没有执行此操作的权限!'def has_permission(self, request, view):"""判断该用户有没有权限"""# 判断用户是不是VIP用户# 如果是VIP用户就返回True# 如果是普通用户就返回Falseprint('我要进行自定义的权限判断啦....')# print(request)print(request.user.username)print(request.user.type)if request.user.type == 2:  # 是VIP用户return Trueelse: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 BasePermissionclass MyPermission(BasePermission):message = '您没有执行此操作的权限!'def has_permission(self, request, view):"""判断该用户有没有权限"""# 判断用户是不是VIP用户# 如果是VIP用户就返回True# 如果是普通用户就返回Falseprint('我要进行自定义的权限判断啦....')if request.method in ['POST', 'PUT', 'DELETE']:print(request.user.username)print(request.user.type)if request.user.type == 2:  # 是VIP用户return Trueelse:return Falseelse: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 timeimport hashlibtimestamp = str(time.time())  # 当前时间戳m = hashlib.md5(bytes(username, encoding='utf8'))m.update(bytes(timestamp, encoding='utf8'))  # update必须接收一个bytesreturn 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:# 登陆成功# 生成Tokentoken = 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"] = tokenelse:# 登录失败res["code"] = 1res["error"] = '用户名或密码错误'return Response(res)class CommentViewSet(ModelViewSet):queryset = models.Comment.objects.all()serializer_class = app01_serializers.CommentSerializerauthentication_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 BasePermissionclass MyPermission(BasePermission):message = '您没有执行此操作的权限!'def has_permission(self, request, view):"""判断该用户有没有权限"""# 判断用户是不是VIP用户# 如果是VIP用户就返回True# 如果是普通用户就返回Falseprint('我要进行自定义的权限判断啦....')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 Truedef 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 Trueelse:return Falseelse:return True

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

查看返回结果

使用VIP用户的token发送

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

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

四、DRF限制

限制也称之为节流

DRF节流源码分析

请参考链接:

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

自定义限制类

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

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

"""
自定义的访问限制类
"""
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
import time

D = {} # {'127.0.0.1': [1533302442, 1533302439,...]}

class MyThrottle(BaseThrottle):

def allow_request(self, request, view):
"""
返回True就放行,返回False表示被限制了...
"""
# 1. 获取当前访问的IP
ip = request.META.get("REMOTE_ADDR")
print('这是自定义限制类中的allow_request')
print(ip)
# 2. 获取当前的时间
now = time.time()
# 判断当前ip是否有访问记录
if ip not in D:
D[ip] = [] # 初始化一个空的访问历史列表
# 高端骚操作
history = D[ip]
while history and now - history[-1] > 10:
history.pop()
# 判断最近一分钟的访问次数是否超过了阈值(3次)
if len(history) >= 3:
return False
else:
# 把这一次的访问时间加到访问历史列表的第一位
D[ip].insert(0, now)
return True

"""
自定义的访问限制类
"""
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
import timeD = {}  # {'127.0.0.1': [1533302442, 1533302439,...]}class MyThrottle(BaseThrottle):def allow_request(self, request, view):"""返回True就放行,返回False表示被限制了..."""# 1. 获取当前访问的IPip = 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 Falseelse:# 把这一次的访问时间加到访问历史列表的第一位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 timeimport hashlibtimestamp = str(time.time())  # 当前时间戳m = hashlib.md5(bytes(username, encoding='utf8'))m.update(bytes(timestamp, encoding='utf8'))  # update必须接收一个bytesreturn 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:# 登陆成功# 生成Tokentoken = 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"] = tokenelse:# 登录失败res["code"] = 1res["error"] = '用户名或密码错误'return Response(res)class CommentViewSet(ModelViewSet):queryset = models.Comment.objects.all()serializer_class = app01_serializers.CommentSerializerauthentication_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.CommentSerializerauthentication_classes = [MyAuth, ]  # 局部使用认证方法MyAuthpermission_classes = [MyPermission, ]  # 局部使用权限方法# throttle_classes = [MyThrottle, ]  # 局部使用限制方法
class CommentViewSet(ModelViewSet):queryset = models.Comment.objects.all()serializer_class = app01_serializers.CommentSerializerauthentication_classes = [MyAuth, ]  # 局部使用认证方法MyAuthpermission_classes = [MyPermission, ]  # 局部使用权限方法# throttle_classes = [MyThrottle, ]  # 局部使用限制方法

再次测试,效果同上!

使用内置限制类

修改about_drf\app01\utils\throttle.py

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

class MyThrottle(SimpleRateThrottle):

scope = "rate" # rate是名字,可以随便定义!

def get_cache_key(self, request, view):
return self.get_ident(request)

"""
自定义的访问限制类
"""
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
# import time
#
# D = {}  # {'127.0.0.1': [1533302442, 1533302439,...]}
#
#
# class MyThrottle(BaseThrottle):
#
#     def allow_request(self, request, view):
#
#         """
#         返回True就放行,返回False表示被限制了...
#         """
#         # 1. 获取当前访问的IP
#         ip = request.META.get("REMOTE_ADDR")
#         print('这是自定义限制类中的allow_request')
#         print(ip)
#         # 2. 获取当前的时间
#         now = time.time()
#         # 判断当前ip是否有访问记录
#         if ip not in D:
#             D[ip] = []  # 初始化一个空的访问历史列表
#         # 高端骚操作
#         history = D[ip]
#         while history and now - history[-1] > 10:
#             history.pop()
#         # 判断最近一分钟的访问次数是否超过了阈值(3次)
#         if len(history) >= 3:
#             return False
#         else:
#             # 把这一次的访问时间加到访问历史列表的第一位
#             D[ip].insert(0, now)
#             return Trueclass 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次

再次测试,效果如下:

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

  

  

  

转载于:https://www.cnblogs.com/haowen980/p/9416531.html

Token 认证的来龙去脉,DRF认证,DRF权限,DRF限制相关推荐

  1. Rest-framework之drf认证组件,权限组件+不存数据库的token认证

    Rest-framework之drf认证组件,权限组件 django中一个请求时一个reques,如果在哪个位置改了request,那么到了后面就是修改过的request 昨日回顾: 认证: -写一个 ...

  2. Django DRF认证组件/权限组件/序列化组件综合总结(完整版)

    本代码完成的功能是: 1.根据token判断用户登录状态,然后提示用户是否登陆, 2.用户登录后,根据用户类型判断用户是否有权限查看资料 使用rest_framework一定要在配置文件设置先设置 ' ...

  3. Token 认证的来龙去脉

    摘要: Token 是在服务端产生的.如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端.前端可以在每次请求的时候带上 Token 证明自己的合法地位 不 ...

  4. 深入了解Token认证的来龙去脉

    Token 是在服务端产生的,如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端.前端可以在每次请求的时候带上 Token 证明自己的合法地位. 不久前, ...

  5. java token 有效期_Java编程——Token 认证的来龙去脉

    通常情况下,我们在讨论某个技术的时候,都是从问题开始.那么第一个问题: 为什么要用 Token? 而要回答这个问题很简单--因为它能解决问题! 可以解决哪些问题呢? 1. Token 完全由应用管理, ...

  6. 基于Token的WEB后台登录认证机制(并讲解其他认证机制以及cookie和session机制)

    几种常用的认证机制 HTTP Basic Auth HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RES ...

  7. 认证学习4 - Bearer认证(Token认证)讲解、代码实现、演示

    文章目录 Bearer认证(JWT-Token)- 用户信息存客户端中 讲解(Bearer认证) 实现(Bearer认证) 代码(Bearer认证) 演示(Bearer认证) 浏览器 postman ...

  8. 认证、授权、鉴权和权限控制

    原文地址 https://www.cnblogs.com/badboyh2o/p/11068779.html 如有侵权,请联系删除,谢谢! 本文将对信息安全领域中认证.授权.鉴权和权限控制这四个概念给 ...

  9. 认证、授权、鉴权和权限控制概念区别

    1.认证: 认证是指根据声明者所特有的识别信息,确认声明者的身份.认证在英文中对应于identification这个单词. 最常见的认证实现方式是通过用户名和密码,但认证方式不限于此.下面都是当前常见 ...

最新文章

  1. HDLBits 系列(35)Lemmings Game
  2. Jenkins定时构建任务
  3. Java 注解用法详解——@SuppressWarnings
  4. wechaty QR code not recognizable..
  5. 防止stack buffer overflows攻击的方法 : Canary 漏洞缓解机制
  6. uvalive5798(树状数组)
  7. Phalcon7 1.2.3 发布,高性能 PHP 7 框架
  8. 【学习笔记】WQS二分详解及常见理解误区解释
  9. ssl初一组周六模拟赛【2018.3.10】
  10. problem a: 简单的整数排序_python里的排序
  11. SONICWALL E-Class NSA 系列简介
  12. 【英语学习】【English L06】U08 News L5 They are expecting a baby!
  13. 嵌入式Linux入门6:u-boot移植
  14. 基于asp网上书店购物商城计算机毕业设计网站作品
  15. 201621123083 《Java程序设计》第9周学习总结
  16. 基于STM32 HAL库硬件SPI的ST7789驱动(TFT-LCD 240*320)
  17. word使用学习总结
  18. 教你利用clustalw和blat解决接头问题问题
  19. 学习笔记42—Win7下安装Linux双系统
  20. Python 搭建一个简易QQ机器人

热门文章

  1. 基于Flink+ClickHouse构建实时游戏数据分析最佳实践
  2. 阿里巴巴招聘最全集合帖:宣讲会+岗位+30篇面试宝典来啦
  3. 揭密微信《跳一跳》小游戏那些外挂
  4. ASP.NET MVC项目的创建
  5. 使用iframe+postMessage跨域操作和通信
  6. 用户 NT AUTHORITY\NETWORK SERVICE 登录失败解决方法
  7. 【转载】最大权闭合子图 【网络流】
  8. 我的第一个python web开发框架(2)——一个简单的小外包
  9. html5和css3的新特性
  10. webstorm快捷键说明