【问题标题】:Phoenix - Invalid CSRF (Cross Site Forgery Protection) token errorPhoenix - 无效的 CSRF(跨站伪造保护)令牌错误
【发布时间】:2015-04-20 03:14:48
【问题描述】:

我在尝试更新(或创建)记录时收到 Invalid CSRF token 错误。我正在使用 Elixir v1.0.3、Erlang/OTP 17 [erts-6.3] 和 Phoenix v0.8.0(我想,我不确定如何检查 Phoenix 的版本)。我正在创建一个 web 应用程序,主要遵循 Phoenix 指南和 Elixir Dose Jobsite Example 资源。但是,当我尝试从 html 表单发布信息时,我收到 Invalid CSRF token 错误。按照错误中给出的建议,我将 '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),“动作”本质上是一条路径,我在其中放置的任何参数都会找到返回应用程序的路径。而且,确实,我发现 x-csrf-token = "" 被传回路由器,所以 @csrf_token 一定不正确。我不确定 csrf_token 的确切来源,所以我不知道如何引用它(或者我这样做完全错误)。

任何想法都将不胜感激。

【问题讨论】:

    标签: elixir phoenix-framework


    【解决方案1】:

    我在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) %>">
    

    就是这样!

    【讨论】:

      【解决方案2】:

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

      cat ./deps/phoenix/mix.exs | grep version
      

      这显示了你在 deps 目录中拥有的 phoenix。

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

      来自Phoenix changelog for v0.9.0 (2015-02-12)

      [Plug] Plug.CSRFProtection 现在使用 cookie 代替 session 和 需要一个“_csrf_token”参数而不是“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 中使用 JQuery cookie 访问 cookie 的方式,以便于访问。通过运行以下命令,您应该能够在浏览器中看到该值

      $.cookie("_csrf_token")
      

      这可能会返回类似的东西

      "K9UDa23e1sacdadfmvu zzOD9VBHTSr1c/lcvWY="
      

      请注意,在 phoenix 中,空格被 url 编码为 +,这仍然导致 CSRF 失败。现在是 Plug 中的一个错误,或者只是需要处理的东西,我不确定,所以现在我只是明确地处理 +

      $.cookie("_csrf_token").replace(/\s/g, '+');
      

      通过访问 CSRF 令牌,我们现在只需将 x-csrf-token 添加到您的请求标头 (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 作为参数发回,但我更喜欢上面的,而且对我来说感觉更干净。

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

      【讨论】:

      • 感谢您的详细回复。原来我运行的是 Phoenix 0.8.0,所以下面的代码可以工作,但是当我升级到 0.9.0 时,你让我很头疼!
      • 再次感谢@a4word,我升级到 Phoenix 0.9.0 并更改了模板以从 cookie 中获取令牌。然而,奇怪的是,它似乎工作,但我仍然收到一个无效的 CSRF(跨站点伪造保护)令牌错误。令牌是ne0GATpoc/EW6jbIbC7tmfkAWl4qb1opTPWmmfYFTRY=(没有空格),模板创建了一个url POST /config/directories/9?x-csrf-token=ne0GATpoc%2FEW6jbIbC7tmfkAWl4qb1opTPWmmfYFTRY%3D 我不知道为什么Plug.CSRFProtection 不喜欢它。你知道还是我应该打开另一个问题?
      • 我将模板中令牌的名称从:'x-csrf-token": Map.get(@conn.req_cookies, "_csrf_token") 更改为:'_csrf_token': Map.get(@conn.req_cookies, "_csrf_token"),现在可以使用了。
      • 没错。在您的 POST 中看到 x-csrf-token 在 URL 上添加了一个参数(但它需要是一个 HTTP 标头)。也就是说,身份验证还将检查 _csrf_token 参数,是的,一切都可以使用。
      【解决方案3】:

      在 Phoenix 0.13 版上,您可以这样做

      <input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>">
      

      因为在文件 web/web.ex 中有一个这个函数的导入。

      【讨论】:

      • 谢谢!我刚刚升级到 0.13。
      • 谢谢——这是从 v0.12 -> 0.13 对生成的 web/web.ex 的唯一更改
      【解决方案4】:

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

      Example from upgrade guide:

      <%= 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: "对于 'post' 请求,表单标签会自动包含一个名为 _csrf_token 的输入标签。"

      【讨论】:

        【解决方案5】:

        就我而言,是plug :scrub_params 引起了问题。 评论该行后,它起作用了。但需要确保修复它,因为如果没有scrub_params,应用程序将不安全。

        【讨论】:

          【解决方案6】:

          我的解决办法是:

          • 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]

          • 在我的表单中添加一个隐藏参数:

            &lt;input type="hidden" name="_csrf_token" value="&lt;%= get_csrf_token() %&gt;" /&gt;

          【讨论】:

            猜你喜欢
            • 2021-10-17
            • 1970-01-01
            • 1970-01-01
            • 2011-10-19
            • 1970-01-01
            • 2017-07-29
            • 2011-09-11
            • 2018-04-25
            • 2017-04-12
            相关资源
            最近更新 更多