Phoenix - 无效的CSRF(跨站点伪造保护)令牌错误

Pau*_*l B 20 elixir phoenix-framework

尝试更新(或创建)记录时,我收到无效的CSRF令牌错误.我正在使用Elixir v1.0.3,Erlang/OTP 17 [erts-6.3]和Phoenix v0.8.0(我想,我不知道如何查看Phoenix的版本).我正在创建一个Web应用程序,主要遵循Phoenix指南和Elixir Dose Jobsite示例资源.但是,当我尝试从html表单发布信息时,我收到无效的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>
...
Run Code Online (Sandbox Code Playgroud)

但是我收到以下错误:

[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
Run Code Online (Sandbox Code Playgroud)

据我所知(对Elixir,Phoenix和HTML来说是新手),"action"本质上是一个路径,我在其中放置的任何参数都将找到返回应用程序的路径.事实上,我发现x-csrf-token =""被传递回路由器,因此@csrf_token必须不正确.我不确定csrf_token的确切位置,所以我不知道如何引用它(或者我可能完全错误).

任何想法将不胜感激.

Tsu*_*esu 20

在凤凰城的0.13版本上你可以做到

<input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>">
Run Code Online (Sandbox Code Playgroud)

因为在档案中web/web.ex有这个功能的导入.


Tay*_*ler 8

作为自v0.10.0以来的另一种解决方案,您可以让Phoenix为您注入CSRF输入.

升级指南示例:

<%= form_tag("/hello", method: :post) %>
... your form stuff. input with csrf value is created for you.
</form>
Run Code Online (Sandbox Code Playgroud)

这将输出表单标签和一些输入标签,包括_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>
Run Code Online (Sandbox Code Playgroud)

form_tagdocs:"对于'post'请求,表单标签将自动包含一个名为_csrf_token的输入标签."


小智 4

要查看安装的版本,请运行

cat ./deps/phoenix/mix.exs | grep version
Run Code Online (Sandbox Code Playgroud)

它显示了 deps 目录中的凤凰。

另外,如果/当您升级到 phoenix 0.9.0 时,情况发生了变化(由于 Plug.CSRFProtection 的更新),CSRF 使用 cookie 而不是会话以不同的方式工作。

来自v0.9.0 的 Phoenix 变更日志 (2015-02-12)

[Plug] Plug.CSRFProtection 现在使用 cookie 而不是会话,并需要“_csrf_token”参数而不是“csrf_token”

要访问令牌的值,请从 cookie 中获取 if ,这在服务器端看起来像

Map.get(@conn.req_cookies, "_csrf_token")
Run Code Online (Sandbox Code Playgroud)

所以对于你的代码来说,看起来像

<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>
Run Code Online (Sandbox Code Playgroud)

现在,为了完整起见,我需要更新的 CSRF 来用于纯客户端构建的请求,因此以下是我如何使用 JQuery cookie 在 javascript 中访问 c​​ookie,以便于访问。您应该能够通过运行以下命令在浏览器中看到该值

$.cookie("_csrf_token")
Run Code Online (Sandbox Code Playgroud)

这可能会返回类似的东西

"K9UDa23e1sacdadfmvu zzOD9VBHTSr1c/lcvWY="
Run Code Online (Sandbox Code Playgroud)

请注意上面的空格,在 phoenix 中被 url 编码为 +,这仍然导致 CSRF 失败。现在是 Plug 中的错误,还是只是需要处理的问题,我不确定,所以现在我只是显式处理 +

$.cookie("_csrf_token").replace(/\s/g, '+');
Run Code Online (Sandbox Code Playgroud)

通过访问 CSRF 令牌,我们现在只需将 x-csrf-token 添加到您的请求标头中(谢谢 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);
  }
});
Run Code Online (Sandbox Code Playgroud)

请注意,您也可以将 _csrf_token 作为参数发送回,但我更喜欢上面的方式,它对我来说感觉更干净。

最后一点,我没有足够的声誉点来正确发布 jquery cookie 的链接,但应该很容易通过谷歌搜索。