Django的CSRF保护引起的403 FORBIDDEN

用Django写了一个API,专门处理客户端发来的POST请求。结果每一次客户端JS发送POST请求,都得到403的Error。

Google搜“django 403 forbidden ajax post”,在神奇的stackoverflow上发现了答案 。 原来是Django的CSRF保护机制导致的。

CSRF

CSRF是跨站点请求伪造,简单来说就是用户在访问受信任网站后,浏览器记录了受信任网站的cookie,此时用户在不登出受信任网站的同时访问了危险网站,危险网站的请求和cookie一起到达服务器,服务器Session未过期的时候,就误认为该请求是用户发出的,从而被危险网站利用。更多细节可参考[ 1 ]和[ 2 ],目前解决CSRF攻击的方法,常用的方法就是进一步确认一个请求是来自于用户还是危险网站,就是客户端发送请求时,增加伪随机数,常用的方法有3种:
(1)验证码
(2)客户端登陆后发送token给服务器,且客户端每一次请求都带上这个token,服务器记录第一次登陆后的token,并对每一次请求进行验证
(3)在HTTP头中自定义属性并验证

Django的CSRF保护机制

HTTP请求,分为两类:“安全请求”和“不安全请求”。GET是“安全请求”, POST, PUT, DELETE是“不安全请求”。安不安全,主要还是看请求设计者。比如,如果银行系统的转账设计是用GET进行的,那GET也是不安全的。所以一般GET就用于获取资源,不要进行资源更新操作,资源更新交给POST来做。

对于“不安全请求”,Django设计了CSRF验证,简单来说这个机制是这样的:
(1) 客户端访问Django站点,Django服务器向客户端发送名为”csrftoken”的cookie
(2) 客户端对Django服务器发送不安全请求时,必须在HTTP头部加入”X-CSRFToken”字段,并将这个cookie的值作为该字段的值
(3) Django服务器端会对HTTP头部X-CSRFToken的值进行验证,依次来判断这个请求是不是来自合法用户

如何解决403

现在搞清楚了,这个403错误的原因,就是我发送的POST请求没有满足Django的CSRF保护机制。原则上,只要在POST的HTTP头部加入”X-CSRFToken”就可以。但实际编码发现,即便加了”“,也会得到403错误,而且,cookie “csrftoken”有时候能获取到,有时候获取不到!这个现象记录在这里。

经过仔细调试,发现只有在浏览器中访问Django服务器,才能获得cookie “csrftoken”,客户端通过JS访问Django服务器(比如GET URL),是无法得到这个cookie的。原因在于,服务器向客户端发送cookie,都是通过HTTP头部的”Set-Cookie”字段发送的。客户端跟服务器不同源(CORS细节见XXX),所以服务器发送的HTTP响应,是不能设置浏览器cookie的。为什么通过浏览器访问服务器,就能设置cookie呢?应为同源策略(CORS)是对脚本规定的限制,浏览器当然不受限了。

在我的设计中,用Django开发的API,是开放给所有人用的,所以后来将同源策略配置为对所有不同源的客户端都开放资源访问,即’Access-Control-Allow-Origin’为*。但HTTP响应返回的头部的”Set-Cookie”字段依然不能设置浏览器cookie。而根据stackoverflow的这个问题,ajax响应是可以设置浏览器cookie的,但我这里依然不能设置,这又是为何呢?再进一步看,发现”To send cross-domain cookies, you need to set the withCredentials flag”。需要在客户端JS中设置HTTP请求的withCredentials标记为True,照此设置后,却报错如下:

XMLHttpRequest cannot load http://localhost:8000/v1/getAuthID?username=testuser1&password=123. A wildcard ‘*’ cannot be used in the ‘Access-Control-Allow-Origin’ header when the credentials flag is true. Origin ‘null’ is therefore not allowed access.

终其原因,为了让JS发送的GET请求的回复,能设置浏览器cookie,必须将GET请求的credentials标记设置为True。而credentials标记设置为True后,是要求服务器的Access-Control-Allow-Origin不能返回的。但在我的设计中,为了将API开放给不同的域去访问,只能将Access-Control-Allow-Origin设置为。所以逻辑死在这里了不是。在我这种设计下,是没办法在JS中,获取到Django服务器返回的cookie的。

最终,只能忍痛将Django的CSRF禁止,因为我的API,设计出来就是给JS调用的,而这个JS可能在不同的源。禁止Django自带的CSRF,不是就不安全了么?安全机制,还有其它的要做,况且自己不也是可以做其他的CSRF验证吗,上文已经给出方法了哦。

总结

Django的CSRF保护机制,对于用Django设计的网站,是很好的,用起来也很简单。但如果Django站点只提供API,没有页面,由于CORS的限制,且你的API是开放给不同源的用户使用的, 那么,对于这种单纯只提供API的Django服务器,是没法采用Django提供的CSRF保护机制的。对于API的安全设计,除了CSRF,还有下面的关键点:

  • 合理的用户认证机制
  • 限制每秒的访问请求数量
  • 使用HTTPS作为API的通道
  • XSS安全防护
  • 表单自动验证
  • 强制数据类型转换
  • 输入数据过滤
  • 表单令牌验证
  • 防SQL注入
  • 图像上传检测

Ref

[ 1 ] http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html
[ 2 ] https://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/
[ 3 ] http://stackoverflow.com/questions/13954080/cross-domain-jquery-ajax-call-with-credentials

你可能感兴趣的:(软件开发心得,Python,Javascript,Django,网络安全)