目录
1.restful规范
在之前的代码习惯中,通常使用一个url对应一个视图函数,现在有了restful规范,就要遵循。简单来说,就是多个url对应一个视图,视图中封装了get,post,put,patch,delete等主要方法。相对于FBV来说更加简单,使用了CBV模式。
第一步:整体说restful规范是什么?
第二步:再详细说restful建议
1. https代替http,保证数据传输时安全。
2. 在url中一般要体现api标识,这样看到url就知道他是一个api。
http://www.luffycity.com/api/....(建议,因为他不会存在跨域的问题)
http://api.luffycity.com/....
假设:
前段:https://www.luffycity.com/home
后端:https://www.luffycity.com/api/
3. 在接口中要体现版本
http://www.luffycity.com/api/v1....(建议,因为他不会存在跨域的问题)
注意:版本还可以放在请求头中
http://www.luffycity.com/api/
accept: ...
4. restful也称为面向资源编程,视网络上的一切都是资源,对资源可以进行操作,所以一般资源都用名词。
http://www.luffycity.com/api/user/
5. 如果要加入一些筛选条件,可以添加在url中
http://www.luffycity.com/api/user/?page=1&type=9
6. 根据method不同做不同操作。
7. 返回给用户状态码
- 200,成功
- 300,301永久 /302临时
- 400,403拒绝 /404找不到
- 500,服务端代码错误
很多公司:
def get(self,request,*args,**kwargs):
result = {'code':1000,'data':None,'error':None}
try:
val = int('你好')
except Exception as e:
result['code'] = 10001
result['error'] = '数据转换错误'
return Response(result)
8. 返回值
GET http://www.luffycity.com/api/user/
[
{'id':1,'name':'alex','age':19},
{'id':1,'name':'alex','age':19},
]
POST http://www.luffycity.com/api/user/
{'id':1,'name':'alex','age':19}
GET http://www.luffycity.com/api/user/2/
{'id':2,'name':'alex','age':19}
PUT http://www.luffycity.com/api/user/2/
{'id':2,'name':'alex','age':19}
PATCH https//www.luffycity.com/api/user/2/
{'id':2,'name':'alex','age':19}
DELETE https//www.luffycity.com/api/user/2/
空
9. 操作异常时,要返回错误信息
{
error: "Invalid API key"
}
10. 对于下一个请求要返回一些接口:Hypermedia AP
{
'id':2,
'name':'alex',
'age':19,
'depart': "http://www.luffycity.com/api/user/30/"
}
2.初始drf
2.1简单认识
2.1.1什么是drf
drf是一个基于django开发的组件,本质是一个django的app。drf可以帮我们快速开发出一个遵循restful规范的程序。
2.1.2drf框架
记忆:请求到来之后,先执行视图的dispatch方法。
1. 视图
2. 版本处理
3. 认证
4. 权限
5. 节流(频率限制)
6. 解析器
7. 筛选器
8. 分页
9. 序列化
10. 渲染
2.1.3drf提供哪些功能
-视图类 2个视图基类(APIView,GenericAPIView)和一些其他视图类,帮助我们完成一些功能。
- 版本 "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
"ALLOWED_VERSIONS": ['v1', 'v2'],
- 认证 通过生成随机字符串保存在后端,前端携带与后端相比较,来实现认证 DEFAULT_AUTHENTICATION_CLASSES = ["类",] BaseAuthentication
- 权限 基于BasePermission
- 节流 throttle_classes = [AnonRateThrottle,]
-解析器,根据用户请求体格式不同进行数据解析,解析之后放在request.data中。在进行解析时候,drf会读取http请求头 content-type. 如果content-type:x-www-urlencoded,那么drf会根据 & 符号分割的形式去处理请 求体。 user=wang&age=19 如果content-type:application/json,那么drf会根据 json 形式去处理请求体。 {"user":"wang","age":19}
-序列化,可以对QuerySet进行序列化,也可以对用户提交的数据进行校验
-筛选器
-分页器
-渲染器,可以帮我们把json数据渲染到页面上进行友好的展示
Response
2.2使用
app注册
服务端会根据访问者使用工具的不同来区分,如果不添加rest_framework,使用postman等工具访问是没有问题的,但是使用浏览器访问会报错,因为会默认找到rest_framework下的静态html文件进行渲染,此时找不到,因此报错。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework'
]
路由
from django.conf.urls import url
from django.contrib import admin
from api import views
urlpatterns = [
url(r'^drf/info/', views.DrfInfoView.as_view()),
]
视图
from rest_framework.views import APIView
from rest_framework.response import Response
class DrfInfoView(APIView):
def get(self,request,*args,**kwargs):
data = {"":""}
return Response(data)
2.3自定义序列化
路由
from django.conf.urls import url
from django.contrib import admin
from api import views
urlpatterns = [
url(r'^drf/category/$', views.DrfCategoryView.as_view()), #get/post通过此url
url(r'^drf/category/(?P<pk>\d+)/$', views.DrfCategoryView.as_view()), #put/patch/delete通过此url
]
视图
from api import models
from django.forms.models import model_to_dict #序列化,将model转为dict类型
class DrfCategoryView(APIView):
def get(self,request,*args,**kwargs):
"""获取所有文章分类/单个文章分类"""
pk = kwargs.get('pk')
if not pk:
queryset = models.Category.objects.all().values('id','name')
data_list = list(queryset)
return Response(data_list)
else:
category_object = models.Category.objects.filter(id=pk).first()
data = model_to_dict(category_object)
return Response(data)
def post(self,request,*args,**kwargs):
"""增加一条分类信息"""
models.Category.objects.create(**request.data)
return Response('成功')
3.drf序列化
https://www.cnblogs.com/wuzhengzheng/p/10411785.html
3.1序列化
-序列化时instance=queryset,反序列化data参数写为data=request.data-进行序列化时,会将fields中所有字段进行序列化,反序列化时,也需要提交fields中的所有字段,如果不想提交某些字段,使用read_only参数。-在添加数据(post)请求时,可以新建一个serializer,可能有一些不需要的字段。
source:可以指定字段(name publish.name),可以指定方法。
SerializerMethodField搭配方法使用(get_字段名字)
read_only:反序列化时,不传
write_only:序列化时,不显示
路由
from django.conf.urls import url
from django.contrib import admin
from api import views
urlpatterns = [
url(r'^new/category/$', views.NewCategoryView.as_view()),
url(r'^new/category/(?P<pk>\d+)/$', views.NewCategoryView.as_view()),
]
序列化
# ModelSerializer跟表模型绑定序列化
from app import models
class BookSerializer(serializers.ModelSerializer):
class Meta:
# 指定表模型
model = models.Book
# 序列化所有的字段
fields = '__all__'
# 只想序列化title和id两个字段
# fields = ['title','id']
# exclude 和 fields不要连用
# excude = ['title]
# depth深度,表示链表的深度
#不建议使用:下几层要取得参数不能控制,官方建议不要超过10,个人建议不超过3
# depth = 1
# publish = serializers.CharField(source='publish.name')
# authors = serializers.SerializerMethodField()
# def get_authors(self, obj):
# author_list = obj.authors.all()
# author_ser = AuthorSer(instance=author_list, many=True)
# return author_ser.data
#为书名增加自定义需求
title = serializers.CharField(max_length=6,min_length=3,error_messages={'max_length':'太长了'})
#也有局部钩子函数
def validate_title(self,value):
from rest_framework import exceptions
print(value)
if value.startswith('tmd'):
raise exceptions.ValidationError('不能以tmd开头')
return value
三个方式
要跨表查看其他表中的字段,或者显示models.CharField(choice=((1,"发布"),(2,"删除"))
class ArticleSerializer(serializers.ModelSerializer):
#方式一:设置跨表深度
depth=1
#方式二:指定source
category = serializers.CharField(source="category.name")
status = serializers.CharField(source="get_status_display")
#方式三,使用钩子,和下面get_tags_txt结合使用
tags_txt = serializers.SerializerMethodField(read_only=True)
class Meta:
model = models.Article
fields = ["id","title","summary","content","category","status","tags_txt"]
def get_tags_txt(self,obj): #obj为表对象
tag_list = []
for i in obj.tags.all():
tag_list.append({"id":i.id,"name":i.name})
return tag_list
查询
class ArticleView(APIView):
def get(self,request,*args,**kwargs):
pk = kwargs.get("pk")
if not pk:
queryset = models.Article.objects.all()
ser = ArticleSerializer(instance=queryset,many=True) #查询多个,many=True
return Response(ser.data)
else:
queryset = models.Article.objects.filter(id=pk).first()
ser = ArticleSerializer(instance=queryset,many=False) #查询一个,many=False
return Response(ser.data)
新增
def post(self,request,*args,**kwargs):
ser = ArticleSerializer(data=request.data)
if ser.is_valid():
ser.save()
return Response("添加成功!")
return Response(ser.errors)
添加
def post(self,request,*args,**kwargs):
ser = ArticleSerializer(data=request.data)
ser_detail = ArticleDetailSerializer(data=request.data)
if ser.is_valid() and ser_detail.is_valid():
#前端提交时没有author_id字段,在save时才添加,此处针对连表添加
article_object = ser.save(author_id=1)
ser_detail.save(article=article_object)
return Response('添加成功')
return Response('错误')
修改
def put(self,request,*args,**kwargs):
pk = kwargs.get("pk")
article_object = models.Article.objects.filter(id=pk).first()
ser = ArticleSerializer(instance=article_object,data=request.data) #局部更新添加partial=True,所有要反序列化的字段都不是必填的
if ser.is_valid():
ser.save()
return Response(ser.data)
return Response(ser.errors)
3.2反序列化
在自己定义的类中重写create方法
class BookSerializer(serializers.Serializer):
... ...
def create(self, validated_data):
res = models.Book.objects.create(**validated_data)
return res
对应的view.py
class Books(APIView):
# 使用继承了Serializers序列化类的对象,反序列化
def post(self,request):
response_dic = {'code': 100, 'msg': '添加成功!'}
# 实例化产生一个序列化类的对象,data是要反序列化的字典
booker = BookSerializer(data=request.data)
if booker.is_valid():
# 清洗通过的数据,通过create方法进行保存
res = booker.create(booker.validated_data)
return Response(response_dic)
4.筛选
url访问
https://www.cnblogs.com/article/?category=1
自定义筛选功能MyFilterBackend
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from . import models
from rest_framework.filters import BaseFilterBackend
class MyFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
val = request.query_params.get('cagetory')
return queryset.filter(category_id=val)
应用
class IndexView(APIView):
def get(self, request, *args,**kwargs):
# http://www.xx.com/cx/index/?category=1
# models.News.objects.filter(category=1)
# http://www.xx.com/cx/index/?category=1
queryset = models.News.objects.all()
obj = MyFilterBackend()
obj.filter_queryset(request,queryset,self)
return Response('...')
视图
#views.py中
from rest_framework.filters import BaseFilterBackend
#自定义筛选器
class MyFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
val = request.query_params.get("category")
return queryset.filter(category=val)
#视图
class ArticleView(ListAPIView,CreateAPIView):
queryset = models.Article.objects.all()
serializer_class = ArticleSerials
filter_backends = [MyFilterBackend,]
5.分页
5.1介绍
drf默认集成了几个分页类
from rest_framework.pagination import BasePagination,PageNumberPagination,LimitOffsetPagination,CursorPagination
自己写类继承这些,只需要配置某些参数
class GoodsPagination(PageNumberPagination):
"""
分页
"""
page_size = 10
page_size_query_param = 'page_size'
page_query_param = 'page'
max_page_size = 100
5.2PageNumberPagination
url访问
http:127.0.0.1:8000/?page=1
配置settings.py,在配置drf组件都要写在REST_FRAMEWORK字典中
REST_FRAMEWORK = { "PAGE_SIZE":2 }
views中视图函数
def get(self,request,*args,**kwargs):
queryset = models.Article.objects.all()
# 方式一:仅数据
"""
page_object = PageNumberPagination()
result = page_object.paginate_queryset(queryset,request,self)
ser = PageArticleSerializer(instance=result,many=True)
return Response(ser.data)
"""
# 方式二:数据 + 分页信息
"""
page_object = PageNumberPagination()
result = page_object.paginate_queryset(queryset, request, self)
ser = PageArticleSerializer(instance=result, many=True)
return page_object.get_paginated_response(ser.data)
"""
# 方式三:数据 + 部分分页信息
"""
page_object = PageNumberPagination()
result = page_object.paginate_queryset(queryset, request, self)
ser = PageArticleSerializer(instance=result, many=True)
return Response({'count':page_object.page.paginator.count,'result':ser.data})
"""
return Response(ser.data)
配置
1.settings中配置
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination", #默认使用PagenumberPagination
"PAGE_SIZE":2, #每页显示数量
}
2.views.py中
from rest_framework.pagination import PageNumberPagination
class ArticleView(ListAPIView,CreateAPIView):
queryset = models.Article.objects.all()
serializer_class = ArticleSerials
pagination_class = PageNumberPagination
5.3LimitOffffsetPagination
url访问
http://127.0.0.1:8000/?limit=4&offset=0
limit多少条 offset后面多少页
from rest_framework.pagination import PageNumberPagination
from rest_framework.pagination import LimitOffsetPagination
from rest_framework import serializers
class HulaLimitOffsetPagination(LimitOffsetPagination):
max_limit = 2
class PageArticleView(APIView):
def get(self,request,*args,**kwargs):
queryset = models.Article.objects.all()
page_object = HulaLimitOffsetPagination()
result = page_object.paginate_queryset(queryset, request, self)
ser = PageArticleSerializer(instance=result, many=True)
return Response(ser.data)
6.视图
2个视图基类
6.1APIView
APIView是drf中所有视图的基类,继承自django的view类
APIView和view的不同之处:
传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象;
视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式;
任何APIException异常都会被捕获到,并且处理成合适的响应信息;
在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
支持定义的类属性:
authentication_classes 列表或元祖,身份认证类
permissoin_classes 列表或元祖,权限检查类
throttle_classes 列表或元祖,流量控制类
在APIView
中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。
from rest_framework.views import APIView
from rest_framework.response import Response
# url(r'^students/$', views.StudentsAPIView.as_view()),
class StudentsAPIView(APIView):
def get(self, request):
data_list = Student.objects.all()
serializer = StudentModelSerializer(instance=data_list, many=True)
return Response(serializer.data)
6.2GenericAPIView[通用视图类]
继承自APIView主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类
支持定义的类属性:
serializer_class 指明视图使用的序列化器
queryset 指明使用的数据查询集
pagination_class 指明分页控制类
filter_backends 指明过滤控制后端
支持的方法:
get_serializer_class(self)
当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在get_serializer_class方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了
get_serializer(self, args, *kwargs)
返回序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。
get_queryset(self)
返回视图使用的查询集,主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回queryset属性
get_object(self)
返回详情视图所需的模型类数据对象,主要用来提供给Mixin扩展类使用
5个视图扩展类
ListModelMixin
列表视图扩展类,提供list(request, *args, **kwargs)方法快速实现列表视图返回200状态码。
该Mixin的list方法会对数据进行过滤和分页
CreateModelMixin
创建视图扩展类,提供create(request, *args, **kwargs)方法快速实现创建资源的视图,成功返回201状态码。
如果序列化器对前端发送的数据验证失败,返回400错误。
RetrieveModelMixin
详情视图扩展类,提供retrieve(request, *args, **kwargs)方法,可以快速实现返回一个存在的数据对象。
如果存在,返回200, 否则返回404
UpdateModelMixin
更新视图扩展类,提供update(request, *args, **kwargs)方法,可以快速实现更新一个存在的数据对象。
同时也提供partial_update(request, *args, **kwargs)方法,可以实现局部更新。
成功返回200,序列化器校验数据失败时,返回400错误
DestroyModelMixin
删除视图扩展类,提供destroy(request, *args, **kwargs)方法,可以快速实现删除一个存在的数据对象。
成功返回204,不存在返回404。
GenericAPIView的视图子类
CreateAPIView
提供 post 方法
继承自: GenericAPIView、CreateModelMixin
ListAPIView
提供 get 方法
继承自:GenericAPIView、ListModelMixin
RetrieveAPIView
提供 get 方法
继承自: GenericAPIView、RetrieveModelMixin
DestoryAPIView
提供 delete 方法
继承自:GenericAPIView、DestoryModelMixin
UpdateAPIView
提供 put 和 patch 方法
继承自:GenericAPIView、UpdateModelMixin
RetrieveUpdateAPIView
提供 get、put、patch方法
继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
整体流程
1.发送get请求,会调用ListAPIView中的get方法
2.执行list方法,先去ArticleView中找,找不到,直到找到父类ListModelMixin中的list方法
3.queryset = self.filter_queryset(self.get_queryset())先找ArticleView中的filter_queryset和get_queryset()方法,找不到,直到找到GenericAPIView中有这2中方式,get_queryset()中queryset = self.queryset,从ArticleView中找self.queryset,找不到报错,找到了将其queryset传给此类中的queryset变量,然后将其值返回给filter_querset方法,最后将filter_queryset的返回值赋值给3步中的queryset,在list方法中执行序列化
使用
class TagSer(serializers.ModelSerializer):
class Meta:
model = models.Tag
fields = "__all__"
class TagView(ListAPIView,CreateAPIView):
queryset = models.Tag.objects.all()
serializer_class = TagSer
def get_serializer_class(self): #重写GenericAPIView中的方法
# self.request
# self.args
# self.kwargs
if self.request.method == 'GET':
return TagSer
elif self.request.method == 'POST':
return OtherTagSer
def perform_create(self,serializer):
serializer.save(author=1)
7.版本
使用思路
在视图类中配置 versioning_class =注意这是单数形式,只能配置一个类
实现 determine_version 方法
全局配置 DEFAULT_VERSION ALLOWED_VERSIONS VERSION_PARAM
在 initial 中首先执行 determine_version,它里面会生成获取版本的对象以及版本。
获取版本:request.version
获取处理版本的对象:request.versioning_scheme
反向生成 url :url = request.versioning_scheme.reverse(viewname='<url 的别名>', request=request)
使用(局部)
url中写version
url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
视图中应用
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.versioning import URLPathVersioning
class OrderView(APIView):
versioning_class = URLPathVersioning
def get(self,request,*args,**kwargs):
print(request.version)
print(request.versioning_scheme)
return Response('...')
def post(self,request,*args,**kwargs):
return Response('post')
settings中配置
REST_FRAMEWORK = {
"PAGE_SIZE":2,
"DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
"ALLOWED_VERSIONS":['v1','v2'], #允许的版本
'VERSION_PARAM':'version' #默认参数
}
使用(全局)
url中写version
url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
url(r'^(?P<version>\w+)/users/$', users_list, name='users-list'),
视图中应用
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.versioning import URLPathVersioning
class OrderView(APIView):
def get(self,request,*args,**kwargs):
print(request.version)
print(request.versioning_scheme)
return Response('...')
def post(self,request,*args,**kwargs):
return Response('post')
settings配置
REST_FRAMEWORK = {
"PAGE_SIZE":2,
"DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", #
"ALLOWED_VERSIONS":['v1','v2'],
'VERSION_PARAM':'version'
}
8.认证
8.1普通认证
先来登录功能,登录成功后将随机字符串加入Userinfo表的token字段中
表
class UserInfo(models.Model):
""" 用户表 """
username = models.CharField(verbose_name='用户名',max_length=32)
password = models.CharField(verbose_name='密码',max_length=64)
token = models.CharField(verbose_name='token',max_length=64,null=True,blank=True)
LoginView登录视图的实现
from rest_framework.views import APIView
from rest_framework.response import Response
from api import models
import uuid
class LoginView(APIView):
"""
登录接口
"""
def post(self,request,*args,**kwargs):
user_obj = models.UserInfo.objects.filter(**request.data).first()
if not user_obj:
return Response("登录失败!")
random_string = str(uuid.uuid4())
# 登录成功后将token添加到数据库,且返回给前端
user_obj.token = random_string
user_obj.save()
return Response(random_string)
在之后写的其他视图函数中,某些视图函数需要验证用户是否登录,就用到drf提供的BaseAuthentication认证功能。
先来单独写认证组件,需要用到的视图函数直接调用即可
from rest_framework.authentication import BaseAuthentication
from api import models
class LuffyAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
if not token:
return (None,None)
user_obj = models.UserInfo.objects.filter(token=token).first()
if user_obj:
return (user_obj,token) #参数一给request.user,参数二给request.auth
return (None,None)
需要用到认证功能的函数如下,调用方式如下:
from rest_framework.views import APIView
from rest_framework.response import Response
from api.extension.auth import LuffyAuthentication
class CommentView(APIView):
#authentication_classes = [] 在settings中设置了全局认证,但此处不需,只需设置为空列表即可
authentication_classes = [LuffyAuthentication,]
def get(self,request,*args,**kwargs):
print(request.user) #request.user得到user object对象,只有request.user才会调用认证
print(request.auth) #得到token值
return Response("获取所有评论")
def post(self,request,*args,**kwargs):
if request.user:
pass
如果你有100个视图想要用认证组件,就需要在全局settings.py中设置了
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES":["api.extension.auth.LuffyAuthentication",]
}
源码分析(从认证功能函数开始)
1.当有请求发来时,会先执行APIView中的dispath方法,dispath方法中会执行initialize_request(request, *args, **kwargs),进行老的request的封装。在封装过程中authenticators=self.get_authenticators(),此时执行get_authenticators(),进去此方法会看到return [auth() for auth in self.authentication_classes],因为自己写的类中有authentication_class变量,此时authenticators=[LuffyAuthentication(),]
2.
当执行到get函数时,也是先进APIView的dispatch()方法,然后进入request的封装函数中,查询def user(self)方法,找到其中的self._authenticate()方法并执行,可以看到 user_auth_tuple = authenticator.authenticate(self),执行自己定义组件中的authenticate方法,返回(a,b)元组,然后self.user, self.auth = 此元组,从而得到request.user等于值1,request.auth等于值2
源码大体流程
当用户发来请求时,找到认证的所有类并实例化成为对象列表,然后将对象列表封装到新的request对象中。
以后在视同中调用request.user
在内部会循环认证的对象列表,并执行每个对象的authenticate方法,该方法用于认证,他会返回两个值分别会赋值给
request.user/request.auth
8.2jwt认证
8.2.1jwt实现原理及其优势
jwt的实现原理:
- 用户登录成功之后,会给前端返回一段token。
- token是由.分割的三段组成。
- 第一段:类型和算法信心
- 第二段:用户信息+超时时间
- 第三段:hs256(前两段拼接)加密 + base64url
- 以后前端再次发来信息时
- 超时验证
- token合法性校验
优势:
- token只在前端保存,后端只负责校验。
- 内部集成了超时时间,后端可以根据时间进行校验是否超时。
- 由于内部存在hash256加密,所以用户不可以修改token,只要一修改就认证失败。
8.2.2使用
app中注册 rest_framework_jwt
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api.apps.ApiConfig',
'rest_framework',
'rest_framework_jwt'
]
登录视图实现生成随机字符串
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
from api import models
from rest_framework_jwt.views import VerifyJSONWebToken
from rest_framework.throttling import AnonRateThrottle,BaseThrottle
class LoginView(APIView):
authentication_classes = []
def post(self,request,*args,**kwargs):
user = models.UserInfo.objects.filter(username=request.data.get("username"),password=request.data.get("password")).first()
if not user:
return Response({"code":10001,"error":"用户名或密码错误"})
#
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
payload = jwt_payload_handler(user)
#
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
token = jwt_encode_handler(payload)
return Response({"code":10000,"data":token})
认证视图
import jwt
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from rest_framework_jwt.settings import api_settings
from api import models
class HulaQueryParamAuthentication(BaseAuthentication):
def authenticate(self, request):
"""
# raise Exception(), 不在继续往下执行,直接返回给用户。
# return None ,本次认证完成,执行下一个认证
# return ('x',"x"),认证成功,不需要再继续执行其他认证了,继续往后权限、节流、视图函数
"""
token = request.query_params.get('token')
if not token:
raise exceptions.AuthenticationFailed({'code':10002,'error':"登录成功之后才能操作"})
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise exceptions.AuthenticationFailed({'code':10003,'error':"token已过期"})
except jwt.DecodeError:
raise exceptions.AuthenticationFailed({'code':10004,'error':"token格式错误"})
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed({'code':10005,'error':"认证失败"})
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
username = jwt_get_username_from_payload(payload)
user_object = models.UserInfo.objects.filter(username=username).first()
return (user_object,token)
settings配置(全局配置)
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES":["api.extension.auth.HulaQueryParamAuthentication"],
}
如果不想使用此认证,在视图函数中添加以下代码
def get_authenticators(self):
if self.request.method == "GET":
return []
elif self.request.method == "POST":
return super().get_authenticators()
9.权限
要用权限需要drf中的BasePermission类,先来自定义权限组件
from rest_framework.permissions import BasePermission
from rest_framework import exceptions
class MyPermission(BasePermission):
message = {'code': 10001, 'error': '你没权限'} #has_permission返回false时会抛出此异常
def has_permission(self, request, view): #针对所有请求
if request.user:
return True
return False
def has_object_permission(self, request, view, obj): #针对有pk值的请求(例RetrieveAPIView)
return False
视图函数中使用上定义的权限组件
from rest_framework.views import APIView
from rest_framework.response import Response
from api.extension.permission import LuffyPermission
class CommentView(APIView):
permission_classes = [] #如果在settings设置了全局权限,但此视图不想使用
permission_classes = [LuffyPermission,]
def get(self,request,*args,**kwargs):
return Response("获取所有评论")
def post(self,request,*args,**kwargs):
if request.user:
pass
settings配置全局权限
REST_FRAMEWORK = {
DEFAULT_PERMISSION_CLASSES:"api.extension.permission.LuffyPermission"
}
源码分析
class APIView(View):
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
def dispatch(self, request, *args, **kwargs): #1执行dispath方法
#2 封装request对象
self.initial(request, *args, **kwargs)
通过反射执行视图中的方法
def initial(self, request, *args, **kwargs):
# 权限判断
self.check_permissions(request)
def check_permissions(self, request):
# [对象,对象,]
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(request, message=getattr(permission, 'message', None))
def permission_denied(self, request, message=None):
if request.authenticators and not request.successful_authenticator:
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied(detail=message)
def get_permissions(self):
return [permission() for permission in self.permission_classes]
class UserView(APIView):
permission_classes = [MyPermission, ]
def get(self,request,*args,**kwargs):
return Response('user')
源码分析
1.请求过来,先走APIView中的dispatch方法,执行self.initialize_request()方法进行老的request的封装,然后执行initial方法,进行认证和权限处理:
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
2.进入check_permissions方法:for循环遍历自己定义的permission_classes,检查每个对象的has_permission()方法,如果返回false,抛出异常,返回true,继续通过反射执行dispatch方法中的请求分发
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request, message=getattr(permission, 'message', None)
)
10.频率限制
频率限制在认证、权限之后
-
知识点
{ throttle_anon_1.1.1.1:[100121340,], 1.1.1.2:[100121251,100120450,] } 限制:60s能访问3次 来访问时: 1.获取当前时间 100121280 2.100121280-60 = 100121220,小于100121220所有记录删除 3.判断1分钟以内已经访问多少次了? 4 4.无法访问 停一会 来访问时: 1.获取当前时间 100121340 2.100121340-60 = 100121280,小于100121280所有记录删除 3.判断1分钟以内已经访问多少次了? 0 4.可以访问
如何实现频率限制
匿名用户:用ip作为唯一标记(缺点:如果用户使用代理ip,无法做到真正的限制)
登录用户:用用户名或者id作为标识
具体实现
首先,django会在缓存中生成如下数据结构,用来存储用户访问的时间戳
throttle_匿名标识(anon)_用户ip:[时间戳1,时间戳2...]
每次用户登录时:
1.获取当前时间戳
2.当前时间戳-60(如3/60s,一分钟内3次),循环数据结构中时间戳B,将得到值A和B比较,B<A,则删除B
3.判断数据结构中列表的个数,如果小于3,则可以访问
使用
#settings配置:
REST_FRAMEWORK={
"DEFAULT_THROTTLE_RATES":{
"anon":'3/m' #一分钟3次, 3/h 3/d
}
}
#views函数中使用
from rest_framework.throttling import AnonRateThrottle
class ArticleView(APIView):
throttle_classes = [AnonRateThrottle,]
源码剖析
请求发来,先执行dispatch方法,从dispatch方法中进入inital函数。先后执行到check_throttles()方法,在此看到,会执行对象的allow_request()方法
def initial(self, request, *args, **kwargs):
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
def check_throttles(self, request):
throttle_durations = []
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
def allow_request(self, request, view):
if self.rate is None:
return True
# 获取请求用户的ip
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
# 根据ip获取所有访问记录,得到的一个列表
self.history = self.cache.get(self.key, [])
self.now = self.timer()
# Drop any requests from the history which have now passed the
# throttle duration
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests: #num_requests自己规定的访问次数
return self.throttle_failure()
return self.throttle_success()
总结
# 如何实现的评率限制
- 匿名用户,用IP作为用户唯一标记,但如果用户换代理IP,无法做到真正的限制。
- 登录用户,用用户名或用户ID做标识。
具体实现:
在django的缓存中 = {
throttle_anon_1.1.1.1:[100121340,],
1.1.1.2:[100121251,100120450,]
}
限制:60s能访问3次
来访问时:
1.获取当前时间 100121280
2.100121280-60 = 100121220,小于100121220所有记录删除
3.判断1分钟以内已经访问多少次了? 4
4.无法访问
停一会
来访问时:
1.获取当前时间 100121340
2.100121340-60 = 100121280,小于100121280所有记录删除
3.判断1分钟以内已经访问多少次了? 0
4.可以访问
组件参考:https://www.jianshu.com/p/c16f8786e9f7
11.跨域
11.1跨域
由于浏览器具有“同源策略”的限制,导致 `发送ajax请求` + `跨域` 存在无法获取数据。
如果在同一个域下发送ajax请求,浏览器的同源策略不会阻止。
如果在不同域下发送ajax,浏览器的同源策略会阻止。
总结
- 域相同,永远不会存在跨域。
- crm,非前后端分离,没有跨域。
- 路飞学城,前后端分离,没有跨域(之前有,现在没有)。
- 域不同时,才会存在跨域。
- l拉勾网,前后端分离,存在跨域(设置响应头解决跨域)
11.2解决跨域:CORS
本质在数据返回值设置响应头
from django.shortcuts import render,HttpResponse
def json(request):
response = HttpResponse("JSONasdfasdf")
response['Access-Control-Allow-Origin'] = "*"
return response
11.3跨域时,发送了2次请求?
11.3.1在跨域时,发送的请求会分为两种:
简单请求,发一次请求
设置响应头就可以解决
from django.shortcuts import render,HttpResponse
def json(request):
response = HttpResponse("JSONasdfasdf")
response['Access-Control-Allow-Origin'] = "*"
return response
复杂请求,发两次请求
- 预检
- 请求
@csrf_exempt
def put_json(request):
response = HttpResponse("JSON复杂请求")
if request.method == 'OPTIONS':
# 处理预检
response['Access-Control-Allow-Origin'] = "*"
response['Access-Control-Allow-Methods'] = "PUT"
return response
elif request.method == "PUT":
return response
条件:
1、请求方式:HEAD、GET、POST
2、请求头信息:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type 对应的值是以下三个中的任意一个
application/x-www-form-urlencoded
multipart/form-data
text/plain
注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
11.3.2总结
1. 由于浏览器具有“同源策略”的限制,所以在浏览器上跨域发送Ajax请求时,会被浏览器阻止。
2. 解决跨域
- 不跨域
- CORS(跨站资源共享,本质是设置响应头来解决)。
- 简单请求:发送一次请求
- 复杂请求:发送两次请求