Как я могу предоставить 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 ]

56 голосов
/ 24 июня 2014

Вам не нужно решение ValidationHttpRequestWrapper начиная с MVC 4. Согласно этой ссылке .

  1. Поместите токен в заголовки.
  2. Создать фильтр.
  3. Поместите атрибут в ваш метод.

Вот мое решение:

var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers['__RequestVerificationToken'] = token;
$.ajax({
    type: 'POST',
    url: '/MyTestMethod',
    contentType: 'application/json; charset=utf-8',
    headers: headers,
    data: JSON.stringify({
        Test: 'test'
    }),
    dataType: "json",
    success: function () {},
    error: function (xhr) {}
});


[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        var httpContext = filterContext.HttpContext;
        var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
        AntiForgery.Validate(cookie != null ? cookie.Value : null, httpContext.Request.Headers["__RequestVerificationToken"]);
    }
}


[HttpPost]
[AllowAnonymous]
[ValidateJsonAntiForgeryToken]
public async Task<JsonResult> MyTestMethod(string Test)
{
    return Json(true);
}
47 голосов
/ 25 мая 2010

Что неправильно, так это то, что действие контроллера, которое должно обрабатывать этот запрос и которое помечено [ValidateAntiForgeryToken], ожидает, что параметр с именем __RequestVerificationToken будет POSTed вместе с запросом.

Нет такого параметра POSTed, как вы используете JSON.stringify(data), который преобразует вашу форму в ее JSON-представление, и поэтому выдается исключение.

Итак, я вижу здесь два возможных решения:

Номер 1: используйте x-www-form-urlencoded вместо JSON для отправки параметров запроса:

data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val();
data["fiscalyear"] = fiscalyear;
// ... other data if necessary

$.ajax({
    url: url,
    type: 'POST',
    context: document.body,
    data: data,
    success: function() { refresh(); }
});

Номер 2: разделить запрос на два параметра:

data["fiscalyear"] = fiscalyear;
// ... other data if necessary
var token = $('[name=__RequestVerificationToken]').val();

$.ajax({
    url: url,
    type: 'POST',
    context: document.body,
    data: { __RequestVerificationToken: token, jsonRequest: JSON.stringify(data) },
    success: function() { refresh(); }
});

Так что во всех случаях вам нужно POST __RequestVerificationToken значение.

11 голосов
/ 04 апреля 2012

Я только что реализовал эту актуальную проблему в моем текущем проекте. Я сделал это для всех постов Ajax, которым требовался аутентифицированный пользователь.

Во-первых, я решил перехватить свои вызовы jQuery Ajax, чтобы не повторяться слишком часто. Этот фрагмент JavaScript гарантирует, что все вызовы ajax (post) добавят в запрос маркер проверки моего запроса. Примечание: имя __RequestVerificationToken используется платформой .NET, поэтому я могу использовать стандартные функции Anti-CSRF, как показано ниже.

$(document).ready(function () {
    securityToken = $('[name=__RequestVerificationToken]').val();
    $('body').bind('ajaxSend', function (elm, xhr, s) {
        if (s.type == 'POST' && typeof securityToken != 'undefined') {
            if (s.data.length > 0) {
                s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
            else {
                s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
        }
    });
});

В ваших представлениях, где требуется, чтобы токен был доступен для вышеуказанного кода JavaScript, просто используйте общий HTML-помощник. Вы можете добавить этот код куда угодно. Я поместил его в оператор if (Request.IsAuthenticated):

@Html.AntiForgeryToken() // You can provide a string as salt when needed which needs to match the one on the controller

В вашем контроллере просто используйте стандартный механизм ASP.NET MVC anti-CSRF. Я сделал это так (хотя я на самом деле использовал соль).

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
    // Do something
    return Json(true);
}

С помощью Firebug или аналогичного инструмента вы можете легко увидеть, как к вашим запросам POST теперь добавлен параметр __RequestVerificationToken.

7 голосов
/ 11 октября 2013

Вы можете установить атрибут $. Ajax 'traditional и установить его на true, чтобы отправлять данные JSON в виде URL-формы. Обязательно установите type:'POST'. С помощью этого метода вы можете даже отправлять массивы и вам не нужно использовать JSON.stringyfy или какие-либо изменения на стороне сервера (например, создание пользовательских атрибутов для сниффер заголовка)

Я пробовал это на ASP.NET MVC3 и jquery 1.7, и он работает

Ниже приведен фрагмент кода.

var data = { items: [1, 2, 3], someflag: true};

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

$.ajax({
    url: 'Test/FakeAction'
    type: 'POST',
    data: data
    dataType: 'json',
    traditional: true,
    success: function (data, status, jqxhr) {
        // some code after succes
    },
    error: function () {
        // alert the error
    }
});

Это будет соответствовать действию MVC со следующей подписью

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult FakeAction(int[] items, bool someflag)
{
}
5 голосов
/ 30 сентября 2011

Вам никогда не придется проверять AntiForgeryToken, когда вы получаете опубликованный JSON.

Причина в том, что AntiForgeryToken был создан для предотвращения CSRF. Поскольку вы не можете публиковать данные AJAX на другом хосте, а HTML-формы не могут отправлять JSON в качестве тела запроса, вам не нужно защищать свое приложение от опубликованного JSON.

5 голосов
/ 12 июля 2011

Вы не можете проверить содержимое типа contentType: 'application / json; charset = utf-8 ', поскольку ваша дата будет загружена не в свойство запроса Form , а в свойство InputStream, и у вас никогда не будет этого Request.Form ["__ RequestVerificationToken"].

Это всегда будет пустым, и проверка не удастся.

5 голосов
/ 24 сентября 2010

Я держу токен в своем объекте JSON, и в результате я изменил класс ValidateAntiForgeryToken, чтобы проверить InputStream объекта Request , когда сообщение является json. Я написал сообщение в блоге об этом, надеюсь, вы найдете его полезным.

3 голосов
/ 22 марта 2017

Я решил это глобально с RequestHeader.

$.ajaxPrefilter(function (options, originalOptions, jqXhr) {
    if (options.type.toUpperCase() === "POST") {
        // We need to add the verificationToken to all POSTs
        if (requestVerificationTokenVariable.length > 0)
            jqXhr.setRequestHeader("__RequestVerificationToken", requestVerificationTokenVariable);
    }
});

где requestVerificationTokenVariable - переменная строка, содержащая значение токена. Затем все вызовы ajax отправляют токен на сервер, но по умолчанию ValidateAntiForgeryTokenAttribute получает значение Request.Form. Я написал и добавил этот globalFilter, который копирует токен из заголовка в request.form, чем я могу использовать стандартный ValidateAntiForgeryTokenAttribute:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
      filters.Add(new GlobalAntiForgeryTokenAttribute(false));
}


public class GlobalAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
    private readonly bool autoValidateAllPost;

    public GlobalAntiForgeryTokenAttribute(bool autoValidateAllPost)
    {
        this.autoValidateAllPost = autoValidateAllPost;
    }

    private const string RequestVerificationTokenKey = "__RequestVerificationToken";
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var req = filterContext.HttpContext.Request;
        if (req.HttpMethod.ToUpperInvariant() == "POST")
        {
            //gestione per ValidateAntiForgeryToken che gestisce solo il recupero da Request.Form (non disponibile per le chiamate ajax json)
            if (req.Form[RequestVerificationTokenKey] == null && req.IsAjaxRequest())
            {
                var token = req.Headers[RequestVerificationTokenKey];
                if (!string.IsNullOrEmpty(token))
                {
                    req.Form.SetReadOnly(false);
                    req.Form[RequestVerificationTokenKey] = token;
                    req.Form.SetReadOnly(true);
                }
            }

            if (autoValidateAllPost)
                AntiForgery.Validate();
        }
    }
}

public static class NameValueCollectionExtensions
{
    private static readonly PropertyInfo NameObjectCollectionBaseIsReadOnly = typeof(NameObjectCollectionBase).GetProperty("IsReadOnly", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);

    public static void SetReadOnly(this NameValueCollection source, bool readOnly)
    {
        NameObjectCollectionBaseIsReadOnly.SetValue(source, readOnly);
    }
}

Эта работа для меня:)

2 голосов
/ 09 июля 2010

Посетите Блог Диксина , чтобы узнать, как именно это сделать.

Кроме того, почему бы не использовать $ .post вместо $ .ajax?

Наряду с плагином jQuery на этой странице вы можете сделать что-то простое:

        data = $.appendAntiForgeryToken(data,null);

        $.post(url, data, function() { refresh(); }, "json");
1 голос
/ 23 октября 2018

Публикация модели на основе AJAX с помощью AntiForgerytoken может быть немного упрощена с помощью библиотеки Newtonsoft.JSON
Ниже подход работал для меня:
Держите свой пост AJAX так:

    $.ajax(
    {
        dataType: 'JSON',
        url: url,
        type: 'POST',
        context: document.body,
        data: {
                 '__RequestVerificationToken' : token,
                  'model_json': JSON.stringify(data)
        }; ,
        success: function() { refresh(); }
    });

Тогда в вашем действии MVC:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(FormCollection data)
    {
        var model= JsonConvert.DeserializeObject<Order>(data["model_json"]);
        return Json(1);
    }

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

...