JQuery добавить токен CSRF ко всем данным $.post() запросов

Я работаю над приложением Laravel 5, для которого включена защита CSRF по умолчанию для всех запросов POST. Мне нравится эта добавленная безопасность, поэтому я пытаюсь работать с ней.

При создании простого запроса $.post() я получил ошибку 'Illuminate\Session\TokenMismatchException', потому что в данных POST отсутствовал необходимый ввод формы _token. Ниже приведен пример запроса $.post:

var userID = $("#userID").val();
$.post('/admin/users/delete-user', {id:userID}, function() {
// User deleted
});

У меня есть мой токен CSRF, который хранится в мета-поле в моем заголовке и может легко получить к нему доступ, используя:

var csrf_token = $('meta[name="csrf-token"]').attr('content');

Можно ли добавить это к json-данным во все исходящие запросы $.post()? Я пытался использовать заголовки, но Laravel, похоже, не узнал их -

var csrf_token = $('meta[name="csrf-token"]').attr('content');
alert(csrf_token);
$.ajaxPrefilter(function(options, originalOptions, jqXHR){
    if (options['type'].toLowerCase() === "post") {
        jqXHR.setRequestHeader('X-CSRFToken', csrf_token);
    }
});

Ответы

Ответ 1

Ваш подход $.ajaxPrefilter является хорошим. Однако вам не нужно добавлять заголовок; вам просто нужно добавить свойство в строку data.

Данные предоставляются как второй аргумент $.post, а затем форматируются как строка запроса (id=foo&bar=baz&...) до того, как prefilter получит доступ к опции data. Таким образом, вам нужно добавить свое собственное поле в строку запроса:

var csrf_token = $('meta[name="csrf-token"]').attr('content');
$.ajaxPrefilter(function(options, originalOptions, jqXHR){
    if (options.type.toLowerCase() === "post") {
        // initialize `data` to empty string if it does not exist
        options.data = options.data || "";

        // add leading ampersand if `data` is non-empty
        options.data += options.data?"&":"";

        // add _token entry
        options.data += "_token=" + encodeURIComponent(csrf_token);
    }
});

Это превратит id=userID в id=userID&_token=csrf_token.

Ответ 2

Из документации Laravel:

Вы могли бы, например, сохранить токен в теге meta:

Как только вы создали метатег, вы можете указать библиотеку, например jQuery, чтобы добавить токен ко всем заголовкам запроса. Это обеспечивает простой, удобная защита CSRF для ваших приложений на основе AJAX:

$. AjaxSetup ({         заголовки: {              "X-CSRF-TOKEN": $('meta [name= "csrf-token" ]'). Attr ('content')         }});

Так, например, вы можете сделать запрос, как показано ниже.

Добавьте этот метатег к вашему виду:

<meta name="csrf-token" content="{{ csrf_token() }}">

И это пример script, с которым вы можете общаться с Laravel (отправляет запрос, когда вы нажимаете элемент с id = "some-id", и вы можете видеть ответ в элементе с id = "result" ):

<script type="text/javascript">
    $(document).ready(function(){

        $.ajaxSetup({
            headers:
            { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }
        });

        $("#some-id").on("click", function () {
            var request;


            request = $.ajax({
                url: "/your/url",
                method: "POST",
                data:
                {
                    a: 'something',
                    b: 'something else',
                },
                datatype: "json"
            });

            request.done(function(msg) {
                $("#result").html(msg);
            });

            request.fail(function(jqXHR, textStatus) {
                $("#result").html("Request failed: " + textStatus);
            });
        });

    });
</script>

Ответ 3

В общем, я согласен с концепцией, предложенной Корнелем, кроме одного.

Да, Laravel рекомендует docs использовать $.ajaxSetup, но он не рекомендуется, так как этот метод влияет на все последующие запросы ajax. Правильнее установить параметры ajax для каждого запроса. Хотя вы можете переустановить материал:

Все последующие вызовы Ajax с использованием любой функции будут использовать новые настройки, если они не переопределены отдельными вызовами, до следующего вызова $.ajaxSetup()

Если вы используете $.ajax(), более удобно использовать либо свойство data, либо headers. Laravel позволяет CSRF-токен как параметр запроса, так и заголовок.

Во-первых, вы добавляете следующий метатег в представление

<meta name="csrf-token" content="{{ csrf_token() }}">

И затем сделайте запрос ajax в любом случае:

$.ajax({
    url: "/your/url",
    method: "POST",
    data:
    {
        a: 'something',
        b: 'something else',
        _token: $('meta[name="csrf-token"]').attr('content')
    },
    datatype: "json"
});

ИЛИ

$.ajax({
    url: "/your/url",
    method: "POST",
    data:
    {
        a: 'something',
        b: 'something else',
    },
    headers: 
    {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
    datatype: "json"
});

Ответ 4

Я думаю, что вышеупомянутое решение может не работать. Когда вы выполните:

var x; 
x + ""
// "undefined" + empty string coerces value to string 

Вы получите данные типа "undefined_token = xxx"

Когда вы используете вышеупомянутое решение для удаления laravel, например, вы должны проверить следующее:

if (typeof options.data === "undefined")
    options.data = "";
else
    options.data += "&";
options.data = "_token=" + csrf_token;

Ответ 5

Документация Django на CSRF дает хороший фрагмент кода с ajaxSetup для автоматического добавления соответствующего заголовка ко всем типам запросов, где это важно

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});