Django CSRF防御具体流程
Django对CSRF的防御主要由django.middleware.csrf.CsrfViewMiddleware
中间件来实现。通过在settings.py
的MIDDLEWARE_CLASSES
(新版的Django该项改为MIDDLEWARE
)加上django.middleware.csrf.CsrfViewMiddleware
来启用这个中间件。通过Django源码的django/middleware/csrf.py
文件,调试程序来分析了Django CSRF防御流程。这篇文章记下我的理解。
版本说明:
Django Version:1.9.5
Python Version:2.7
Django CSRF防御具体流程
首先要清楚Django是怎么验证一个请求不是CSRF:Django会从请求头cookie取csrftoken
这一项的值,再从POST表单里取csrfmiddlewaretoken
这一项(或从请求头X-CSRFToken
取)的值,通过比对两者是否一致来判断这个请求是不是非法,非法就返回403状态码。这里的“是否一致”,并不是判断两者的值是否相等,而是判断_unsalt_cipher_token(csrfmiddlewaretoken)
和_unsalt_cipher_token(csrftoken)
是否相等,具体留在后面说。
而生成csrftoken
和csrfmiddlewaretoken
的代码在get_token(request)
函数
1 |
|
这个函数会在渲染模板的时候调用,具体来说是由csrf context processor
调用。
如果request.META["CRSF_COOKIE"]
不存在,就调用_get_new_csrf_string()
函数来生成一串随机字符(32个字符,大小写字母和数字),赋给csrf_secret
,再调用_salt_cipher_secret(scrf_secret)
生成64个字符的字符串赋给request.META["CSRF_COOKIE"]
,而这个request.META["CSRF_COOKIE"]
之后用来设置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
,还有两个函数,_unsalt_cipher_token(token)
和_salt_cipher_secret(token)
。用图来说明下这两个过程:
- 生成
csrftoken
和csrfmiddlewaretoken
。 - 验证
csrftoken
和csrfmiddlewaretoken
是否一致。
那_unsalt_cipher_token
和_salt_cipher_secret
这两个函数具体怎么实现呢?怎么做到_salt_cipher_secret(csrf_secret)
每次返回的token值不同,调用_unsalt_cipher_token(token)
就返回原来的csrf_secret
?
用图来表示(简化下,把csrf_secret
长度改为3)
上面的过程主要涉及到的数值运算就这两条式子(下面的符号都表示一个整数):
1 |
|
其它
django/middleware/csrf.py
文件里有个函数:rotate_token(request)
,这个函数用来改变csrftoken
这个COOKIE。 在用户登录后(是指django.contrib.auth
这个组件的登录)调用,主要从安全考虑,避免这个COOKIE跟登录前的一样。 如果自己实现的登录逻辑,可以调用这个函数提高点安全性。- 一般
csrftoken
这个COOKIE是不会变的,除了第一点说的登录,和不存在时重新生成一个。有时候会出现登录后csrftoken
失效的情况。官网一个FAQ - 为什么Django要把
csrftoken
和csrfmiddlewaretoken
设置成不相等,直接生成的时候让它们相等,验证的时候判断是否相等不就好了?个人觉得这样做有个好处,有时候csrftoken
这个COOKIE前端不需要获取,可以设置成HTTP ONLY,提高点安全性。 大家怎么看? - 从上面分析的算法来看,
csrftoken
跟csrfmiddlewaretoken
相同也可以通过CSRF验证。所以在AJAX请求中,直接取csrftoken
值加到请求中就好了。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!