Phoenix - 无效的 CSRF(跨站点伪造保护)令牌错误
Phoenix - Invalid CSRF (Cross Site Forgery Protection) token error
我在尝试更新(或创建)记录时收到无效的 CSRF 令牌错误。我正在使用 Elixir v1.0.3、Erlang/OTP 17 [erts-6.3] 和 Phoenix v0.8.0(我想,我不确定如何检查 Phoenix 的版本)。我正在创建一个主要遵循 Phoenix 指南和 Elixir Dose Jobsite 示例资源的网络应用程序。但是,当我尝试从 html 表单中获取 post 信息时,我收到无效的 CSRF 令牌错误。按照错误中给出的建议,我将 'x-csrf-token': csrf_token 添加到操作中。
edit.html.eex:
<h2>Edit Directory</h2>
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id, 'x-csrf-token': @csrf_token %>" method="post">
<div class="form-group">
<label for="directory" class="col-sm-2 control-label">Directory</label>
<div class="col-sm-10">
<input type="hidden" name="_method" value="PATCH">
<input type="text" class="form-control" value="<%= @directory.directory %>" name="directory" placeholder="Directory" required="required">
</div>
</div>
...
但我收到以下错误:
[error] #PID<0.579.0> running Ainur.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /config/directories/2?x-csrf-token=
** (exit) an exception was raised:
** (Plug.CSRFProtection.InvalidCSRFTokenError) Invalid CSRF (Cross Site Forgery Protection) token. Make sure that all your non-HEAD and non-GET requests include the csrf_token as part of form params or as a value in your request's headers with the key 'x-csrf-token'
(plug) lib/plug/csrf_protection.ex:54: Plug.CSRFProtection.call/2
(ainur) web/router.ex:4: Ainur.Router.browser/2
(ainur) lib/phoenix/router.ex:2: Ainur.Router.call/2
(plug) lib/plug/debugger.ex:104: Plug.Debugger.wrap/3
(phoenix) lib/phoenix/endpoint/error_handler.ex:43: Phoenix.Endpoint.ErrorHandler.wrap/3
(ainur) lib/ainur/endpoint.ex:1: Ainur.Endpoint.phoenix_endpoint_pipeline/2
(plug) lib/plug/debugger.ex:104: Plug.Debugger.wrap/3
(phoenix) lib/phoenix/endpoint/error_handler.ex:43: Phoenix.Endpoint.ErrorHandler.wrap/3
据我所知(我是 Elixir、Phoenix 和 HTML 的新手),"action" 本质上是一条路径,我在其中放置的任何参数都会找到返回到应用。而且,确实,我发现 x-csrf-token = "" 被传回路由器,所以 @csrf_token 一定是不正确的。我不确定 csrf_token 的确切来源,所以我不知道如何引用它(或者我这样做完全错误)。
如有任何想法,我们将不胜感激。
我在 http://phoenix.thefirehoseproject.com 上找到了答案。您必须创建一个函数来获取 csrf 令牌:
web/view.ex
def csrf_token(conn) do
Plug.Conn.get_session(conn, :csrf_token)
end
然后在模板中取回:
web/template/directory/edit.html.eex
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id %>" method="post">
<input type="hidden" name="csrf_token" value="<%= csrf_token(@conn) %>">
就是这样!
要查看安装的版本,运行
cat ./deps/phoenix/mix.exs | grep version
显示 deps 目录中的凤凰。
此外,if/when 您升级到 phoenix 0.9.0,情况发生了变化(由于对 Plug.CSRFProtection 的更新),CSRF 的工作方式不同,使用 cookie 而不是会话。
来自Phoenix changelog for v0.9.0 (2015-02-12)
[Plug] Plug.CSRFProtection now uses a cookie instead of session and
expects a "_csrf_token" parameter instead of "csrf_token"
要访问令牌的值,请从 cookie 中获取 if,在服务器端看起来像
Map.get(@conn.req_cookies, "_csrf_token")
所以对于你的代码,看起来像
<h2>Edit Directory</h2>
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id, 'x-csrf-token': Map.get(@conn.req_cookies, "_csrf_token") %>" method="post">
<div class="form-group">
<label for="directory" class="col-sm-2 control-label">Directory</label>
<div class="col-sm-10">
<input type="hidden" name="_method" value="PATCH">
<input type="text" class="form-control" value="<%= @directory.directory %>" name="directory" placeholder="Directory" required="required">
</div>
</div>
现在,为了完整起见,我需要为纯客户端构建的请求更新 CSRF,所以这里是我如何访问 javascript 中的 cookie,使用 JQuery cookie,以便于访问。您应该可以通过 运行以下
在浏览器中看到该值
$.cookie("_csrf_token")
这可能 return 类似
"K9UDa23e1sacdadfmvu zzOD9VBHTSr1c/lcvWY="
注意上面的 space,它在 phoenix 中被 url 编码为 +,这仍然导致 CSRF 失败。现在是 Plug 中的错误,还是只是需要处理的东西,我不确定,所以现在我只是简单地处理 + 显式
$.cookie("_csrf_token").replace(/\s/g, '+');
通过访问 CSRF 令牌,我们现在只需将 x-csrf-token 添加到您的请求 header (thank you ilake)。这是使其与 ajax 调用一起工作的代码(相应地填写 url 和数据和响应)。
$.ajax({
url: 'YOUR URL HERE',
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader('x-csrf-token', $.cookie("_csrf_token").replace(/\s/g, '+'))
},
data: 'someData=' + someData,
success: function(response) {
$('#someDiv').html(response);
}
});
请注意,您也可以将 _csrf_token 作为参数发回,但我更喜欢上面的,我觉得它更干净。
最后请注意,我没有足够的信誉点数来正确 post 一个 link 到 jquery cookie,但应该很容易 google。
在 Phoenix 0.13 版本上你可以做到
<input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>">
因为在文件 web/web.ex
中导入了此函数。
作为自 v0.10.0 以来可用的另一种解决方案,您可以让 Phoenix 为您注入 CSRF 输入。
<%= form_tag("/hello", method: :post) %>
... your form stuff. input with csrf value is created for you.
</form>
这将输出表单标签和一些输入标签,包括 _csrf_token
标签。结果将如下所示:
<form accept-charset="UTF-8" action="/hello" method="post">
<input name="_csrf_token" value="[automatically-inserted token]" type="hidden">
<input name="_utf8" value="✓" type="hidden">
</form>
form_tag
docs: "for 'post' requests, the form tag will automatically include an input tag with name _csrf_token."
在我的例子中,是 plug :scrub_params
行导致了问题。
评论该行后,它起作用了。但需要确保修复它,因为如果没有 scrub_params.
应用程序将不安全
我的解决方案是:
将 Phoenix.Controller.get_csrf_token:0
导入到 MyModule.view:0
(在 apps/my_app_web/lib/my_app_web.ex 下):
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
在我的表单中添加一个隐藏参数:
<input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>" />
我在尝试更新(或创建)记录时收到无效的 CSRF 令牌错误。我正在使用 Elixir v1.0.3、Erlang/OTP 17 [erts-6.3] 和 Phoenix v0.8.0(我想,我不确定如何检查 Phoenix 的版本)。我正在创建一个主要遵循 Phoenix 指南和 Elixir Dose Jobsite 示例资源的网络应用程序。但是,当我尝试从 html 表单中获取 post 信息时,我收到无效的 CSRF 令牌错误。按照错误中给出的建议,我将 'x-csrf-token': csrf_token 添加到操作中。
edit.html.eex:
<h2>Edit Directory</h2>
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id, 'x-csrf-token': @csrf_token %>" method="post">
<div class="form-group">
<label for="directory" class="col-sm-2 control-label">Directory</label>
<div class="col-sm-10">
<input type="hidden" name="_method" value="PATCH">
<input type="text" class="form-control" value="<%= @directory.directory %>" name="directory" placeholder="Directory" required="required">
</div>
</div>
...
但我收到以下错误:
[error] #PID<0.579.0> running Ainur.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /config/directories/2?x-csrf-token=
** (exit) an exception was raised:
** (Plug.CSRFProtection.InvalidCSRFTokenError) Invalid CSRF (Cross Site Forgery Protection) token. Make sure that all your non-HEAD and non-GET requests include the csrf_token as part of form params or as a value in your request's headers with the key 'x-csrf-token'
(plug) lib/plug/csrf_protection.ex:54: Plug.CSRFProtection.call/2
(ainur) web/router.ex:4: Ainur.Router.browser/2
(ainur) lib/phoenix/router.ex:2: Ainur.Router.call/2
(plug) lib/plug/debugger.ex:104: Plug.Debugger.wrap/3
(phoenix) lib/phoenix/endpoint/error_handler.ex:43: Phoenix.Endpoint.ErrorHandler.wrap/3
(ainur) lib/ainur/endpoint.ex:1: Ainur.Endpoint.phoenix_endpoint_pipeline/2
(plug) lib/plug/debugger.ex:104: Plug.Debugger.wrap/3
(phoenix) lib/phoenix/endpoint/error_handler.ex:43: Phoenix.Endpoint.ErrorHandler.wrap/3
据我所知(我是 Elixir、Phoenix 和 HTML 的新手),"action" 本质上是一条路径,我在其中放置的任何参数都会找到返回到应用。而且,确实,我发现 x-csrf-token = "" 被传回路由器,所以 @csrf_token 一定是不正确的。我不确定 csrf_token 的确切来源,所以我不知道如何引用它(或者我这样做完全错误)。
如有任何想法,我们将不胜感激。
我在 http://phoenix.thefirehoseproject.com 上找到了答案。您必须创建一个函数来获取 csrf 令牌:
web/view.ex
def csrf_token(conn) do
Plug.Conn.get_session(conn, :csrf_token)
end
然后在模板中取回:
web/template/directory/edit.html.eex
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id %>" method="post">
<input type="hidden" name="csrf_token" value="<%= csrf_token(@conn) %>">
就是这样!
要查看安装的版本,运行
cat ./deps/phoenix/mix.exs | grep version
显示 deps 目录中的凤凰。
此外,if/when 您升级到 phoenix 0.9.0,情况发生了变化(由于对 Plug.CSRFProtection 的更新),CSRF 的工作方式不同,使用 cookie 而不是会话。
来自Phoenix changelog for v0.9.0 (2015-02-12)
[Plug] Plug.CSRFProtection now uses a cookie instead of session and expects a "_csrf_token" parameter instead of "csrf_token"
要访问令牌的值,请从 cookie 中获取 if,在服务器端看起来像
Map.get(@conn.req_cookies, "_csrf_token")
所以对于你的代码,看起来像
<h2>Edit Directory</h2>
<form class="form-horizontal" action="<%= directory_path @conn, :update, @directory.id, 'x-csrf-token': Map.get(@conn.req_cookies, "_csrf_token") %>" method="post">
<div class="form-group">
<label for="directory" class="col-sm-2 control-label">Directory</label>
<div class="col-sm-10">
<input type="hidden" name="_method" value="PATCH">
<input type="text" class="form-control" value="<%= @directory.directory %>" name="directory" placeholder="Directory" required="required">
</div>
</div>
现在,为了完整起见,我需要为纯客户端构建的请求更新 CSRF,所以这里是我如何访问 javascript 中的 cookie,使用 JQuery cookie,以便于访问。您应该可以通过 运行以下
在浏览器中看到该值$.cookie("_csrf_token")
这可能 return 类似
"K9UDa23e1sacdadfmvu zzOD9VBHTSr1c/lcvWY="
注意上面的 space,它在 phoenix 中被 url 编码为 +,这仍然导致 CSRF 失败。现在是 Plug 中的错误,还是只是需要处理的东西,我不确定,所以现在我只是简单地处理 + 显式
$.cookie("_csrf_token").replace(/\s/g, '+');
通过访问 CSRF 令牌,我们现在只需将 x-csrf-token 添加到您的请求 header (thank you ilake)。这是使其与 ajax 调用一起工作的代码(相应地填写 url 和数据和响应)。
$.ajax({
url: 'YOUR URL HERE',
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader('x-csrf-token', $.cookie("_csrf_token").replace(/\s/g, '+'))
},
data: 'someData=' + someData,
success: function(response) {
$('#someDiv').html(response);
}
});
请注意,您也可以将 _csrf_token 作为参数发回,但我更喜欢上面的,我觉得它更干净。
最后请注意,我没有足够的信誉点数来正确 post 一个 link 到 jquery cookie,但应该很容易 google。
在 Phoenix 0.13 版本上你可以做到
<input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>">
因为在文件 web/web.ex
中导入了此函数。
作为自 v0.10.0 以来可用的另一种解决方案,您可以让 Phoenix 为您注入 CSRF 输入。
<%= form_tag("/hello", method: :post) %>
... your form stuff. input with csrf value is created for you.
</form>
这将输出表单标签和一些输入标签,包括 _csrf_token
标签。结果将如下所示:
<form accept-charset="UTF-8" action="/hello" method="post">
<input name="_csrf_token" value="[automatically-inserted token]" type="hidden">
<input name="_utf8" value="✓" type="hidden">
</form>
form_tag
docs: "for 'post' requests, the form tag will automatically include an input tag with name _csrf_token."
在我的例子中,是 plug :scrub_params
行导致了问题。
评论该行后,它起作用了。但需要确保修复它,因为如果没有 scrub_params.
我的解决方案是:
将
Phoenix.Controller.get_csrf_token:0
导入到MyModule.view:0
(在 apps/my_app_web/lib/my_app_web.ex 下):import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
在我的表单中添加一个隐藏参数:
<input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>" />