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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容