?Django對CSRF的防御主要由django.middleware.csrf.CsrfViewMiddleware中間件來實現(xiàn)。通過在settings.py的MIDDLEWARE_CLASSES(新版的Django該項改為MIDDLEWARE)加上django.middleware.csrf.CsrfViewMiddleware來啟用這個中間件。通過Django源碼的django/middleware/csrf.py文件,調(diào)試程序來分析了Django CSRF防御流程。這篇文章記下我的理解。
版本說明:
Django Version:1.9.5
Python Version:2.7
Django CSRF防御具體流程
?首先要清楚Django是怎么驗證一個請求不是CSRF:Django會從請求頭cookie取csrftoken這一項的值,再從POST表單里取csrfmiddlewaretoken這一項(或從請求頭X-CSRFToken?。┑闹担ㄟ^比對兩者是否一致來判斷這個請求是不是非法,非法就返回403狀態(tài)碼。這里的“是否一致”,并不是判斷兩者的值是否相等,而是判斷_unsalt_cipher_token(csrfmiddlewaretoken)和_unsalt_cipher_token(csrftoken)是否相等,具體留在后面說。
?而生成csrftoken和csrfmiddlewaretoken的代碼在get_token(request)函數(shù)
def get_token(request):
if "CSRF_COOKIE" not in request.META:
csrf_secret = _get_new_csrf_string()
request.META["CSRF_COOKIE"] = _salt_cipher_secret(csrf_secret)
else:
csrf_secret = _unsalt_cipher_token(request.META["CSRF_COOKIE"])
request.META["CSRF_COOKIE_USED"] = True
return _salt_cipher_secret(csrf_secret)
這個函數(shù)會在渲染模板的時候調(diào)用,具體來說是由csrf context processor調(diào)用。
如果request.META["CRSF_COOKIE"]不存在,就調(diào)用_get_new_csrf_string()函數(shù)來生成一串隨機字符(32個字符,大小寫字母和數(shù)字),賦給csrf_secret,再調(diào)用_salt_cipher_secret(scrf_secret)生成64個字符的字符串賦給request.META["CSRF_COOKIE"],而這個request.META["CSRF_COOKIE"]之后用來設(shè)置COOKIE 的csrftoken。
最后的返回值_salt_cipher_secret(csrf_secret)就渲染到POST表單的csrfmiddlewaretoken。值得一提的是_salt_cipher_secret(csrf_secret)每次的返回值都不一樣,而csrf_secret == _unsalt_cipher_token(_salt_cipher_secret(csrf_secret))。
?總的來說,涉及到三個值,csrftoken、csrfmiddlewaretoken和csrf_secret,還有兩個函數(shù),_unsalt_cipher_token(token)和_salt_cipher_secret(token)。用圖來說明下這兩個過程:
- 生成
csrftoken和csrfmiddlewaretoken。 - 驗證
csrftoken和csrfmiddlewaretoken是否一致。

?那_unsalt_cipher_token和_salt_cipher_secret這兩個函數(shù)具體怎么實現(xiàn)呢?怎么做到_salt_cipher_secret(csrf_secret)每次返回的token值不同,調(diào)用_unsalt_cipher_token(token)就返回原來的csrf_secret?
?用圖來表示(簡化下,把csrf_secret長度改為3)
?

?上面的過程主要涉及到的數(shù)值運算就這兩條式子(下面的符號都表示一個整數(shù)):
1. Cipher = (Secret + Salt) mod N
2. (Cipher - Salt) mod N 會等于 Secret
其它
-
django/middleware/csrf.py文件里有個函數(shù):rotate_token(request),這個函數(shù)用來改變csrftoken這個COOKIE。 在用戶登錄后(是指django.contrib.auth這個組件的登錄)調(diào)用,主要從安全考慮,避免這個COOKIE跟登錄前的一樣。 如果自己實現(xiàn)的登錄邏輯,可以調(diào)用這個函數(shù)提高點安全性。 - 一般
csrftoken這個COOKIE是不會變的,除了第一點說的登錄,和不存在時重新生成一個。有時候會出現(xiàn)登錄后csrftoken失效的情況。官網(wǎng)一個FAQ - 為什么Django要把
csrftoken和csrfmiddlewaretoken設(shè)置成不相等,直接生成的時候讓它們相等,驗證的時候判斷是否相等不就好了?個人覺得這樣做有個好處,有時候csrftoken這個COOKIE前端不需要獲取,可以設(shè)置成HTTP ONLY,提高點安全性。 大家怎么看? - 從上面分析的算法來看,
csrftoken跟csrfmiddlewaretoken相同也可以通過CSRF驗證。所以在AJAX請求中,直接取csrftoken值加到請求中就好了。