单页应用和 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 默认移除了对 jqueryjquery_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

CORS、Ajax 和 CSRF

如果使用 JWT,我需要 CSRF 吗?

在基于 AJAX 的应用程序中使用令牌防止 CSRF

Django Rest Framework 将不接受我的 CSRF 令牌