restful api

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,)

推荐阅读更多精彩内容