詳解Django的CSRF認(rèn)證實現(xiàn)
什么是 CSRF
CSRF, Cross Site Request Forgery, 跨站點偽造請求。舉例來講,某個惡意的網(wǎng)站上有一個指向你的網(wǎng)站的鏈接,如果某個用戶已經(jīng)登錄到你的網(wǎng)站上了,那么當(dāng)這個用戶點擊這個惡意網(wǎng)站上的那個鏈接時,就會向你的網(wǎng)站發(fā)來一個請求,你的網(wǎng)站會以為這個請求是用戶自己發(fā)來的,其實呢,這個請求是那個惡意網(wǎng)站偽造的。
1.csrf原理
csrf要求發(fā)送post,put或delete請求的時候,是先以get方式發(fā)送請求,服務(wù)端響應(yīng)時會分配一個隨機字符串給客戶端,客戶端第二次發(fā)送post,put或delete請求時攜帶上次分配的隨機字符串到服務(wù)端進(jìn)行校驗
2.Django中的CSRF中間件
首先,我們知道Django中間件作用于整個項目。
在一個項目中,如果想對全局所有視圖函數(shù)或視圖類起作用時,就可以在中間件中實現(xiàn),比如想實現(xiàn)用戶登錄判斷,基于用戶的權(quán)限管理(RBAC)等都可以在Django中間件中來進(jìn)行操作
Django內(nèi)置了很多中間件,其中之一就是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內(nèi)置的CSRF中間件
3.Django中間件的執(zhí)行流程
Django中間件中最多可以定義5個方法
- process_request
- process_response
- process_view
- process_exception
- process_template_response
Django中間件的執(zhí)行順序
1.請求進(jìn)入到Django后,會按中間件的注冊順序執(zhí)行每個中間件中的process_request方法
如果所有的中間件的process_request方法都沒有定義return語句,則進(jìn)入路由映射,進(jìn)行url匹配
否則直接執(zhí)行return語句,返回響應(yīng)給客戶端
2.依次按順序執(zhí)行中間件中的process_view方法
如果某個中間件的process_view方法沒有return語句,則根據(jù)第1步中匹配到的URL執(zhí)行對應(yīng)的視圖函數(shù)或視圖類
如果某個中間件的process_view方法中定義了return語句,則后面的視圖函數(shù)或視圖類不會執(zhí)行,程序會直接返回
3.視圖函數(shù)或視圖類執(zhí)行完成之后,會按照中間件的注冊順序逆序執(zhí)行中間件中的process_response方法
如果中間件中定義了return語句,程序會正常執(zhí)行,把視圖函數(shù)或視圖類的執(zhí)行結(jié)果返回給客戶端
否則程序會拋出異常
4.程序在視圖函數(shù)或視圖類的正常執(zhí)行過程中
如果出現(xiàn)異常,則會執(zhí)行按順序執(zhí)行中間件中的process_exception方法
否則process_exception方法不會執(zhí)行
如果某個中間件的process_exception方法中定義了return語句,則后面的中間件中的process_exception方法不會繼續(xù)執(zhí)行了
5.如果視圖函數(shù)或視圖類中使用render方法來向客戶端返回數(shù)據(jù),則會觸發(fā)中間件中的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_token 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
從Django項目配置文件夾中讀取 CSRF_USE_SESSIONS 的值,如果獲取成功,則 從session中讀取CSRF_SESSION_KEY的值 ,默認(rèn)為 '_csrftoken' ,如果沒有獲取到 CSRF_USE_SESSIONS 的值,則從發(fā)送過來的請求中獲取 CSRF_COOKIE_NAME 的值,如果沒有定義則返回None。
再來看process_view方法
在process_view方法中,先檢查視圖函數(shù)是否被 csrf_exempt 裝飾器裝飾,如果視圖函數(shù)沒有被csrf_exempt裝飾器裝飾,則程序繼續(xù)執(zhí)行,否則返回None。接著從request請求頭中或者cookie中獲取攜帶的token并進(jìn)行驗證,驗證通過才會繼續(xù)執(zhí)行與URL匹配的視圖函數(shù),否則就返回 403 Forbidden 錯誤。
實際項目中,會在發(fā)送POST,PUT,DELETE,PATCH請求時,在提交的form表單中添加
{% csrf_token %}
即可,否則會出現(xiàn)403的錯誤
5.csrf_exempt裝飾器和csrf_protect裝飾器
5.1 基于Django FBV
在一個項目中,如果注冊起用了 CsrfViewMiddleware 中間件,則項目中所有的視圖函數(shù)和視圖類在執(zhí)行過程中都要進(jìn)行CSRF驗證。
此時想使某個視圖函數(shù)或視圖類不進(jìn)行CSRF驗證,則可以使用 csrf_exempt 裝飾器裝飾不想進(jìn)行CSRF驗證的視圖函數(shù)
from django.views.decorators.csrf import csrf_exempt @csrf_exempt def index(request): pass
也可以把csrf_exempt裝飾器直接加在URL路由映射中,使某個視圖函數(shù)不經(jīng)過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 中間件,但是想讓某個視圖函數(shù)進(jìn)行CSRF驗證,則可以使用 csrf_protect 裝飾器
csrf_protect裝飾器的用法跟csrf_exempt裝飾器用法相同 ,都可以加上視圖函數(shù)上方裝飾視圖函數(shù)或者在URL路由映射中直接裝飾視圖函數(shù)
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,則不可以直接加在視圖類的視圖函數(shù)中了
此時有三種方式來對Django CBV進(jìn)行CSRF驗證或者不進(jìn)行CSRF驗證
方法一,在視圖類中定義dispatch方法,為dispatch方法加csrf_exempt裝飾器
from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator class UserAuthView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(UserAuthView,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_exempt urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^auth/', csrf_exempt(views.UserAuthView.as_view())), ]
csrf_protect裝飾器的用法跟上面一樣
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
python3對接mysql數(shù)據(jù)庫實例詳解
這篇文章主要介紹了python3對接mysql數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04解決ImportError:DLL load failed while impo
在安裝pywin32后,可能會出現(xiàn)無法導(dǎo)入win32api的錯誤,一個有效的解決方案是運行pywin32_postinstall.py腳本,首先,打開cmd并切換到環(huán)境的Scripts文件夾,確保存在pywin32_postinstall.py文件2024-09-09淺談哪個Python庫才最適合做數(shù)據(jù)可視化
數(shù)據(jù)可視化是任何探索性數(shù)據(jù)分析或報告的關(guān)鍵步驟,目前有許多非常好的商業(yè)智能工具,比如Tableau、googledatastudio和PowerBI等,本文就詳細(xì)的進(jìn)行對比,感興趣的可以了解一下2021-06-06七個生態(tài)系統(tǒng)核心庫[python自學(xué)收藏]
無論你是想快速入手Python,還是想成為數(shù)據(jù)分析大神或者機器學(xué)習(xí)大佬,亦或者對Python代碼進(jìn)行優(yōu)化,本文的python庫都能為你提供一些幫助2021-08-08python中g(shù)etaddrinfo()基本用法實例分析
這篇文章主要介紹了python中g(shù)etaddrinfo()基本用法,實例分析了Python中使用getaddrinfo方法進(jìn)行IP地址解析的基本技巧,需要的朋友可以參考下2015-06-06Pytorch使用shuffle打亂數(shù)據(jù)的操作
這篇文章主要介紹了Pytorch使用shuffle打亂數(shù)據(jù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-05-05PyCharm中鼠標(biāo)懸停在函數(shù)上時顯示函數(shù)和幫助的解決方法
這篇文章主要介紹了PyCharm中鼠標(biāo)懸停在函數(shù)上時顯示函數(shù)和幫助,本文給大家分享問題解決方法,對PyCharm鼠標(biāo)懸停函數(shù)上顯示函數(shù)的解決方法感興趣的朋友跟隨小編一起看看吧2022-11-11