Rails 单页应用程序的 CSRF 保护(反应,angular,ember)
Rails CSRF protection with Single Page Application (react, angular, ember)
好的。我正式对这个问题失去了理智。
让我们使用默认的 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
header Rails 期望的名称是 X_CSRF_TOKEN
(注意下划线)。我没有看到您共享的其余代码有问题 - 除了可能来自 cookie 的令牌必须经过 URI 解码 (decodeURIComponent
),所以如果您仍然收到警告,请检查它。
您检查readCookie("X-CSRF-Token")
值了吗?
存储到 cookie 时值可能不同,可能值已转义
Tnx,我让你的代码工作了。我只需要添加 decodeURIComponent()
const token = decodeURIComponent(readCookie("X-CSRF-Token"));
我将这种方法用于具有缓存 html 的渐进式 Web 应用程序。默认 rails 元标记 (<%= csrf_meta_tags %>
) 不适用于缓存 html.
这篇博客post也给出了一些其他的替代方案:https://www.fastly.com/blog/caching-uncacheable-csrf-security
好的。我正式对这个问题失去了理智。
让我们使用默认的 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
header Rails 期望的名称是 X_CSRF_TOKEN
(注意下划线)。我没有看到您共享的其余代码有问题 - 除了可能来自 cookie 的令牌必须经过 URI 解码 (decodeURIComponent
),所以如果您仍然收到警告,请检查它。
您检查readCookie("X-CSRF-Token")
值了吗?
存储到 cookie 时值可能不同,可能值已转义
Tnx,我让你的代码工作了。我只需要添加 decodeURIComponent()
const token = decodeURIComponent(readCookie("X-CSRF-Token"));
我将这种方法用于具有缓存 html 的渐进式 Web 应用程序。默认 rails 元标记 (<%= csrf_meta_tags %>
) 不适用于缓存 html.
这篇博客post也给出了一些其他的替代方案:https://www.fastly.com/blog/caching-uncacheable-csrf-security