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

63

Я использую следующий код этого сообщения:

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

Используя приведенный ниже код, я думаю, что это должно быть очень просто, просто добавив следующую строку в код 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(); }
        });
    }
Теги:
asp.net-mvc
antiforgerytoken

12 ответов

46

Неправильно то, что действие контроллера, которое должно обрабатывать этот запрос и которое помечено знаком [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, то проверка Anti-Forgery завершится неудачно. Из-за этого я буду смотреть на решение TWith2Sugars. Но вышесказанное действительно работает!
  • 2
    Работает ли сценарий № 2 в MVC 3. Я попытался безуспешно.
Показать ещё 7 комментариев
43

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

  • Поместите токен в заголовки.
  • Создайте фильтр.
  • Поместите атрибут в свой метод.

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

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);
}
  • 0
    Где вы разместили открытый класс ValidateJsonAntiForgeryTokenAttribute ?
  • 1
    Я создал папку прямо в корне проекта с именем Filters, где я создал класс с именем ValidateJsonAntiForgeryTokenAttribute.cs.
Показать ещё 3 комментария
11

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

Во-первых, я решил подключить мои вызовы 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.

  • 0
    Это работает с большинством типов постов ajax в MVC из моего тестирования, я опустил макет и оставил его работать без кода.
  • 2
    Мне кажется, ты упустил суть. Ваш код должен работать только с полезными данными запроса как тип содержимого application/x-www-form-urlencoded . ОП хотел отправить свой запрос данных полезной нагрузки как application/json . Добавление &__Request... к полезной нагрузке JSON должно завершиться неудачно. (Он просил не ответ JSON, как это делает ваш пример кода, а запрос JSON.)
6

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

Я пробовал это в настройках 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)
{
}
  • 0
    Этот метод работает при попытке отправить данные, которые содержат массивы типов значений C #, однако я не могу заставить его работать при использовании пользовательских классов. Вам нужно сделать что-то по-другому, чтобы заставить это работать?
5

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

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

  • 3
    Это не всегда правильно. Возможно подделать JSON-пост, используя HTML-формы. Если вы посмотрите на AjaxRequestExtensions.IsAjaxRequest, он проверяет в теле запроса «X-Requested-With», а не только заголовки. Таким образом, либо вы проверяете свою собственную проверку, чтобы убедиться, что данные публикуются с помощью AJAX, либо добавляете AntiForgeryToken.
  • 0
    Но тело запроса не будет JSON, оно будет закодировано в форме URL.
Показать ещё 4 комментария
5

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

4

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

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

2

Посмотрите Dixin Blog за отличную запись о том, как это сделать.

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

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

        data = $.appendAntiForgeryToken(data,null);

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

Я разрешил его глобально с помощью 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);
    }
}

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

1

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

//If it 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 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 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);
    }
});

`` `

  • 0
    Пожалуйста, дайте мне знать, если есть ошибка - я просто набрал ее вручную, а не копировал и вставлял беспорядочную кучу попыток, которая является моим настоящим кодом: \
0

Вы должны поместить 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]

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню