什么是 CSRF
CSRF,(Cross-site request forgery),跨站请求伪造,指通过伪装来自受信任用户的请求来进行对受信任的网站一些操作。
从上图可以看出,要完成一次 CSRF 攻击,受害者必须依次完成两个步骤:
1.登录受信任网银网站,并在本地生成 Cookie。 2.在不登出网银网站的情况下,访问危险网站钓鱼网站。 这时候受害者就中招了。
- CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以随机产生一个 token 放到 HTTP 头中自定义的属性里。并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
- token 可以在用户登陆后产生并放于 session 之中,然后通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 server 产生的 token 值放入其中。然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对。
Django 的 CSRF 保护机制
一种防范方法是在 HTTP 头中自定义属性并验证:
HTTP 请求,一般分为两类:“安全请求”和“不安全请求”。GET 是“安全请求”, POST, PUT, DELETE 是“不安全请求”。
对于“不安全请求”,Django 设计了 CSRF 验证,简单来说这个机制是这样的:
(1) 客户端访问 Django 站点,Django 服务器向客户端发送名为 ”csrftoken” 的 cookie
(2) 客户端对 Django 服务器发送不安全请求时,必须在 HTTP 头部加入 ”X-CSRFToken”字段,并将这个 cookie 的值作为该字段的值
(3) Django 服务器端会对 HTTP 头部 X-CSRFToken 的值进行验证,依次来判断这个请求是不是来自合法用户
解决 403 FORBIDDEN
在使用 postman 测试 API 时候,对于 POST 请求,返回如下错误:
{"detail":"CSRF Failed: CSRF token missing or incorrect."}
原则上,只要在 POST 的 HTTP 头部加入 ”X-CSRFToken” 字段就可以。但实际测试发现,即便加了,也会得到 403 错误,而且,cookie 中的 “csrftoken” 有时候能获取到,有时候获取不到。
在浏览器的 Network 中查看报文发现,client 在登录后,server response header 中无 ”Set-Cookie” 字段,导致再次访问时 server 无法根据 cookie 判断当前用户,只能得到匿名用户。
原因应该又是跨源访问造成的,client 和 server 不同源(CORS 可以见 X2.16 报告),所以服务器发送的 HTTP 响应,是不能设置浏览器 cookie 的。
所以安装 django-cors-header,并在 setting 中加入
CORS_ALLOW_CREDENTIALS = True
这样会让服务器同意客户端发送 Cookie
但有时候因为 domain 的问题 cookie 还是获取不到,所以一种强制的方法是继承重写 Django 的 SessionAuthentication 类,使其跳过 csrf 检测:
class CsrfExemptSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return # To not perform the csrf check previously happening
然后在 APIView 类中使用
authentication_classes = (CsrfExemptSessionAuthentication,)