单页应用和 CSRF Token
Posted
技术标签:
【中文标题】单页应用和 CSRF Token【英文标题】:Single Page Application and CSRF Token 【发布时间】:2018-10-14 01:13:48 【问题描述】:我需要使用带有 Rails CSRF 保护机制的单页应用程序(React、Ember、Angular,我不在乎)。
我想知道是否需要像这样在ApplicationController
中创建一个令牌:
class ApplicationController < ActionController::Base
after_action :set_csrf_cookie
def set_csrf_cookie
cookies["X-CSRF-Token"] = form_authenticity_token
end
end
或者我可以只创建一次令牌。
每个会话还是每个(非 GET)请求?
我认为令牌在会话有效之前仍然有效,对吧?
澄清:
每次浏览页面时,我都会看到 Rails 默认应用程序(服务器渲染页面)更新 csrf-token。所以每次都会变。
所以在我的情况下,如果我为每个 after_action
创建一个新令牌,则之前的 CSRF-Token 仍然适用于该会话。那么,如何使之前的令牌失效呢?我必须吗?
因为只有我将其无效才有意义,对吧?
【问题讨论】:
有趣的阅读:github.com/equivalent/scrapbook2/blob/master/archive/blogs/… 感谢@nathanvda,但我已经阅读了该页面以及底部的所有链接以及所有 Google 的服务器都知道我是这个论点上世界上最好的人。但对此我仍然没有答案! 只是为了让我完全理解上下文,您使用的是 Devise 还是任何其他身份验证 gem? 仅设计 gem。 【参考方案1】:客户端(SPA)
您只需每个会话一次获取 CSRF 令牌。您可以在浏览器中保留它并在每个(非 GET)请求时发送它。
Rails 似乎会在每个请求上生成一个新的 CSRF 令牌,但它会接受从该会话生成的任何令牌。实际上,它只是对每个请求使用一次性填充来掩盖单个令牌,以防止 SSL BREACH 攻击。更多详情请访问https://***.com/a/49783739/2016618。您无需跟踪/存储这些令牌。
服务器端
我强烈建议使用 Rails 的 protect_from_forgery
指令,而不是自己在标头中编码 CSRF 令牌。它将为每个请求生成不同的掩码令牌。
您当然可以自己用不多的代码来重现它,但我不明白您为什么需要这样做。
您需要使用 API 进行 CSRF 保护吗?
是的!如果您使用 cookie 进行身份验证,则需要 CSRF 保护。这是因为 cookie 随每个请求一起发送,因此恶意网站可能会向您的站点发送 POST 请求并代表登录用户执行请求。 CSRF 令牌可以防止这种情况发生,因为恶意网站不知道 CSRF 令牌。
【讨论】:
只有在您的端点接受application/x-www-form-urlencoded
时才能访问来自 POST 请求的攻击,对吗?我可以想象对于许多 RESTful API,它们只允许使用 application/json
的 POST。
@seansean11 当然,如果您只允许该内容类型,那么您不需要 CSRF,但 OP 专门询问了默认 Rails 应用程序。
恶意网站还可以向网络服务器发送请求以获取 CSRF 令牌,然后发布任何内容。
@BasheerKharoti 同源策略是所有主流浏览器的标准功能,并且已经存在多年。确实可以通过使用非浏览器客户端或 Postman 等开发人员工具绕过这些保护措施,但恶意网站无法使用这些工具绕过 SOP 限制。如果您知道任何例外情况,请分享。
@Sarkom 不一定是非浏览器客户端,但也可以在任何浏览器中绕过。 Bypass chrome【参考方案2】:
我不知道您面临的具体问题是什么。但是,如果您在新 Rails 版本中遇到 CSRF 问题并且需要包含 Rails CSRF 令牌 在 ajax 请求中,您可以按照以下步骤操作。
最近我使用的是 Rails 5.1 应用程序。
当使用 ajax 调用从 API 获取一些数据时,我遇到了 CSRF 令牌问题:
‘WARNING: Can't verify CSRF token authenticity rails’
原因是
Rails 5.1 默认移除了对 jquery 和 jquery_ujs 的支持,并添加了
//= require rails-ujs in application.js
它做了以下事情:
-
对各种操作强制确认对话框;
从超链接发出非 GET 请求;
使用 Ajax 使表单或超链接异步提交数据;
在提交表单时自动禁用提交按钮,以防止双击。
(来自:https://github.com/rails/rails-ujs/tree/master)
但默认情况下它不包括用于 ajax 请求的 csrf 令牌。小心那个。我们必须像这样明确地传递它:
$( document ).ready(function()
$.ajaxSetup(
headers:
'X-CSRF-Token': Rails.csrfToken()
);
----
----
);
注意,在Rails 5.1版本中,你在js中获得了'Rails'类,并且可以使用这些功能。
更新: 如果你使用 Rails 服务器端和其他前端,你真的不想使用 Rails 提供的 CSRF 令牌。因为您使用的后端服务真的无关紧要。
如果您的目标是阻止 CSRF,则需要在后端设置 CORS,即您的 Rails 后端。 Rails 现在为此在初始化程序中提供了一个单独的文件。您可以提及允许哪些网站向您的后端发送 ajax 请求。
在这里编辑:
config/initializers/cors.rb
如果你想要认证,使用基本认证,令牌认证或JWT
【讨论】:
亲爱的@Abhi,我没有将 Rails 用于前端页面。我正在使用 Ember、Angular、React、Vue。【参考方案3】:我只是回答这个问题。这篇文章解释了完整的细节: https://blog.eq8.eu/article/rails-api-authentication-with-spa-csrf-tokens.html
鉴于 Rails 正在为 SPA 设置 cookie
CSRF 令牌在会话的生命周期内有效。因此,是的,只需在登录后的会话期间生成一个 CSRF 令牌即可。
class LoginController < ApplicationController
def create
if user_password_match
# ....
cookie[:my_csrf_token]= form_authenticity_token
end
end
end
或者您可以按照建议的方式刷新 cookie
class ApplicationController < ActionController::Base
after_action :set_csrf_cookie
def set_csrf_cookie
cookies["my_csrf_token"] = form_authenticity_token
end
end
您的 SPA 只需读取 cookie 并将其设置为标题。
两者同样有效
或者您可以只提供 CSRF 令牌作为登录响应,SPA 会将其存储在某处并在 X-CSRF-Token
标头中使用它:
curl POST https://api.my-app.com/login.json -d ""email":'equivalent@eq8.eu", "password":"Hello"" -H 'ContentType: application/json'
# Cookie with session_id was set
# response:
"login": "ok", "csrf": 'yyyyyyyyy"
# next request
curl POST https://api.my-app.com/transfer_my_money -d ""to_user_id:":"1234"" -H "ContentType: application/json" -H "X-CSRF-Token: yyyyyyyyy"
鉴于 Rails 只接受标头身份验证
如果您不使用 cookie 发送 session_id,那么您的 API 仅使用 Authentication
标头进行身份验证。那么你就不需要 CSRF 保护了。
没有会话 cookie 没有 CSRF 问题!
例子:
curl POST https://api.my-app.com/login.json -d ""email":'equivalent@eq8.eu", "password":"Hello"" -H 'ContentType: application/json'
# No cookie/session is set
# response:
"login": "ok", "jwt": "xxxxxxxxxxx.xxxxxxxx.xxxxx"
# Next Request:
curl POST https://api.my-app.com/transfer_my_money -d ""to_user_id:":"1234"" -H "ContentType: application/json" -H "Authentication: Bearer xxxxxxxxxxx.xxxxxxxx.xxxxx"
再一次,这只是在您不使用 cookie 进行用户识别时!所以在这种情况下 CSRF 不是问题,但您仍然可以防止跨站脚本攻击,确保您的通信仅是 HTTPs,等等...
【讨论】:
【参考方案4】:如果您使用 SPA 应用程序,那么您主要将 Rails 用作 API。 CSRF 令牌是为服务器渲染设计的......不是 SPA。在 SPA 中,您已经在身份验证期间使用了令牌,因此无需为 CSRF 使用另一个令牌。 CSRF 旨在为跨站点调用提供保护,但 API 本身的设计方式是允许来自任何地方的请求,直到它们通过身份验证。
只需为您的 API 禁用它即可。我会使用一些 API
命名空间并设置一个 BaseController
,它将被所有 API 控制器继承。你应该在那里设置protect_from_forgery
:
class API::BaseController < ApplicationController
protect_from_forgery with: :null_session
end
【讨论】:
我不明白。 CSRF 仅适用于服务器渲染的页面?但是,如果我除了 cookie 身份验证之外还有我的 API 怎么办?例如使用设计?我的客户只是我的SPA?我不想使用 JWT 或类似的东西。所以我认为对于基于 cookie 的会话,我的 SPA 和 Rails API 也需要 CSRF,对吧? "CSRF 仅适用于服务器渲染的页面?" -> 是的; “但是如果我除了 cookie 身份验证之外还有我的 API 怎么办?” -> 那么它只是一个ajax请求,而不是API方式(我的观点); “例如使用设计?” -> Devise 有用于令牌认证的扩展 gems; “我的客户只是我的SPA?” -> 任何东西。移动设备/其他服务器...任何使用令牌进行身份验证或访问您的公共端点的东西; “我不想使用 JWT 或类似的东西。” -> 为什么? “所以我认为对于基于 cookie 的会话,我的 SPA 和 Rails API 也需要 CSRF,对吧?” -> 这会强制你每次都加载 CSRF 您对此有何看法:***.com/a/15056471/4412054? 我不喜欢使用 JWT,因为所有这些原因:cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions 你所说的ajax calls
是什么SPA框架(Ember、React、Angular)调用API requests
?以上是关于单页应用和 CSRF Token的主要内容,如果未能解决你的问题,请参考以下文章
Rails CSRF 保护与单页应用程序(反应,角度,余烬)
“警告:无法验证 CSRF 令牌真实性”错误 - 带有 Devise 和 :token_authenticable 的 CORS