老问题,但安全性非常重要,我觉得它值得一个完整的答案。正如question 中所讨论的,即使使用 API,仍然存在一些 CSRF 风险。是的,浏览器应该默认防止这种情况发生,但由于您无法完全控制用户安装的浏览器和插件,因此仍应将其视为防止 API 中的 CSRF 的最佳做法。
有时我看到的做法是从 HTML 页面本身解析 CSRF 元标记。我不太喜欢这个,因为它不适合当今许多单页 + API 应用程序的工作方式,而且我觉得 CSRF 令牌应该在每个请求中发送,无论它是 HTML、JSON 还是 XML。
所以我建议改为通过所有请求的后过滤器将 CSRF 令牌作为 cookie 或标头值传递。 API 可以简单地将其作为 X-CSRF-Token 的标头值重新提交回来,Rails 已经检查了该值。
这就是我使用 AngularJS 的方式:
# In my ApplicationController
after_filter :set_csrf_cookie
def set_csrf_cookie
if protect_against_forgery?
cookies['XSRF-TOKEN'] = form_authenticity_token
end
end
AngularJS automatically looks for a cookie 命名为 XSRF-TOKEN,但您可以根据自己的目的随意命名。然后,当您提交 POST/PUT/DELETE 时,您应该设置 Rails 自动查找的标题属性 X-CSRF-Token。
不幸的是,AngualrJS 已经在X-XSRF-TOKEN 的标头值中发回了XSRF-TOKEN cookie。在ApplicationController 中覆盖 Rails 的默认行为很容易,如下所示:
protected
def verified_request?
super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
end
对于 Rails 4.2,现在有一个内置的帮助器来验证应该使用的 CSRF。
protected
def verified_request?
super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
end
我希望这会有所帮助。
编辑:在discussion on this for a Rails pull-request 我提交的结果表明,通过 API 传递 CSRF 令牌进行登录是一种特别糟糕的做法(例如,有人可以为您的网站创建使用用户凭据而不是令牌的第三方登录)。所以自告奋勇。由您决定您对应用程序的关注程度。在这种情况下,您仍然可以使用上述方法,但仅将 CSRF cookie 发送回已经具有经过身份验证的会话的浏览器,而不是针对每个请求。这将阻止在不使用 CSRF 元标记的情况下提交有效登录。