Як я можу надати AntiForgeryToken під час розміщення даних JSON за допомогою $ .ajax?


77

Я використовую код, як показано нижче в цій публікації:

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

Використовуючи код нижче, я вважаю, що це має бути дуже просто, просто додавши наступний рядок до коду JavaScript:

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

Це <%= Html.AntiForgeryToken() %>знаходиться в потрібному місці, і дія має a[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(); }
        });
    }

Відповіді:


67

Вам не потрібно рішення 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);
}

Куди ви поклали громадський клас ValidateJsonAntiForgeryTokenAttribute ?
Турп

1
Я створив папку безпосередньо в корені проекту з іменем Filters, де створив клас з іменем ValidateJsonAntiForgeryTokenAttribute.cs.
Ken Q

Це все одно не працювало для мене. Я створив новий файл .CS у папці в моєму корені мого проекту, маю [ValidateJsonAntiForgeryToken]на моєму ActionResult, а потім JS точно так, як у вас. Інструменти розробника Chrome "Мережа> Ім'я сторінки> Заголовки" відображається: __RequestVerificationToken:egrd5Iun...8AH6_t8w2під Request Headers. Що ще може бути не так !?
Турп

1
У мене це працює зараз; це, швидше за все, проблема кешування з мого кінця! Дякую! Ця відповідь була чудовою!
Турп

2
Елегантне рішення, дуже приємне використання атрибутів і призводить до більш чистого коду.
Аерокней

49

Помилковим є те, що дія контролера, яка повинна обробляти цей запит і яка позначена позначкою, [ValidateAntiForgeryToken]очікує, що параметр, що викликається, __RequestVerificationTokenбуде розміщений разом із запитом.

Немає такого параметра 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(); }
});

Тож у всіх випадках потрібно опублікувати __RequestVerificationTokenзначення.


4
Мені подобається такий підхід, і він працює ... до тих пір, поки ви не очікуєте, що обтяжений об'єкт json буде гідратований за допомогою класу JsonValueProviderFactory класу MVC 2 Futures / MVC 3, і ви обробляєте гідратацію вручну, щоб знати, що ігнорувати __RequestVerificationToken. Якщо я не скажу contentType очікувати json за $ .ajax, то маркер перевірки обробляється, але об'єкт json не гідратований. Якщо я СКАЖУ встановити json contentType, перевірка анти-підробки не вдається. Через це я розгляну рішення TWith2Sugars. Але вищесказане дійсно працює!
kdawg

якщо у вас є параметри, передані у функцію, що містить виклик ajax, ви можете використовувати $ .extend, щоб додати маркер "ненав’язливо". `дані var = $ .extend (параметри, {__RequestVerificationToken: маркер, jsonRequest: параметри}); `: stackoverflow.com/questions/617036/appending-to-a-json-object
Грег Огле

@kdawg як ти точно змусив свій код працювати? Або як ви визначаєте dataзмінну?
hardywang

@GregOgle Це лише у простішому випадку, коли ви не публікуєте JSON, який уже добре описаний у цій відповіді простим призначенням. Немає потреби в галочках процесора $ .extend ().
Кріс Москіні,

Я використовував «номер 2» , запропонований @Darin Димитров, і для того , щоб працювати , я повинен був видалити такі параметри , які я мав за $ .ajax: dataType: 'JSON'а також contentType: 'application/json; charset=utf-8', точно як Дарін Димитров має його на своїй посаді.
Саша

10

Я просто реалізовував цю актуальну проблему у своєму поточному проекті. Я зробив це для всіх POST-адрес 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

У вашому контролері просто використовуйте стандартний механізм анти-CSRF ASP.NET MVC. Я зробив це так (хоча насправді використовував сіль).

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

За допомогою Firebug або подібного інструменту ви можете легко побачити, як до ваших запитів POST тепер додається параметр __RequestVerificationToken.


Це працює з більшістю типів повідомлень ajax у MVC з мого тестування, я зайшов у макет і залишаю його працювати без будь-якого коду.

3
Мені здається, ви пропустили суть. Ваш код повинен працювати лише з корисним навантаженням даних запиту як application/x-www-form-urlencodedтипом вмісту. ОП хотів надіслати корисний набір даних його запиту як application/json. Додавання &__Request...до корисного навантаження JSON не повинно відбутися. (Він просив не відповіді JSON, а саме зразка коду, а запиту JSON.)
Фредерік,

7

Ви можете встановити $ .ajax «s 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)
{
}

Цей метод працює при спробі надіслати дані, що включають масиви типів значень C #, однак я не можу змусити його працювати при використанні визначених користувачем класів. Вам потрібно щось робити інакше, щоб це працювало?
Ліам Фланаган,

6

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

Це завжди буде порожнім, а перевірка не вдасться.



5

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

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


3
Це не завжди правильно. Можна сфальсифікувати допис JSON, використовуючи HTML-форми. Якщо ви подивитесь на AjaxRequestExtensions.IsAjaxRequest, він перевіряє в тілі запиту "X-Request-With", а не лише заголовки. Таким чином, ви або прокручуєте власну перевірку, щоб переконатися, що дані розміщуються за допомогою AJAX, або додаєте AntiForgeryToken.
Jeow Li Huan

Але тіло запиту не буде JSON, воно буде закодовано за допомогою форми.
Антуан Леклер

3
хто сказав, що ви не можете передавати дані іншому хосту? stackoverflow.com/questions/298745 / ...
Adam Tuliper - MSFT

1
Погодився, але моя заява була відповіддю на "ви не можете розміщувати дані Ajax на іншому хості". Ви можете розміщувати дані. Якщо ви мали на увазі щось інше, можливо, редакція вже під рукою, оскільки вона звучить так, ніби ви цього не можете зробити.
Адам Туліпер - MSFT

2
Це було б правдою, якби ви могли вимагати, щоб дія була доступна лише для запитів AJAX, але ви не можете. Як зазначено. Єдине, що ви змогли б зробити, це заблокувати Action, щоб прийняти лише application / json як тіло запиту. Але я насправді не знайомий з тим, як ви обмежуєте Action у MVC лише певним типом вмісту, вам доведеться виконати багато власних робіт, про які я здогадуюсь. Наскільки мені відомо, це не нестандартна функція.
Nicholi

3

Я вирішив це глобально за допомогою 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. Я написав і додав цей глобальний фільтр, який копіює маркер із заголовка до 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

Перегляньте блог Dixin, щоб отримати чудову публікацію про те, як саме це зробити.

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

Разом із плагіном jQuery на цій сторінці ви можете зробити щось таке просте, як:

        data = $.appendAntiForgeryToken(data,null);

        $.post(url, data, function() { refresh(); }, "json");

2

Публікація моделі на основі 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);
}

Сподіваюся, це допомагає :)


1

Мені довелося бути трохи тіньовим, щоб перевірити токени підробки під час розміщення 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

На жаль для мене, інші відповіді покладаються на певне форматування запитів, яке обробляє 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);
    }
});

``


Будь ласка, дайте мені знати, якщо є помилка - я просто набрав її вручну, а не копіював і
вставляв переплутаний нагромадження

-1

Ви повинні розмістити 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]

Сподіваюся, це допоможе.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.