詳解Django的CSRF認證

發布日期:2019-10-25

1.csrf原理

csrf要求發送post,put或delete請求的時候,是先以get方式發送請求,服務端響應時會分配一個隨機字符串給客戶端,客戶端第二次發送post,put或delete請求時攜帶上次分配的隨機字符串到服務端進行校驗

2.Django中的CSRF中間件

首先,我們知道Django中間件作用于整個項目。

在一個項目中,如果想對全局所有視圖函數或視圖類起作用時,就可以在中間件中實現,比如想實現用戶登錄判斷,基于用戶的權限管理(RBAC)等都可以在Django中間件中來進行操作

Django內置了很多中間件,其中之一就是CSRF中間件

MIDDLEWARE_CLASSES = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.SessionAuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",]

上面第四個就是Django內置的CSRF中間件

3.Django中間件的執行流程

Django中間件中最多可以定義5個方法

process_requestprocess_responseprocess_viewprocess_exceptionprocess_template_response

Django中間件的執行順序

1.請求進入到Django后,會按中間件的注冊順序執行每個中間件中的process_request方法 如果所有的中間件的process_request方法都沒有定義return語句,則進入路由映射,進行url匹配 否則直接執行return語句,返回響應給客戶端2.依次按順序執行中間件中的process_view方法 如果某個中間件的process_view方法沒有return語句,則根據第1步中匹配到的URL執行對應的視圖函數或視圖類 如果某個中間件的process_view方法中定義了return語句,則后面的視圖函數或視圖類不會執行,程序會直接返回3.視圖函數或視圖類執行完成之后,會按照中間件的注冊順序逆序執行中間件中的process_response方法 如果中間件中定義了return語句,程序會正常執行,把視圖函數或視圖類的執行結果返回給客戶端 否則程序會拋出異常4.程序在視圖函數或視圖類的正常執行過程中 如果出現異常,則會執行按順序執行中間件中的process_exception方法 否則process_exception方法不會執行 如果某個中間件的process_exception方法中定義了return語句,則后面的中間件中的process_exception方法不會繼續執行了5.如果視圖函數或視圖類中使用render方法來向客戶端返回數據,則會觸發中間件中的process_template_response方法

4.Django CSRF中間件的源碼解析

Django CSRF中間件的源碼

class CsrfViewMiddleware(MiddlewareMixin): def _accept(self, request): request.csrf_processing_done = True return None def _reject(self, request, reason): logger.warning( "Forbidden (%s): %s", reason, request.path, extra={ "status_code": 403, "request": request, } ) return _get_failure_view()(request, reason=reason) def _get_token(self, request): if settings.CSRF_USE_SESSIONS: try: return request.session.get(CSRF_SESSION_KEY) except AttributeError: raise ImproperlyConfigured( "CSRF_USE_SESSIONS is enabled, but request.session is not " "set. SessionMiddleware must appear before CsrfViewMiddleware " "in MIDDLEWARE%s." % ("_CLASSES" if settings.MIDDLEWARE is None else "") ) else: try: cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME] except KeyError: return None csrf_token = _sanitize_token(cookie_token) if csrf_token != cookie_token: # Cookie token needed to be replaced; # the cookie needs to be reset. request.csrf_cookie_needs_reset = True return csrf_token def _set_token(self, request, response): if settings.CSRF_USE_SESSIONS: request.session[CSRF_SESSION_KEY] = request.META["CSRF_COOKIE"] else: response.set_cookie( settings.CSRF_COOKIE_NAME, request.META["CSRF_COOKIE"], max_age=settings.CSRF_COOKIE_AGE, domain=settings.CSRF_COOKIE_DOMAIN, path=settings.CSRF_COOKIE_PATH, secure=settings.CSRF_COOKIE_SECURE, httponly=settings.CSRF_COOKIE_HTTPONLY, ) patch_vary_headers(response, ("Cookie",)) def process_request(self, request): csrf_token = self._get_token(request) if csrf_token is not None: # Use same token next time. request.META["CSRF_COOKIE"] = csrf_token def process_view(self, request, callback, callback_args, callback_kwargs): if getattr(request, "csrf_processing_done", False): return None if getattr(callback, "csrf_exempt", False): return None if request.method not in ("GET", "HEAD", "OPTIONS", "TRACE"): if getattr(request, "_dont_enforce_csrf_checks", False): return self._accept(request) if request.is_secure(): referer = force_text( request.META.get("HTTP_REFERER"), strings_only=True, errors="replace" ) if referer is None: return self._reject(request, REASON_NO_REFERER) referer = urlparse(referer) if "" in (referer.scheme, referer.netloc): return self._reject(request, REASON_MALFORMED_REFERER) if referer.scheme != "https": return self._reject(request, REASON_INSECURE_REFERER) good_referer = ( settings.SESSION_COOKIE_DOMAIN if settings.CSRF_USE_SESSIONS else settings.CSRF_COOKIE_DOMAIN ) if good_referer is not None: server_port = request.get_port() if server_port not in ("443", "80"): good_referer = "%s:%s" % (good_referer, server_port) else: good_referer = request.get_host() good_hosts = list(settings.CSRF_TRUSTED_ORIGINS) good_hosts.append(good_referer) if not any(is_same_domain(referer.netloc, host) for host in good_hosts): reason = REASON_BAD_REFERER % referer.geturl() return self._reject(request, reason) csrf_token = request.META.get("CSRF_COOKIE") if csrf_token is None: return self._reject(request, REASON_NO_CSRF_COOKIE) request_csrf_token = "" if request.method == "POST": try: request_csrf_token = request.POST.get("csrfmiddlewaretoken", "") except IOError: pass if request_csrf_token == "": request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, "") request_csrf_token = _sanitize_token(request_csrf_token) if not _compare_salted_tokens(request_csrf_token, csrf_token): return self._reject(request, REASON_BAD_TOKEN) return self._accept(request) def process_response(self, request, response): if not getattr(request, "csrf_cookie_needs_reset", False): if getattr(response, "csrf_cookie_set", False): return response if not request.META.get("CSRF_COOKIE_USED", False): return response self._set_token(request, response) response.csrf_cookie_set = True return response

從上面的源碼中可以看到,CsrfViewMiddleware中間件中定義了process_request,process_view和process_response三個方法

先來看process_request方法

def _get_token(self, request): if settings.CSRF_USE_SESSIONS: try: return request.session.get(CSRF_SESSION_KEY) except AttributeError: raise ImproperlyConfigured( "CSRF_USE_SESSIONS is enabled, but request.session is not " "set. SessionMiddleware must appear before CsrfViewMiddleware " "in MIDDLEWARE%s." % ("_CLASSES" if settings.MIDDLEWARE is None else "") ) else: try: cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME] except KeyError: return None csrf_token = _sanitize_token(cookie_token) if csrf_token != cookie_token: # Cookie token needed to be replaced; # the cookie needs to be reset. request.csrf_cookie_needs_reset = True return csrf_tokendef process_request(self, request): csrf_token = self._get_token(request) if csrf_token is not None: # Use same token next time. request.META["CSRF_COOKIE"] = csrf_token

從Django項目配置文件夾中讀取CSRF_USE_SESSIONS的值,如果獲取成功,則從session中讀取CSRF_SESSION_KEY的值,默認為"_csrftoken",如果沒有獲取到CSRF_USE_SESSIONS的值,則從發送過來的請求中獲取CSRF_COOKIE_NAME的值,如果沒有定義則返回None。

再來看process_view方法

在process_view方法中,先檢查視圖函數是否被csrf_exempt裝飾器裝飾,如果視圖函數沒有被csrf_exempt裝飾器裝飾,則程序繼續執行,否則返回None。接著從request請求頭中或者cookie中獲取攜帶的token并進行驗證,驗證通過才會繼續執行與URL匹配的視圖函數,否則就返回403 Forbidden錯誤。

實際項目中,會在發送POST,PUT,DELETE,PATCH請求時,在提交的form表單中添加

{% csrf_token %}

即可,否則會出現403的錯誤

5.csrf_exempt裝飾器和csrf_protect裝飾器

5.1 基于Django FBV

在一個項目中,如果注冊起用了CsrfViewMiddleware中間件,則項目中所有的視圖函數和視圖類在執行過程中都要進行CSRF驗證。

此時想使某個視圖函數或視圖類不進行CSRF驗證,則可以使用csrf_exempt裝飾器裝飾不想進行CSRF驗證的視圖函數

from django.views.decorators.csrf import [email protected]_exempt def index(request): pass

也可以把csrf_exempt裝飾器直接加在URL路由映射中,使某個視圖函數不經過CSRF驗證

from django.views.decorators.csrf import csrf_exempt from users import views urlpatterns = [ url(r"^admin/", admin.site.urls), url(r"^index/",csrf_exempt(views.index)), ]

同樣的,如果在一個Django項目中,沒有注冊起用CsrfViewMiddleware中間件,但是想讓某個視圖函數進行CSRF驗證,則可以使用csrf_protect裝飾器

csrf_protect裝飾器的用法跟csrf_exempt裝飾器用法相同,都可以加上視圖函數上方裝飾視圖函數或者在URL路由映射中直接裝飾視圖函數

from django.views.decorators.csrf import csrf_exempt @csrf_protect def index(request): pass

或者

from django.views.decorators.csrf import csrf_protect from users import views urlpatterns = [ url(r"^admin/", admin.site.urls), url(r"^index/",csrf_protect(views.index)), ]

5.1 基于Django CBV

上面的情況是基于Django FBV的,如果是基于Django CBV,則不可以直接加在視圖類的視圖函數中了

此時有三種方式來對Django CBV進行CSRF驗證或者不進行CSRF驗證

方法一,在視圖類中定義dispatch方法,為dispatch方法加csrf_exempt裝飾器

from django.views.decorators.csrf import csrf_exemptfrom django.utils.decorators import method_decoratorclass UserAuthView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(StudentsView,self).dispatch(request,*args,**kwargs) def get(self,request,*args,**kwargs): pass def post(self,request,*args,**kwargs): pass def put(self,request,*args,**kwargs): pass def delete(self,request,*args,**kwargs): pass

方法二:為視圖類上方添加裝飾器

@method_decorator(csrf_exempt,name="dispatch")class UserAuthView(View): def get(self,request,*args,**kwargs): pass def post(self,request,*args,**kwargs): pass def put(self,request,*args,**kwargs): pass def delete(self,request,*args,**kwargs): pass

方式三:在url.py中為類添加裝飾器

from django.views.decorators.csrf import csrf_exempturlpatterns = [ url(r"^admin/", admin.site.urls), url(r"^auth/", csrf_exempt(views.UserAuthView.as_view())),]

csrf_protect裝飾器的用法跟上面一樣

, 1, 0, 9);

澳洲快乐8数据分析