十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
如何在Django中實(shí)現(xiàn)CSRF認(rèn)證?針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。

1.csrf原理
csrf要求發(fā)送post,put或delete請(qǐng)求的時(shí)候,是先以get方式發(fā)送請(qǐng)求,服務(wù)端響應(yīng)時(shí)會(huì)分配一個(gè)隨機(jī)字符串給客戶端,客戶端第二次發(fā)送post,put或delete請(qǐng)求時(shí)攜帶上次分配的隨機(jī)字符串到服務(wù)端進(jìn)行校驗(yàn)
2.Django中的CSRF中間件
首先,我們知道Django中間件作用于整個(gè)項(xiàng)目。
在一個(gè)項(xiàng)目中,如果想對(duì)全局所有視圖函數(shù)或視圖類起作用時(shí),就可以在中間件中實(shí)現(xiàn),比如想實(shí)現(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', ]
上面第四個(gè)就是Django內(nèi)置的CSRF中間件
3.Django中間件的執(zhí)行流程
Django中間件中最多可以定義5個(gè)方法
process_request
process_response
process_view
process_exception
process_template_response
Django中間件的執(zhí)行順序
1.請(qǐng)求進(jìn)入到Django后,會(huì)按中間件的注冊(cè)順序執(zhí)行每個(gè)中間件中的process_request方法
如果所有的中間件的process_request方法都沒有定義return語句,則進(jìn)入路由映射,進(jìn)行url匹配
否則直接執(zhí)行return語句,返回響應(yīng)給客戶端
2.依次按順序執(zhí)行中間件中的process_view方法
如果某個(gè)中間件的process_view方法沒有return語句,則根據(jù)第1步中匹配到的URL執(zhí)行對(duì)應(yīng)的視圖函數(shù)或視圖類
如果某個(gè)中間件的process_view方法中定義了return語句,則后面的視圖函數(shù)或視圖類不會(huì)執(zhí)行,程序會(huì)直接返回
3.視圖函數(shù)或視圖類執(zhí)行完成之后,會(huì)按照中間件的注冊(cè)順序逆序執(zhí)行中間件中的process_response方法
如果中間件中定義了return語句,程序會(huì)正常執(zhí)行,把視圖函數(shù)或視圖類的執(zhí)行結(jié)果返回給客戶端
否則程序會(huì)拋出異常
4.程序在視圖函數(shù)或視圖類的正常執(zhí)行過程中
如果出現(xiàn)異常,則會(huì)執(zhí)行按順序執(zhí)行中間件中的process_exception方法
否則process_exception方法不會(huì)執(zhí)行
如果某個(gè)中間件的process_exception方法中定義了return語句,則后面的中間件中的process_exception方法不會(huì)繼續(xù)執(zhí)行了
5.如果視圖函數(shù)或視圖類中使用render方法來向客戶端返回?cái)?shù)據(jù),則會(huì)觸發(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三個(gè)方法
先來看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項(xiàng)目配置文件夾中讀取 CSRF_USE_SESSIONS 的值,如果獲取成功,則 從session中讀取CSRF_SESSION_KEY的值 ,默認(rèn)為 '_csrftoken' ,如果沒有獲取到 CSRF_USE_SESSIONS 的值,則從發(fā)送過來的請(qǐng)求中獲取 CSRF_COOKIE_NAME 的值,如果沒有定義則返回None。
再來看process_view方法
在process_view方法中,先檢查視圖函數(shù)是否被 csrf_exempt 裝飾器裝飾,如果視圖函數(shù)沒有被csrf_exempt裝飾器裝飾,則程序繼續(xù)執(zhí)行,否則返回None。接著從request請(qǐng)求頭中或者cookie中獲取攜帶的token并進(jìn)行驗(yàn)證,驗(yàn)證通過才會(huì)繼續(xù)執(zhí)行與URL匹配的視圖函數(shù),否則就返回 403 Forbidden 錯(cuò)誤。
實(shí)際項(xiàng)目中,會(huì)在發(fā)送POST,PUT,DELETE,PATCH請(qǐng)求時(shí),在提交的form表單中添加
{% csrf_token %}即可,否則會(huì)出現(xiàn)403的錯(cuò)誤

5.csrf_exempt裝飾器和csrf_protect裝飾器
5.1 基于Django FBV
在一個(gè)項(xiàng)目中,如果注冊(cè)起用了 CsrfViewMiddleware 中間件,則項(xiàng)目中所有的視圖函數(shù)和視圖類在執(zhí)行過程中都要進(jìn)行CSRF驗(yàn)證。
此時(shí)想使某個(gè)視圖函數(shù)或視圖類不進(jìn)行CSRF驗(yàn)證,則可以使用 csrf_exempt 裝飾器裝飾不想進(jìn)行CSRF驗(yàn)證的視圖函數(shù)
from django.views.decorators.csrf import csrf_exempt @csrf_exempt def index(request): pass
也可以把csrf_exempt裝飾器直接加在URL路由映射中,使某個(gè)視圖函數(shù)不經(jīng)過CSRF驗(yàn)證
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)), ]
同樣的,如果在一個(gè)Django項(xiàng)目中,沒有注冊(cè)起用 CsrfViewMiddleware 中間件,但是想讓某個(gè)視圖函數(shù)進(jìn)行CSRF驗(yàn)證,則可以使用 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ù)中了
此時(shí)有三種方式來對(duì)Django CBV進(jìn)行CSRF驗(yàn)證或者不進(jìn)行CSRF驗(yàn)證
方法一,在視圖類中定義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())), ]
關(guān)于如何在Django中實(shí)現(xiàn)CSRF認(rèn)證問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。