Django: csrf防御机制
csrf攻击过程
1.用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
3.用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
4.网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
5.浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
在django防御csrf攻击
原理
在客户端页面上添加csrftoken, 服务器端进行验证,服务器端验证的工作通过'django.middleware.csrf.CsrfViewMiddleware'这个中间层来完成。在django当中防御csrf攻击的方式有两种, 1.在表单当中附加csrftoken 2.通过request请求中添加X-CSRFToken请求头。注意:Django默认对所有的POST请求都进行csrftoken验证,若验证失败则403错误侍候。
取消csrftoken验证
通过csrf_exempt, 来取消csrftoken验证,方式有两种。
在视图函数当中添加csrf_exempt装饰器
GET, POST, PUT, DELETE 正好可以对应我们 CRUD (Create, Read, Update, Delete) 四种数据操作
Django REST framework教程二: 请求和响应
请求对象(Request object)
REST framework引入了一个Request对象, 它继承自普通的HttpRequest, 但能够更加灵活的解析收到的请求。Request对象的核心功能,就是其中的request.data属性。这个属性跟request.POST相似,但对我们的Web API来说,更加的有用。
request.POST # 只能处理表单(form)数据,只能处理“POST”方法.
request.data # 处理任意数据.可以处理'POST', 'PUT' 和 'PATCH'方法.
响应对象(Response object)
REST framework 同时引入了Response对象,是一种TemplateResponse,它携带着纯粹的内容,通过内容协商(Content Negotiation)来决定,将以何种形式,返回给客户端。
return Response(data) #根据客户端的要求,把内容,生成对应的形式。
包装API视图(wrapping API views)
REST framework提供了两种编写API view的封装:
(1)使用@api_view装饰器,基于方法的视图
(2)继承APIView类,基于类的视图
这些视图封装,提供了些许的功能,比如:确保你的视图能够收到Request实例;还有,将内容赋予Response对象,使得内容协商(content negotiation) 可以正常的运作。
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@api_view(['GET', 'POST’])
def snippet_list(request):
"""
列出所有的代码片段(snippets),或者创建一个代码片段(snippet)
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response (serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer (data = request.data)
if serializer.is_valid():
serializer.save()
return Response (serializer.data, status=status.HTTP_201_CREATED)
return Response (serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET', 'PUT', 'DELETE’])
def snippet_detail(request, pk):
"""
读取, 更新 或 删除 一个代码片段实例(snippet instance)。
"""
try:
snippet = Snippet.objects.get (pk = pk)
except Snippet.DoesNotExist:
return Response (status = status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = SnippetSerializer (snippet)
return Response (serializer.data)
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data = request.data)
if serializer.is_valid():
serializer.save()
return Response (serializer.data)
return Response (serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
snippet.delete()
return Response (status=status.HTTP_204_NO_CONTENT)
在这里,我们已经不再明确地,解析/定义视图中 Request/Response的内容类型。request.data会自行处理输入的json请求,当然,也能处理别的格式。同样的,我们只需返回响应对象以及数据,REST framework会帮我们,将响应内容,渲染(render)成正确的格式。
为URLs添加可选的格式后缀
现在,我们的响应,不再硬性绑定在,某一种返回格式上,利用这点优势,我们可以为API端,添加格式的后缀。使用格式后缀,可以定制我们的URLs,使它明确的指向指定的格式,这意味着,我们的API可以处理一些URLs,类似这样的格式http://example.com/api/items/4/.json。
首先,需要添加一个format关键字参数,如下所示:
def snippet_list (request, format=None):
然后对urls.py文件,做些修改:
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = format_suffix_patterns(urlpatterns)
Django REST framework教程三: 基于类的视图
与其使用基于方法(function based)的视图,我们更加倾向使用基于类(class based)的视图。
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
列出所有代码片段(snippets), 或者新建一个代码片段(snippet).
"""
def get (self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer (snippets, many=True)
return Response (serializer.data)
def post (self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response (serializer.data, status=status.HTTP_201_CREATED)
return Response (serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class SnippetDetail (APIView):
"""
读取, 更新 or 删除一个代码片段(snippet)实例(instance).
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
returnResponse(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
returnResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P[0-9]+)/$', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
使用混入(mixins)
目前为止,我们所用的增删改查操作,在我们创建的,任何支持模型的视图里,都没有太大区别。这些通用的行为,在REST framework的混入(mixin)类中,都已经实现(implemented)了。
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
class SnippetList(mixins.ListModelMixin,mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
我们使用GenericAPIView创建了我们的视图,并且加入了ListModelMixin和CreateModelMixin
基本类提供了核心的功能,而混入(mixin)类提供了.list()和.create()行为。然后,我们显式地在get和post方法里面,放入对应的行动。非常简单,但目前够用。
class SnippetDetail(mixins.RetrieveModelMixin,mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
我们使用了GenericAPIView类提供了核心功能,而混入(mixin)类 提供了.retrieve(),.update()和.destroy()行为。
使用基于泛类(generic class)的视图
REST framework提供了一套已经实现了混入类的通用(generic)视图,我们可以使我们的views.py模块,更加瘦身!
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
Django REST framework的各种技巧——3.权限
权限的类型
(1)用户是否有访问某个api的权限
(2)用户对于相同的api不同权限看到不同的数据(其实一个filter)
(3)不同权限用户对于api的访问频次,其他限制等等
(4)假删除,各种级联假删除
基本讲解
首先在django中,group以及user都可以有很多的permission,一个user会有他自身permission+所有隶属group的permission。比如:user可能显示的有3个permission,但他隶属于3个组,每个组有2个不同的权限,那么他有3+2*3个权限。
permission会在api的models.py种统一建立,在Meta中添加对应的permission然后跑migrate数据库就会有新的权限。
由于django对每一个不同的Model都会建立几个基本的权限,我会在api/models.py里面单独建一个ModulePermission的Model,没有任何其他属性,就只有一个Meta class上面对应各种权限,这就是为了syncdb用的,另外还一个原因后面再说。
当前我们的API在编辑或者删除的时候没有任何限制,我们不希望有些人有高级的行为,确保:
(1)代码段始终与创建者相关联
(2)只允许授权的用户可以创建代码段
(3)只允许代码段创建者可以更新和删除
(4)没有认证的请求应该有一个完整的只读权限列表
添加用户信息在我们的models中
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
为我们user models添加serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets =serializers.PrimaryKeyRelatedField(many=True,queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id','username','snippets’)
因为'snippets'是User模型上的反向关系,所以在使用ModelSerializer类时,它不会被默认包含,因此我们需要为它添加一个显式字段。
from django.contrib.auth.models import User
from snippets.serializers import UserSerializer
class UserList(generics.ListAPIView):
queryset =User.objects.all()
serializer_class =UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset =User.objects.all()
serializer_class =UserSerializer
将代码段与用户关联
现在,如果我们创建了代码段,就无法将创建代码段的用户与代码段实例相关联。用户不作为序列化表示的一部分发送,而是传入请求的属性。
我们处理它的方式是通过在我们的代码片段视图上覆盖一个perform_create()方法,允许我们修改实例保存的方式,并处理在传入请求或请求的URL中隐含的任何信息。
在我们SnippetList 视图类中,添加下面的方法
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
当我们的serializer里create()方法被调用时,将自动添加owner字段和验证合法的请求数据
更新我们的seralizer
现在sippets创建的时候已经和我们的用户关联起来,让我们更新我们的SnippetSerializer,添加以下定义的字段:
owner= serializers.ReadOnlyField(source='owner.username’)
source参数用于控制那个属性被用来填充字段,并且可以指向序列化实例上的任何属性。 它也可以采用上面所示的虚线符号,在这种情况下,它将遍历给定的属性,与使用Django的模板语言类似的方式。
我们添加的字段是无类型的ReadOnlyField类,与其他类型的字段,例如CharField,BooleanField等相反。无类型的ReadOnlyField总是只读的,并且将用于序列化表示,但不会 用于在反序列化时更新模型实例。 我们也可以在这里使用CharField(read_only = True)
同时需要在meta class中添加owner字段
class Meta:
model = Snippet
fields = ('id','title','code','linenos','language','style','owner')
在views中添加请求权限
现在snippets 已经和我们的用户关联上了,我们需要确保仅仅通过验证的用户能够增删改
REST framework 包含有一些权限类,我们可以用来限制谁可以访问给定的视图,在这种情况下,首先我们查找IsAuthenticatedOrReadOnl,这将确保已验证的请求获得读写访问权限,并且未验证的请求获得只读访问权限。
然后在SnippetList和SnippetDetailview中添加下面这个属性
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
对象级别的权限
我们希望所有的代码片段对任何人都可见,但也确保只有创建代码片段的用户能够更新或删除它。为了实现这个,我们需要创建一个自定义权限
在snippets.app中,创建一个新的文件,名为permissions.py
from rest_framework import permissions
classIsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
"
读取权限对任何的request都适用,接下来将要添加GET,HEAD,OPTIONS请求权限
''
if request.method in permissions.SAFE_METHODS:
return True
return obj.owner == request.user
现在我们在SnippetDetail view中通过编辑permission_classes属性为snippet实例中添加自定义的权限
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)