Laravel 5 页面中所有表单的 CSRF 全局令牌隐藏字段
Laravel 5 CSRF global token hidden field for all forms in a page
我最近迁移到 Laravel 5,现在每次 post 提交都会进行 CSRF 检查。我考虑过删除它,但我想遵循最佳做法,所以我会保留它。
另一方面,我在提交 ajax 请求时遇到问题。我的页面有多个表单,有些提交甚至不是来自表单,只是普通的 ajax 调用。我的想法是在页面上有一个隐藏的 "token" 输入并将其附加到每个提交。使用通用的单一令牌输入有什么缺点吗?
另外,请问如何输出token?只在页脚创建一个隐藏输入可以吗?
我没有看到任何缺点。您可以轻松地在布局文件中创建全局令牌字段:
<input type="hidden" name="_token" id="csrf-token" value="{{ Session::token() }}" />
或者,如果您使用表单生成器:
{!! Form::token() !!}
在 jQuery 中,您可以使用类似 的方式将令牌附加到每个请求。
您需要传递 header X-XSRF-TOKEN
,其中包含 csrf-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'));
}
});
以下是我如何让我的 CSRF 在我最近升级为使用 Laravel 5:
的 jQuery 移动应用程序中的所有不同场景中工作的一些摘录
我在变量中添加了一个加密的 csrf 令牌,该变量将传递到我的主要基础控制器中的视图:
app\Http\Controllers\MyController.php
$this->data['encrypted_csrf_token'] = Crypt::encrypt(csrf_token());
然后,我在主视图中添加了元标记 header:
resources\views\partials\htmlHeader.blade.php
<meta name="_token" content="{!! $encrypted_csrf_token !!}"/>
然后,我还按照某些论坛的建议添加了这个 jquery 片段:
$(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));
}
在我补充之前,几乎所有的 AJAX POST(包括表单提交和延迟加载列表视图)都因 TokenMismatchException
而失败。
编辑:
转念一想,我不确定将 session 令牌与 cookie 中设置的令牌进行比较有多大意义(这首先来自 session 令牌,对吗?) .那可能只是绕过了这一切的安全性。
我认为我的主要问题是上面的 jquery 片段,它应该将 X-XSRF-TOKEN header 添加到每个 ajax 请求。在我为插件本身添加一些选项之前,这在我的 jQuery 移动应用程序(特别是在我的 lazyloader plugin 中)对我不起作用。我添加了一个新的默认选择器 csrf
(在本例中为 meta[name="_token"]
)和一个新的默认设置 csrfHeaderKey
(在本例中为 X-XSRF-TOKEN
)。基本上,在插件初始化期间,如果 csrf
选择器(默认或 user-defined)可以找到一个新的 _headers
属性,则使用 CSRF 令牌对其进行初始化。然后,在可以触发 ajax POST 的 3 个不同位置(重置 session 变量或延迟加载列表视图时)的 headers 选项 $.ajax 设置为 _headers
中的任何内容。
无论如何,由于 server-side 上收到的 X-XSRF-TOKEN 来自加密的 meta _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)));
}
我想你可以做这样的事情(没有测试,如果有机会会更新)
$(document).on('submit', 'form', function(e)
$(this).append('<input name="_token" value="{{{ Session::token() }}}">);
});
您实际上可能希望将令牌存储在您在过期时重新更新的变量中。
在提交时附加它的好处是,如果您通过 ajax 附加元素,我认为它仍然可以工作而无需添加任何其他内容。
编辑: 这是一篇关于将 Rails UJS 与 Laravel 结合使用的好文章(其中包括此 auto CRSF 令牌 功能):https://medium.com/@barryvdh/unobtrusive-javascript-with-jquery-ujs-and-laravel-e05f444d3439
有一个 helper 可以在表单中添加表单标记。你可以只使用
{!! csrf_field() !!}
在表格内。它将添加隐藏的输入和令牌。
您可以在页面底部使用类似这样的内容:
$('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文件。如果我们不使用 blade 而想使用 JS 文件怎么办。 blade 语法在那里不起作用。
下面是适用于表单和 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);
}
}
});
我最近迁移到 Laravel 5,现在每次 post 提交都会进行 CSRF 检查。我考虑过删除它,但我想遵循最佳做法,所以我会保留它。
另一方面,我在提交 ajax 请求时遇到问题。我的页面有多个表单,有些提交甚至不是来自表单,只是普通的 ajax 调用。我的想法是在页面上有一个隐藏的 "token" 输入并将其附加到每个提交。使用通用的单一令牌输入有什么缺点吗?
另外,请问如何输出token?只在页脚创建一个隐藏输入可以吗?
我没有看到任何缺点。您可以轻松地在布局文件中创建全局令牌字段:
<input type="hidden" name="_token" id="csrf-token" value="{{ Session::token() }}" />
或者,如果您使用表单生成器:
{!! Form::token() !!}
在 jQuery 中,您可以使用类似
您需要传递 header X-XSRF-TOKEN
,其中包含 csrf-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'));
}
});
以下是我如何让我的 CSRF 在我最近升级为使用 Laravel 5:
的 jQuery 移动应用程序中的所有不同场景中工作的一些摘录我在变量中添加了一个加密的 csrf 令牌,该变量将传递到我的主要基础控制器中的视图:
app\Http\Controllers\MyController.php
$this->data['encrypted_csrf_token'] = Crypt::encrypt(csrf_token());
然后,我在主视图中添加了元标记 header:
resources\views\partials\htmlHeader.blade.php
<meta name="_token" content="{!! $encrypted_csrf_token !!}"/>
然后,我还按照某些论坛的建议添加了这个 jquery 片段:
$(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));
}
在我补充之前,几乎所有的 AJAX POST(包括表单提交和延迟加载列表视图)都因 TokenMismatchException
而失败。
编辑: 转念一想,我不确定将 session 令牌与 cookie 中设置的令牌进行比较有多大意义(这首先来自 session 令牌,对吗?) .那可能只是绕过了这一切的安全性。
我认为我的主要问题是上面的 jquery 片段,它应该将 X-XSRF-TOKEN header 添加到每个 ajax 请求。在我为插件本身添加一些选项之前,这在我的 jQuery 移动应用程序(特别是在我的 lazyloader plugin 中)对我不起作用。我添加了一个新的默认选择器 csrf
(在本例中为 meta[name="_token"]
)和一个新的默认设置 csrfHeaderKey
(在本例中为 X-XSRF-TOKEN
)。基本上,在插件初始化期间,如果 csrf
选择器(默认或 user-defined)可以找到一个新的 _headers
属性,则使用 CSRF 令牌对其进行初始化。然后,在可以触发 ajax POST 的 3 个不同位置(重置 session 变量或延迟加载列表视图时)的 headers 选项 $.ajax 设置为 _headers
中的任何内容。
无论如何,由于 server-side 上收到的 X-XSRF-TOKEN 来自加密的 meta _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)));
}
我想你可以做这样的事情(没有测试,如果有机会会更新)
$(document).on('submit', 'form', function(e)
$(this).append('<input name="_token" value="{{{ Session::token() }}}">);
});
您实际上可能希望将令牌存储在您在过期时重新更新的变量中。
在提交时附加它的好处是,如果您通过 ajax 附加元素,我认为它仍然可以工作而无需添加任何其他内容。
编辑: 这是一篇关于将 Rails UJS 与 Laravel 结合使用的好文章(其中包括此 auto CRSF 令牌 功能):https://medium.com/@barryvdh/unobtrusive-javascript-with-jquery-ujs-and-laravel-e05f444d3439
有一个 helper 可以在表单中添加表单标记。你可以只使用
{!! csrf_field() !!}
在表格内。它将添加隐藏的输入和令牌。
您可以在页面底部使用类似这样的内容:
$('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文件。如果我们不使用 blade 而想使用 JS 文件怎么办。 blade 语法在那里不起作用。
下面是适用于表单和 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);
}
}
});