我的CSRF实现是否正确?基于Django+React/Redux的疑问
问题描述
我正在开发一个前端为业务界面、后端基于Django的网站,目前对Django的CSRF验证存在疑问:
每个请求都需要CSRF token,实际上是两个:一个由Cookie自动设置,另一个需作为POST参数提供。因此我开发了一个API接口用于返回token,再用该token发起其他请求。但这感觉像是守卫没密码不让进,却会主动告知密码,这样的机制到底能起到什么作用?
注:我是新手,希望这个问题不会太幼稚。
核心原理:防跨站伪造请求
你觉得这个机制矛盾,本质是没搞懂CSRF攻击的逻辑——攻击者是利用用户已登录目标站点的状态,诱导用户访问恶意页面,借助浏览器自动携带Cookie的特性,伪造用户身份发起非法请求。
举个实际场景:你登录了银行网站,浏览器里存着银行的登录Cookie,这时不小心点开钓鱼网站,它会偷偷向银行的转账接口发POST请求,浏览器会自动带上你的银行Cookie,如果银行没做CSRF验证,就会把这个请求当成你发起的,执行转账操作。
而Django的CSRF机制就是从根源上切断这种攻击:
- 服务器给合法访问的用户浏览器设置一个CSRF Cookie,这个Cookie会在用户访问站点时自动被浏览器携带
- 同时要求所有POST请求必须携带和Cookie中一致的CSRF token(可以放在POST参数里,也可以放在
X-CSRFToken请求头中)
恶意站点为什么拿不到这个有效token?因为浏览器的同源策略:恶意站点的JS代码既读不到你目标站点的Cookie,也没法通过跨域AJAX请求获取目标站点返回的token(跨域请求默认不携带Cookie,就算配置了携带,恶意站点也无法读取响应里的token内容)。
你写的initialize接口是GET请求,只有当用户浏览器已经携带了合法的会话Cookie(也就是用户已经登录过,或者至少访问过你的站点,服务器已经给它分配了CSRF Cookie),才能拿到对应的有效token。恶意站点完全没法模拟这个场景,因为它拿不到用户的Cookie,自然也没法通过这个接口获取能通过验证的token。
代码优化建议
其实Django已经内置了更简便的处理方式,你不需要自己写initialize接口来返回token:
- 如果是服务端渲染模板,直接在表单里用
{% csrf_token %}标签,Django会自动把token注入到页面中,前端可以直接从DOM中获取 - 如果是单页应用,也可以直接读取浏览器Cookie里的
csrftoken字段,不用专门发请求。比如在React里可以通过解析document.cookie来提取这个值,省去initializeCsrf函数的编写
附上你的代码
Django后端代码
def initialize(request): """ 页面首次加载时由前端调用,返回所有POST请求需要的csrf_token(本接口用GET请求),以及当前请求关联的登录用户名。 """ data = dict() data['csrf_token'] = get_token(request) data['logged_in_user_name'] = auth.get_user(request).get_username() return HttpResponse(json.dumps(data), status = status.HTTP_200_OK)
React/Redux前端代码
var csrfToken = null as null | string; /** * 从服务器获取POST请求所需的CSRF token,同时获取登录用户名并更新到状态中。 * * @param dispatch 用于分发action更新store的函数。 */ export async function initializeCsrf(dispatch: Dispatch) { if (csrfToken) { return csrfToken; } try { const response = await fetch(`${Ernots.APIURLPrefix}initialize`, { method: 'GET', mode: 'cors', credentials: 'include', }); var json = await response.json(); csrfToken = json.csrf_token; dispatch(loginActions.setLoggedInUserName(json.logged_in_user_name)); dispatch(requestActions.setBackendUnreachable(false)); return csrfToken; } catch(e) { dispatch(requestActions.setBackendUnreachable(true)); } }
内容的提问来源于stack exchange,提问作者Tiiba

