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