Rails CSRF 保护与单页应用程序(反应,角度,余烬)

Posted

技术标签:

【中文标题】Rails CSRF 保护与单页应用程序(反应,角度,余烬)【英文标题】:Rails CSRF protection with Single Page Application (react, angular, ember) 【发布时间】:2018-10-11 20:02:08 【问题描述】:

好的。我对这个问题正式失去了理智。

让我们使用默认的 Rails 应用程序(5,但我也尝试使用4 默认应用程序)。

我正在尝试使用简单的 javascript 代码向一个控制器操作发送 ajax POST 请求。

在我的ApplicationController 我有这个代码:

class ApplicationController < ActionController::Base

  after_action :set_csrf_cookie

  protected

    def set_csrf_cookie
      cookies["X-CSRF-Token"] = form_authenticity_token
    end

end

它设置了一个 cookie "X-CSRF-Token",其值为 form_authenticity_token

之后,我可以使用以下代码在我的 SPA(单页应用程序)中读取此 cookie:

<script>
function readCookie(name) 
    var nameEQ = name + "=";
    var ca = document.cookie.split(";");
    for (var i = 0; i < ca.length; i++) 
      var c = ca[i];
      while (c.charAt(0) === " ") c = c.substring(1, c.length);
      if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
    
    return null;
  

// var token = document.getElementsByName('csrf-token')[0].content; // this works!
const token = readCookie("X-CSRF-Token"); // this doesn't work!

fetch('/api/v1', 
  method: 'POST',
  body: "",
  headers: 
    'Content-Type': 'application/json',
    'X-CSRF-Token': token
  ,
  credentials: 'include'
).then(function(response) 
  return response.json();
);
</script>

当我使用这条线时:

var token = document.getElementsByName('csrf-token')[0].content;

它之所以有效,是因为它读取 Rails 在 html 页面中插入的内容:

<%= csrf_meta_tags %>

<meta name="csrf-param" content="authenticity_token">
<meta name="csrf-token" content="VXaKlO+/Gr/8pGhr5y0bThQ5L/0IDiznMR/9SpaoI6vOoF9KtmB5/9ka+Hz+zjyssNRi/Em/Ye27C+E5pl3odg==">

所以“csrf-token”的内容有效,我的 Rails 应用程序可以验证 CSRF。

这是来自 Rails 源的代码:https://github.com/rails/rails/blob/v5.2.0/actionpack/lib/action_controller/metal/request_forgery_protection.rb

当我改用这一行时:

const token = readCookie("X-CSRF-Token");

它不起作用,我收到此错误:

Started POST "/api/v1" for 172.18.0.1 at 2018-05-01 18:52:56 +0000
Processing by MyController#action as */*
  Parameters: "body"=>
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms)
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

另外,如果我将另一个页面与另一个服务器(npm http-server 或 Microsoft IIS 或其他)使用相同的脚本,问题也是一样的。

如果我从 Rails html 页面复制“csrf-token”的内容并在我的 Javascript 脚本中使用这一行:

const token = "VXaKlO+/Gr/8pGhr5y0bThQ5L/0IDiznMR/9SpaoI6vOoF9KtmB5/9ka+Hz+zjyssNRi/Em/Ye27C+E5pl3odg==";

有效!

所以我的问题是:为什么?


我读过的(什么都没有!):

https://github.com/equivalent/scrapbook2/blob/master/archive/blogs/2017-10-12-csrf-protection-on-single-page-app-api.md Invalid Auth Token with Rails, Graphql, Apollo Client https://www.bhalash.com/archives/13544808782 Rails CSRF Protection + Angular.js: protect_from_forgery makes me to log out on POST https://technpol.wordpress.com/2014/04/17/rails4-angularjs-csrf-and-devise/ Work with authenticity token? Or disable it? Rails API design without disabling CSRF protection

【问题讨论】:

你可以用这个来简化你的 readCookie 函数:document.cookie.match("X-CSRF-Token=([^;]+)")[1] 【参考方案1】:

Tnx,我让你的代码工作了。我只需要添加decodeURIComponent()

const token = decodeURIComponent(readCookie("X-CSRF-Token"));

我将这种方法用于带有缓存 html 的渐进式 Web 应用程序。默认的 Rails 元标记 (&lt;%= csrf_meta_tags %&gt;) 不适用于缓存的 html。

这篇博文还提供了一些其他选择:https://www.fastly.com/blog/caching-uncacheable-csrf-security

【讨论】:

您能否详细说明您如何使用缓存的 html 重现此问题?我的 SPA 遇到了同样的问题(我相信)。我在标头中设置了 X-CSRF-Token,但最近收到了大量 InvalidAuthenticityToken 异常,我相信是因为某些浏览器在浏览器关闭时正在缓存 HTML(并且我的 cookie 过期)。【参考方案2】:

你检查过readCookie("X-CSRF-Token") 值吗? 存储到cookie时值可能不同,可能值转义

【讨论】:

【参考方案3】:

Rails 期望的标题名称是X_CSRF_TOKEN(注意下划线)。我没有看到您共享的其余代码有问题 - 除非来自 cookie 的令牌必须经过 URI 解码 (decodeURIComponent),因此如果您仍然收到警告,请检查此内容。

【讨论】:

你确定X_CSRF_TOKEN中的下划线吗? 这并不重要,因为 Rails 很可能会以同样的方式对待它们 - 这就是我使用它的方式,只是为了安全起见。仅在我确定它有效时才发布:) 我认为它在代码中:github.com/rails/rails/blob/master/actionpack/lib/… 我看到连字符...对吗? 幕后发生了很多事情,请检查github.com/rails/rails/blob/… - 请求环境中的标头都是下划线,但是它们已经被处理了.. 我认为可以安全地假设1. 使用带有连字符的标题是首选方式 2. Rails/rack 似乎平等对待连字符和下划线

以上是关于Rails CSRF 保护与单页应用程序(反应,角度,余烬)的主要内容,如果未能解决你的问题,请参考以下文章

Rails,设计认证,CSRF 问题

如何在 Rails 应用程序中关闭 CSRF 保护?

Rails 3.2 - 为控制器操作禁用 CSRF 保护

在 Rails 中使用 HTTP GET 请求进行 CSRF 保护

不禁用 CSRF 保护的 Rails API 设计

Rails - 如何向用 javascript 创建的表单添加 CSRF 保护?