jQuery将CSRF令牌添加到所有$.post()请求的数据中

37

我正在开发一个使用Laravel 5框架的应用程序,它默认启用了所有POST请求的CSRF保护。我喜欢这种额外的安全性,所以我正在尝试让它正常工作。

当我进行一个简单的$.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');

是否可能将此附加到所有传出的$.post()请求的json数据中?我尝试使用标头,但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);
    }
});

我是否正确理解您想让您的数据对象具有_token字段?(例如,类似于options['data']._token = csrf_token?)如果您有一个工作请求的示例,那将非常有帮助。 - apsillers
为了使请求起作用,我必须直接将令牌添加到发布数据中 - 因此{id:userID,'_token':token} - NightMICU
好的,你在上面的评论中基本上已经回答了,但是并没有一个可用的解决方案 - options['data']._token = csrf_token; 似乎不能完成任务,但很接近。请发布一个可行的答案,你将得到一个+1和被接受的答案。 :) - NightMICU
请查看此答案 - https://dev59.com/77Dla4cB1Zd3GeqP61qd#53684929 - Prateek
7个回答

40

根据 Laravel 文档:

您可以将令牌存储在“meta”标记中:

创建 meta 标签后,您可以指示像 jQuery 这样的库将令牌添加到所有请求标头中。这为基于 AJAX 的应用程序提供了简单、方便的 CSRF 保护:

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

因此,例如您可以像下面这样发起请求。

在视图中添加此 meta 标记:

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

这是一个示例脚本,您可以使用它与Laravel进行通信(单击ID为“some-id”的元素时发送请求,并在ID为“result”的元素中查看响应):

这是一个可用于与 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>

34

你的$.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


1
好的,我只能通过 options['data'] = options['data'] + '&_token=' + csrf_token; 让它工作 - 点符号似乎不起作用。这看起来更好,你有什么想法可能是问题出在哪里? - NightMICU
@NightMICU 我明白了,问题不在于点符号表示法,而是jQuery在prefilter获取数据对象之前将其转换为查询字符串。我已经进行了编辑(但我还没有测试,所以如果仍然出现问题请告诉我)。 - apsillers
@apsillers 不是的,我的问题具体在于 ajaxPrefilter。它说 options.data 未定义。 - r3wt
我认为我的问题可能更深层次。我正在使用 $(this).serialize() 创建选项,我认为它可能与 $.param 不兼容。 - r3wt

14

总体上,我同意Kornel提出的概念,除了一点。

是的,Laravel的文档建议使用$.ajaxSetup,但不建议使用该方法,因为它会影响所有后续的Ajax请求。更正确的做法是为每个请求设置Ajax设置。虽然您可以重新设置这些内容:

所有使用任何函数的后续Ajax调用都将使用新设置,除非被单独调用覆盖,直到下一次调用$.ajaxSetup()

如果您使用$.ajax(),则更方便地利用data属性或headers。 Laravel允许CSRF-token作为请求参数或头文件。

首先,您需要将以下元标记添加到视图中:

<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"
});

11

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);
        }
    }
});

0

有一种更简单的方法可以做到这一点,您可以在发送之前对数据进行序列化。

<form method="post" id="formData">
 <input type="text" name="name" >
 <input type="hidden" name="_token" value="{{ csrf_token() }}">
 <input type="submit" id="sub" class="btn btn-default" value="Submit" />                            
</form>
<script>
    $(document).on('submit', '#formData', function (event) {
    event.preventDefault();
     var formData = $('#formData').serialize();
    $.ajax({
        url: url,
       type: "POST",
       data : formData,   
                success: function (result) {
                    console.log(result);                             
                }
           });
        });

    });
</script>

所有带有name属性的元素将被放入查询并提交


0

我认为上述解决方案可能不太可行。当你执行以下操作时:

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;

0

不需要设置任何元标签,也不需要使用csrf_token()csrf_field()

您可以使用此jQuery代码片段:

$.ajaxPrefilter(function( settings, original, xhr ) {
    if (['post','put','delete'].includes(settings.type.toLowerCase())
        && !settings.crossDomain) {
            xhr.setRequestHeader("X-XSRF-TOKEN", getCookie('XSRF-TOKEN'));
    }
});

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

只有少数其他答案尝试设置csrf头,但根据Laravel文档(这里),头键应该是"X-XSRF-TOKEN",而Laravel本身在名为"XSRF-TOKEN"的cookie中为我们提供了所需的值。

因此,只需更正键并编辑几行即可,谢谢。


1
操作可能无法访问这些cookie。有很多配置CSRF令牌的方法,而cookie可能不是合适的选择。 - Jamie Marshall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接