Недавно я перешел на Laravel 5, и теперь проверка CSRF находится на каждой отправке сообщений. Я подумал об удалении, но я хочу следовать лучшим практикам, поэтому я сохраню это.
С другой стороны, у меня проблемы с отправкой ajax-запросов .. моя страница имеет несколько форм, а некоторые представления даже не из форм, а просто для вызовов ajax. Моя идея состоит в том, чтобы на странице был введен один скрытый «токен» и прикрепить его к каждой отправке. Есть ли недостатки в использовании этого универсального входа с одним токеном?
Кроме того, как я могу вывести токен? Было бы хорошо просто создать скрытый ввод в нижнем колонтитуле страницы?
Я не вижу недостатков. Вы можете легко создать глобальное поле маркера в вашем файле макета:
<input type="hidden" name="_token" id="csrf-token" value="{{ Session::token() }}" />
Или если вы используете построитель форм:
{!! Form::token() !!}
В jQuery вы можете использовать что-то вроде этого, чтобы прикрепить токен к каждому запросу.
Существует помощник, чтобы добавить формальный токен внутри форм. Вы можете просто использовать
{!! csrf_field() !!}
внутри форм. Он добавит скрытый ввод и токен.
Вот некоторые отрывки из того, как я получил свой CSRF, работающий для всех различных сценариев в моем приложении jQuery Mobile, которое я недавно обновил, чтобы использовать Laravel 5:
Я добавил зашифрованный токен 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, как это было предложено на некоторых форумах:
$(function () { $.ajaxSetup({ headers: { 'X-XSRF-TOKEN': $('meta[name="_token"]').attr('content') } }); });
Но ключ (по крайней мере, для моей настройки) заключался в добавлении проверки для файла cookie XSRF-TOKEN
в моем обычном промежуточном программном обеспечении VerifyCsrfToken: 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)); }
Прежде чем я добавил, что почти все мои POST-адреса AJAX (включая представления форм и просмотры списков lazyloading) не TokenMismatchException
из-за TokenMismatchException
.
EDIT: Во-вторых, я не уверен, насколько это важно, чтобы сравнить токен сеанса с тем, который был установлен в файле cookie (который, скорее всего, исходил из токена сеанса в первую очередь?). Возможно, это просто обошло безопасность всего этого.
Я думаю, что моя основная проблема заключалась в фрагменте jquery, выше которого предполагалось добавлять заголовок X-XSRF-TOKEN для каждого запроса ajax. Это не работало для меня в моем приложении jQuery Mobile (в частности, в моем плагине lazyloader ), пока я не добавил некоторые параметры для самого модуля. Я добавил новый селектор по умолчанию csrf
(который в этом случае будет meta[name="_token"]
) и новый параметр по умолчанию csrfHeaderKey
(который в этом случае будет X-XSRF-TOKEN
). В принципе, при инициализации плагина новое свойство _headers
инициализируется маркером CSRF, если он csrf
селектором csrf
(по умолчанию или определяется пользователем). Затем, в трех разных местах, где может быть запущен ajax POST (при сбросе переменных сеанса или при lazyloading listview), опция headers в $ .ajax устанавливается с любым, что находится в _headers
.
Во всяком случае, поскольку 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))); }
Вы можете использовать что-то вроде этого в нижней части страницы:
$('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()}}"); } } });
Вам нужно пройти по заголовку X-XSRF-TOKEN
который содержит зашифрованную версию csrf-token
.
Есть два способа сделать это, о которых я знаю. Вы можете зашифровать токен и передать его вместе с представлением:
$xsrfToken = app('Illuminate\Encryption\Encrypter')->encrypt(csrf_token()); return view('some.ajax.form.view')->with('xsrf_token', $xsrfToken);
Или вы можете захватить токен из файлов cookie с помощью JavaScript (Угловая делает это легко). В 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')); } });
Я думаю, вы можете сделать что-то вроде этого (не проверено будет обновляться, если я получаю шанс)
$(document).on('submit', 'form', function(e) $(this).append('<input name="_token" value="{{{ Session::token() }}}">); });
вы действительно можете захотеть сохранить токен в переменной, которую вы повторно заполняете по мере ее истечения.
Преимущество добавления его в submit – если вы добавляете элементы через ajax, я думаю, что он все равно будет работать без необходимости добавлять что-либо еще.
EDIT: Вот отличная статья об использовании Rails UJS с Laravel (которая включает в себя эту функцию автоматического маркера CRSF ): https://medium.com/@barryvdh/unobtrusive-javascript-with-jquery-ujs-and-laravel-e05f444d3439