【问题标题】:Laravel 5 CSRF global token hidden field for all forms in a page页面中所有表单的 Laravel 5 CSRF 全局令牌隐藏字段
【发布时间】:2015-04-14 13:11:25
【问题描述】:

我最近迁移到 Laravel 5,现在 CSRF 检查每个帖子提交。我考虑过删除它,但我想遵循最佳做法,所以我会保持这种方式。

另一方面,我在提交 ajax 请求时遇到问题。我的页面有多个表单,有些提交甚至不是来自表单,只是简单的 ajax 调用。我的想法是在页面上有一个隐藏的“令牌”输入并将其附加到每个提交中。使用通用的单一令牌输入有什么缺点吗?

另外,我怎样才能输出令牌?可以只在页脚上创建一个隐藏的输入吗?

【问题讨论】:

    标签: php laravel csrf


    【解决方案1】:

    有一个helper 可以在表单中添加表单令牌。你可以使用

    {!! csrf_field() !!}
    

    在表格内。它将添加隐藏的输入和令牌。

    【讨论】:

    • 从 Laravel >= 5.1 开始,您不再需要使用原始输出,您可以使用常规转义语法 {{ csrf_field() }}
    • 自 Laravel 5.6 起你可以使用@csrf
    【解决方案2】:

    我没有看到任何缺点。您可以在布局文件中轻松创建全局标记字段:

    <input type="hidden" name="_token" id="csrf-token" value="{{ Session::token() }}" />
    

    或者如果您使用表单生成器:

    {!! Form::token() !!}
    

    在 jQuery 中,您可以使用 this 之类的东西将令牌附加到每个请求。

    【讨论】:

    • 很好,在 L5 中会是 {!!形式::token() !!}
    • @RaphaelCabrera 是的,你是对的。该死的,我永远不会习惯{!!...幸运的是你可以改变它们:)
    • 验证错误后无法重新提交具有相同令牌的表单
    • @mvladk 真的吗?如果是这种情况,您必须将带有验证错误的新令牌发回,以便将其用于下一个请求。
    • @mvladk 不幸的是,我现在真的没有时间。请提出一个新问题,人们会帮助你:)
    【解决方案3】:

    你可以在页面底部使用这样的东西:

    $('form').append('{{csrf_field()}}');
    

    这会将隐藏的输入附加到您的所有forms

    <input type="hidden" name="_token" value="yIcHUzipr2Y2McGE3EUk5JwLOPjxrC3yEBetRtlV">
    

    对于您的所有 AJAX 请求:

    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            //////////// Only for your domain
            if (settings.url.indexOf(document.domain) >= 0) {
                xhr.setRequestHeader("X-CSRF-Token", "{{csrf_token()}}");
            }
        }
    });
    

    【讨论】:

    • 附加表单在 JS 文件中不起作用。它只适用于刀片文件。
    【解决方案4】:

    以下是我如何让我的 CSRF 在我最近升级为使用 Laravel 5 的 jQuery Mobile 应用程序中的所有不同场景中工作的一些摘录:

    我在变量中添加了一个加密的 csrf 令牌,该变量将传递给我的主基本控制器中的视图: app\Http\Controllers\MyController.php

    $this->data['encrypted_csrf_token'] = Crypt::encrypt(csrf_token());
    

    然后,我在主视图标题中添加了元标记: resources\views\partials\htmlHeader.blade.php

    <meta name="_token" content="{!! $encrypted_csrf_token !!}"/>
    

    然后,我还按照一些论坛的建议添加了这个 jquery sn-p:

        $(function () {
                $.ajaxSetup({
                        headers: {
                                'X-XSRF-TOKEN': $('meta[name="_token"]').attr('content')
                        }
                });
        });
    

    但是,关键(至少对于我的设置而言)是在我的自定义 VerifyCsrfToken 中间件中添加了对 XSRF-TOKEN cookie 的检查: app\Http\Middleware\VerifyCsrfToken.php:

        /**
         * Determine if the session and input CSRF tokens match.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return bool
         */
        protected function tokensMatch($request)
        {       
                $token = $request->session()->token();
    
                $header = $request->header('X-XSRF-TOKEN');
    
                $cookie = $request->cookie('XSRF-TOKEN');
    
                return StringUtils::equals($token, $request->input('_token')) ||
                       ($header && StringUtils::equals($token, $this->encrypter->decrypt($header))) ||
                        ($cookie && StringUtils::equals($token, $cookie));
        }
    

    在我添加之前,由于TokenMismatchException,我几乎所有的 AJAX POST(包括表单提交和延迟加载列表视图)都失败了。

    编辑: 再想一想,我不确定将会话令牌与 cookie 中的集合进行比较有多大意义(这首先来自会话令牌,对吧?)。这可能只是绕过了这一切的安全性。

    我认为我的主要问题是上面的 jquery sn-p 应该将 X-XSRF-TOKEN 标头添加到每个 ajax 请求中。在我为插件本身添加一些选项之前,这在我的 jQuery Mobile 应用程序中(特别是在我的 lazyloader plugin 中)对我不起作用。我添加了一个新的默认选择器csrf(在这种情况下为meta[name="_token"])和一个新的默认设置csrfHeaderKey(在这种情况下为X-XSRF-TOKEN)。基本上,在插件初始化期间,如果 csrf 选择器(默认或用户定义)可定位一个新的 _headers 属性,则使用 CSRF 令牌初始化。然后,在可以触发 ajax POST 的 3 个不同位置(重置会话变量或延迟加载列表视图时),$.ajax 的 headers 选项设置为 _headers 中的任何内容。

    无论如何,由于服务器端收到的 X-XSRF-TOKEN 来自加密的元 _token,我认为 CSRF 保护现在可以正常工作。

    我的app\Http\Middleware\VerifyCsrfToken.php 现在看起来像这样(这基本上回到了 Laravel 5 提供的默认实现 - LOL):

        /**
         * Determine if the session and input CSRF tokens match.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return bool
         */
        protected function tokensMatch($request)
        {
                $token = $request->session()->token();
    
                $_token = $request->input('_token');
    
                $header = $request->header('X-XSRF-TOKEN');
    
                return StringUtils::equals($token, $_token) ||
                       ($header && StringUtils::equals($token, $this->encrypter->decrypt($header)));
        }
    

    【讨论】:

      【解决方案5】:

      我认为你可以做这样的事情(如果我有机会,未测试会更新)

      $(document).on('submit', 'form', function(e)
            $(this).append('<input name="_token" value="{{{ Session::token() }}}">);
      });
      

      您实际上可能希望将令牌存储在一个变量中,以便在它过期时重新更新。

      在提交时附加它的好处是,如果您通过 ajax 附加元素,我认为它仍然可以工作而无需添加任何其他内容。

      编辑:这是一篇关于在 Laravel 中使用 Rails UJS 的精彩文章(包括此自动 CRSF 令牌 功能):https://medium.com/@barryvdh/unobtrusive-javascript-with-jquery-ujs-and-laravel-e05f444d3439

      【讨论】:

        【解决方案6】:

        您需要传递包含csrf-token 的加密版本的标头X-XSRF-TOKEN

        据我所知,有两种方法可以做到这一点。您可以加密令牌并将其传递给视图:

        $xsrfToken = app('Illuminate\Encryption\Encrypter')->encrypt(csrf_token());
        
        return view('some.ajax.form.view')->with('xsrf_token', $xsrfToken);
        

        或者您可以使用 JavaScript 从 cookie 中获取令牌(Angular 使这很容易)。在 vanilla JS 中,你可能会这样做:

        function getCookie(name) {
            var pattern = RegExp(name + "=.[^;]*")
            matched = document.cookie.match(pattern)
            if (matched) {
                var cookie = matched[0].split('=')
                return decodeURIComponent(cookie[1])
            }
            return false
        }
        

        在 jQuery 中,您可能会为 ajax 请求执行类似的操作:

        $.ajax({
            // your request
            //
            beforeSend: function(request) {
                return request.setRequestHeader('X-XSRF-TOKEN', getCookie('XSRF-TOKEN'));
            }
        });
        

        【讨论】:

          【解决方案7】:

          所有答案都不包括 JS 文件。如果我们不使用刀片并且我们想使用 JS 文件怎么办。刀片语法在那里不起作用。

          所以这里的代码适用于表单和 ajax。

          var csrf = document.querySelector('meta[name="csrf-token"]').content;
          var csrf_field = '<input type="hidden" name="_token" value=“'+csrf+'”>';
          
          $('form').append(csrf_field);
          
          $.ajaxSetup({
              beforeSend: function (xhr, settings) {
                  if (settings.url.indexOf(document.domain) >= 0) {
                      xhr.setRequestHeader("X-CSRF-Token", csrf);
                  }
              }
          });
          

          【讨论】:

            猜你喜欢
            • 2020-10-21
            • 2017-12-12
            • 2016-09-11
            • 1970-01-01
            • 2018-09-22
            • 2011-10-16
            • 1970-01-01
            • 1970-01-01
            • 2016-09-20
            相关资源
            最近更新 更多