Как я могу предоставить AntiForgeryToken при публикации данных JSON с использованием $ .ajax? - PullRequest
69 голосов
/ 25 мая 2010

Я использую код, указанный ниже:

Сначала я заполню переменную массива правильными значениями для действия контроллера.

Используя приведенный ниже код, я думаю, что это будет очень просто, просто добавив следующую строку в код JavaScript:

data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val();

<%= Html.AntiForgeryToken() %> находится на своем месте, и действие имеет [ValidateAntiForgeryToken]

Но мое действие контроллера постоянно говорит: «Неверный токен подделки»

Что я здесь не так делаю?

код

data["fiscalyear"] = fiscalyear;
data["subgeography"] = $(list).parent().find('input[name=subGeography]').val();
data["territories"] = new Array();

$(items).each(function() {
    data["territories"].push($(this).find('input[name=territory]').val());
});

    if (url != null) {
        $.ajax(
        {
            dataType: 'JSON',
            contentType: 'application/json; charset=utf-8',
            url: url,
            type: 'POST',
            context: document.body,
            data: JSON.stringify(data),
            success: function() { refresh(); }
        });
    }

Ответы [ 13 ]

1 голос
/ 16 июля 2013

При публикации JSON мне нужно было немного сомневаться в проверке токенов защиты от подделки, но это сработало.

//If it's not a GET, and the data they're sending is a string (since we already had a separate solution in place for form-encoded data), then add the verification token to the URL, if it's not already there.
$.ajaxSetup({
    beforeSend: function (xhr, options) {
        if (options.type && options.type.toLowerCase() !== 'get' && typeof (options.data) === 'string' && options.url.indexOf("?__RequestVerificationToken=") < 0 && options.url.indexOf("&__RequestVerificationToken=") < 0) {
            if (options.url.indexOf('?') < 0) {
                options.url += '?';
            }
            else {
                options.url += '&';
            }
            options.url += "__RequestVerificationToken=" + encodeURIComponent($('input[name=__RequestVerificationToken]').val());
        }
    }
});

Но, как уже упоминали несколько человек, проверка только проверяет форму - не JSON и не строку запроса. Итак, мы отвергли поведение атрибута. Повторная реализация всей проверки была бы ужасной (и, вероятно, небезопасной), поэтому я просто переопределил свойство Form, чтобы, если токен был передан в QueryString, иметь встроенную проверку THINK, которая была в форме.

Это немного сложно, потому что форма только для чтения, но выполнимая.

    if (IsAuth(HttpContext.Current) && !IsGet(HttpContext.Current))
    {
        //if the token is in the params but not the form, we sneak in our own HttpContext/HttpRequest
        if (HttpContext.Current.Request.Params != null && HttpContext.Current.Request.Form != null
            && HttpContext.Current.Request.Params["__RequestVerificationToken"] != null && HttpContext.Current.Request.Form["__RequestVerificationToken"] == null)
        {
            AntiForgery.Validate(new ValidationHttpContextWrapper(HttpContext.Current), null);
        }
        else
        {
            AntiForgery.Validate(new HttpContextWrapper(HttpContext.Current), null);
        }
    }

    //don't validate un-authenticated requests; anyone could do it, anyway
    private static bool IsAuth(HttpContext context)
    {
        return context.User != null && context.User.Identity != null && !string.IsNullOrEmpty(context.User.Identity.Name);
    }

    //only validate posts because that's what CSRF is for
    private static bool IsGet(HttpContext context)
    {
        return context.Request.HttpMethod.ToUpper() == "GET";
    }

...

internal class ValidationHttpContextWrapper : HttpContextBase
{
    private HttpContext _context;
    private ValidationHttpRequestWrapper _request;

    public ValidationHttpContextWrapper(HttpContext context)
        : base()
    {
        _context = context;
        _request = new ValidationHttpRequestWrapper(context.Request);
    }

    public override HttpRequestBase Request { get { return _request; } }

    public override IPrincipal User
    {
        get { return _context.User; }
        set { _context.User = value; }
    }
}

internal class ValidationHttpRequestWrapper : HttpRequestBase
{
    private HttpRequest _request;
    private System.Collections.Specialized.NameValueCollection _form;

    public ValidationHttpRequestWrapper(HttpRequest request)
        : base()
    {
        _request = request;
        _form = new System.Collections.Specialized.NameValueCollection(request.Form);
        _form.Add("__RequestVerificationToken", request.Params["__RequestVerificationToken"]);
    }

    public override System.Collections.Specialized.NameValueCollection Form { get { return _form; } }

    public override string ApplicationPath { get { return _request.ApplicationPath; } }
    public override HttpCookieCollection Cookies { get { return _request.Cookies; } }
}

Есть некоторые другие вещи, которые отличаются от нашего решения (в частности, мы используем HttpModule, поэтому нам не нужно добавлять атрибут к каждому отдельному POST), который я оставил для краткости. Я могу добавить его при необходимости.

0 голосов
/ 19 ноября 2015

К сожалению для меня, другие ответы основаны на некотором форматировании запросов, обрабатываемых jquery, и ни один из них не работал при установке полезной нагрузки напрямую. (Если честно, поместить его в заголовок сработало бы, но я не хотел идти по этому пути.)

Чтобы выполнить это в функции beforeSend, работает следующее. $.params() преобразует объект в стандартный формат / кодированный в формате url.

Я пробовал все виды вариаций строкового json с токеном, и ни один из них не работал.

$.ajax({
...other params...,
beforeSend: function(jqXHR, settings){

    var token = ''; //get token

    data = {
        '__RequestVerificationToken' : token,
        'otherData': 'value'
     }; 
    settings.data = $.param(data);
    }
});

`` `

0 голосов
/ 20 февраля 2014

Вы должны поместить AntiForgeryToken в тег формы:

@using (Html.BeginForm(actionName:"", controllerName:"",routeValues:null, method: FormMethod.Get, htmlAttributes: new { @class="form-validator" }))
{
    @Html.AntiForgeryToken();
}

, затем в javascript измените следующий код на

var DataToSend = [];
DataToSend.push(JSON.stringify(data),$('form.form-validator').serialize());
$.ajax(
        {
            dataType: 'JSON',
            contentType: 'application/json; charset=utf-8',
            url: url,
            type: 'POST',
            context: document.body,
            data: DataToSend,
            success: function() { refresh(); }
        });

тогда вы сможете проверить запрос с помощью аннотаций ActionResult

[ValidateAntiForgeryToken]
        [HttpPost]

Надеюсь, это поможет.

...