JQuery Ajax вызывает и Html.AntiForgeryToken()

Я применил в своем приложении смягчение атак CSRF в соответствии с информацией, которую я прочитал в блоге в Интернете. В частности, эти сообщения были драйвером моей реализации

В основном эти статьи и рекомендации говорят, что для предотвращения атаки CSRF кто-то должен реализовать следующий код:

1) Добавьте [ValidateAntiForgeryToken] для каждого действия, принимающего HTTP-адрес POST

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SomeAction( SomeModel model ) {
}

2) Добавьте вспомогательный элемент <%= Html.AntiForgeryToken() %> внутри форм, который отправляет данные на сервер

<div style="text-align:right; padding: 8px;">
    <%= Html.AntiForgeryToken() %>
    <input type="submit" id="btnSave" value="Save" />
</div>

В любом случае в некоторых частях моего приложения я делаю AJAX POST с jQuery на сервере без какой-либо формы. Это происходит, например, когда я позволяю пользователю щелкнуть изображение, чтобы выполнить конкретное действие.

Предположим, что у меня есть таблица со списком действий. У меня есть изображение в столбце таблицы, в котором говорится: "Отметить активность как завершенную", и когда пользователь нажимает на эту активность, я делаю Ajax POST, как в следующем примере:

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {},
        success: function (response) {
            // ....
        }
    });
});

Как я могу использовать <%= Html.AntiForgeryToken() %> в этих случаях? Должен ли я включать вспомогательный вызов внутри параметра данных вызова Ajax?

Извините за длинный пост и очень спасибо за помощь

ИЗМЕНИТЬ

По jayrdub ответ, который я использовал следующим образом

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {
            AddAntiForgeryToken({}),
            id: parseInt($(this).attr("title"))
        },
        success: function (response) {
            // ....
        }
    });
});

Ответ 1

Я использую простую функцию js, подобную этой

AddAntiForgeryToken = function(data) {
    data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
    return data;
};

Так как каждая форма на странице будет иметь одинаковое значение для токена, просто поместите что-то вроде этого на самую верхнюю главную страницу

<%-- used for ajax in AddAntiForgeryToken() --%>
<form id="__AjaxAntiForgeryForm" action="#" method="post"><%= Html.AntiForgeryToken()%></form>  

Затем в вашем вызове ajax do (отредактировано в соответствии с вашим вторым примером)

$.ajax({
    type: "post",
    dataType: "html",
    url: $(this).attr("rel"),
    data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
    success: function (response) {
        // ....
    }
});

Ответ 2

Мне нравится решение, предоставляемое 360Airwalk, но оно может быть немного улучшено.

Первая проблема заключается в том, что если вы создаете $.post() с пустыми данными, jQuery не добавляет заголовок Content-Type, и в этом случае ASP.NET MVC не сможет получить и проверить токен. Поэтому вы должны гарантировать, что заголовок всегда есть.

Еще одно усовершенствование - поддержка всех HTTP-глаголов с содержимым: POST, PUT, DELETE и т.д. Хотя вы можете использовать только POST в своем приложении, лучше иметь общее решение и убедиться, что все данные, которые вы получаете с помощью любого глагола, токен анти-подделки.

$(document).ready(function () {
    var securityToken = $('[name=__RequestVerificationToken]').val();
    $(document).ajaxSend(function (event, request, opt) {
        if (opt.hasContent && securityToken) {   // handle all verbs with content
            var tokenParam = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            opt.data = opt.data ? [opt.data, tokenParam].join("&") : tokenParam;
            // ensure Content-Type header is present!
            if (opt.contentType !== false || event.contentType) {
                request.setRequestHeader( "Content-Type", opt.contentType);
            }
        }
    });
});

Ответ 3

Я знаю, что есть много других ответов, но эта статья хороша и лаконична и заставляет вас проверять все ваши HttpPosts, а не только некоторые из них:

http://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests/

Он использует заголовки HTTP вместо того, чтобы пытаться изменить коллекцию форм.

Сервер

//make sure to add this to your global action filters
[AttributeUsage(AttributeTargets.Class)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public override void OnAuthorization( AuthorizationContext filterContext )
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {
            //  Ajax POSTs and normal form posts have to be treated differently when it comes
            //  to validating the AntiForgeryToken
            if (request.IsAjaxRequest())
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value 
                    : null;

                AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}

Client

var token = $('[name=__RequestVerificationToken]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;

$.ajax({
    type: 'POST',
    url: '/Home/Ajax',
    cache: false,
    headers: headers,
    contentType: 'application/json; charset=utf-8',
    data: { title: "This is my title", contents: "These are my contents" },
    success: function () {
        ...
    },
    error: function () {
        ...
    }
});

Ответ 5

Я чувствую себя продвинутым некромантом здесь, но это все еще проблема 4 года спустя в MVC5.

Чтобы правильно обрабатывать запросы ajax, токен анти-подделки должен быть передан серверу на вызовы ajax. Интеграция его в ваши данные и модели сообщений беспорядочна и не нужна. Добавление маркера в качестве настраиваемого заголовка является чистым и многоразовым - и вы можете настроить его, чтобы вам не приходилось делать это каждый раз.

Есть исключение. Ненавязчивый аякс не нуждается в специальной обработке для вызовов ajax. В обычном скрытом поле ввода токен передается как обычно. Точно так же, как обычный POST.

_Layout.cshtml

В _layout.cshtml У меня есть этот блок JavaScript. Он не записывает токен в DOM, а использует jQuery, чтобы извлечь его из скрытого входного литерала, созданного MVC Helper. Строка Magic, которая является именем заголовка, определяется как константа в классе атрибутов.

<script type="text/javascript">
    $(document).ready(function () {
        var isAbsoluteURI = new RegExp('^(?:[a-z]+:)?//', 'i');
        //http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative

        $.ajaxSetup({
            beforeSend: function (xhr) {
                if (!isAbsoluteURI.test(this.url)) {
                    //only add header to relative URLs
                    xhr.setRequestHeader(
                       '@.ValidateAntiForgeryTokenOnAllPosts.HTTP_HEADER_NAME', 
                       $('@Html.AntiForgeryToken()').val()
                    );
                }
            }
        });
    });
</script>

Обратите внимание на использование одинарных кавычек в функции beforeSend - элемент ввода, который визуализирован, использует двойные кавычки, которые бы разрушили литерал JavaScript.

Клиентский JavaScript

Когда это выполняется, вызывается функция beforeSend выше, и AntiForgeryToken автоматически добавляется в заголовки запроса.

$.ajax({
  type: "POST",
  url: "CSRFProtectedMethod",
  dataType: "json",
  contentType: "application/json; charset=utf-8",
  success: function (data) {
    //victory
  }
});

Серверная библиотека

Пользовательский атрибут требуется для обработки нестандартного токена. Это основано на решении @viggity, но правильно обрабатывает ненавязчивый аякс. Этот код можно убрать в общей библиотеке

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public const string HTTP_HEADER_NAME = "x-RequestVerificationToken";

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {

            var headerTokenValue = request.Headers[HTTP_HEADER_NAME];

            // Ajax POSTs using jquery have a header set that defines the token.
            // However using unobtrusive ajax the token is still submitted normally in the form.
            // if the header is present then use it, else fall back to processing the form like normal
            if (headerTokenValue != null)
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value
                    : null;

                AntiForgery.Validate(cookieValue, headerTokenValue);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}

Сервер/контроллер

Теперь вы примените этот атрибут к своему действию. Еще лучше вы можете применить этот атрибут к своему контроллеру, и все запросы будут проверены.

[HttpPost]
[ValidateAntiForgeryTokenOnAllPosts]
public virtual ActionResult CSRFProtectedMethod()
{
  return Json(true, JsonRequestBehavior.DenyGet);
}

Ответ 6

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

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

$(document).ready(function () {
    var 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-Helper. Вы можете в принципе добавить этот код везде, где хотите. Я поместил его в оператор 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

Я думаю, что все, что вам нужно сделать, это убедиться, что вход "__RequestVerificationToken" включен в запрос POST. Другая половина информации (т.е. Токен в cookie пользователя) уже отправляется автоматически с запросом POST AJAX.

например.

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: { 
            "__RequestVerificationToken":
            $("input[name=__RequestVerificationToken]").val() 
        },
        success: function (response) {
            // ....
        }
    });
});

Ответ 8

Вы также можете сделать это:

$("a.markAsDone").click(function (event) {
    event.preventDefault();

    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: $('<form>@Html.AntiForgeryToken()</form>').serialize(),
        success: function (response) {
        // ....
        }
    });
});

Это использует Razor, но если вы используете синтаксис WebForms, вы можете просто использовать теги <%= %>

Ответ 9

В дополнение к моему комментарию к ответу @JBall, который помог мне на этом пути, это окончательный ответ, который работает на меня. Я использую MVC и Razor, и я представляю форму с использованием jQuery AJAX, поэтому я могу обновить частичный вид с некоторыми новыми результатами, и я не хотел делать полную обратную передачу (и мерцание страницы).

Добавьте @Html.AntiForgeryToken() внутри формы, как обычно.

Код кнопки отправки моего AJAX (то есть событие onclick):

//User clicks the SUBMIT button
$("#btnSubmit").click(function (event) {

//prevent this button submitting the form as we will do that via AJAX
event.preventDefault();

//Validate the form first
if (!$('#searchForm').validate().form()) {
    alert("Please correct the errors");
    return false;
}

//Get the entire form data - including the antiforgerytoken
var allFormData = $("#searchForm").serialize();

// The actual POST can now take place with a validated form
$.ajax({
    type: "POST",
    async: false,
    url: "/Home/SearchAjax",
    data: allFormData,
    dataType: "html",
    success: function (data) {
        $('#gridView').html(data);
        $('#TestGrid').jqGrid('setGridParam', { url: '@Url.Action("GetDetails", "Home", Model)', datatype: "json", page: 1 }).trigger('reloadGrid');
    }
});

Я оставил действие "успех", так как он показывает, как обновляется частичный вид, содержащий MvcJqGrid и как он обновляется (очень мощная сетка jqGrid, и это блестящая оболочка MVC для нее).

Мой метод контроллера выглядит следующим образом:

    //Ajax SUBMIT method
    [ValidateAntiForgeryToken]
    public ActionResult SearchAjax(EstateOutlet_D model) 
    {
        return View("_Grid", model);
    }

Я должен признать, что не являюсь поклонником POSTing всей формы данных как модели, но если вам нужно это сделать, то это один из способов, который работает. MVC просто делает привязку данных слишком простой, поэтому, не предполагая 16 отдельных значений (или слабо типизированного FormCollection), это нормально. Если вы знаете лучше, сообщите мне, поскольку я хочу создать надежный код MVC С#.

Ответ 10

нашел эту очень умную идею из https://gist.github.com/scottrippey/3428114 для каждого вызова $.ajax, она изменяет запрос и добавляет токен.

// Setup CSRF safety for AJAX:
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    if (options.type.toUpperCase() === "POST") {
        // We need to add the verificationToken to all POSTs
        var token = $("input[name^=__RequestVerificationToken]").first();
        if (!token.length) return;

        var tokenName = token.attr("name");

        // If the data is JSON, then we need to put the token in the QueryString:
        if (options.contentType.indexOf('application/json') === 0) {
            // Add the token to the URL, because we can't add it to the JSON data:
            options.url += ((options.url.indexOf("?") === -1) ? "?" : "&") + token.serialize();
        } else if (typeof options.data === 'string' && options.data.indexOf(tokenName) === -1) {
            // Append to the data string:
            options.data += (options.data ? "&" : "") + token.serialize();
        }
    }
});

Ответ 11

Функция 1.Define для получения токена с сервера

@function
{

        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;                
        }
}

2. Установите токен и установите заголовок перед отправкой на сервер

var token = '@TokenHeaderValue()';    

       $http({
           method: "POST",
           url: './MainBackend/MessageDelete',
           data: dataSend,
           headers: {
               'RequestVerificationToken': token
           }
       }).success(function (data) {
           alert(data)
       });

3. Проверка Onserver на HttpRequestBase по методу, с которым вы обрабатываете сообщение /get

        string cookieToken = "";
        string formToken = "";
        string[] tokens = Request.Headers["RequestVerificationToken"].Split(':');
            if (tokens.Length == 2)
            {
                cookieToken = tokens[0].Trim();
                formToken = tokens[1].Trim();
            }
        AntiForgery.Validate(cookieToken, formToken);

Ответ 12

Я знаю, что прошло некоторое время с момента публикации этого вопроса, но я нашел действительно полезный ресурс, в котором обсуждается использование AntiForgeryToken и делает его менее трудным для использования. Он также предоставляет плагин jquery для простого включения маркера antiforgery в вызовы AJAX:

Рецепты запроса на антивирусные запросы для ASP.NET MVC и AJAX

Я не очень люблю, но, возможно, кто-то найдет его полезным.

Ответ 13

Вот самый простой способ, который я видел. Примечание. Убедитесь, что в вашем представлении есть "@Html.AntiForgeryToken()".

  $("a.markAsDone").click(function (event) {
        event.preventDefault();
        var sToken = document.getElementsByName("__RequestVerificationToken")[0].value;
        $.ajax({
            url: $(this).attr("rel"),
            type: "POST",
            contentType: "application/x-www-form-urlencoded",
            data: { '__RequestVerificationToken': sToken, 'id': parseInt($(this).attr("title")) }
        })
        .done(function (data) {
            //Process MVC Data here
        })
        .fail(function (jqXHR, textStatus, errorThrown) {
            //Process Failure here
        });
    });

Ответ 14

Небольшое улучшение для решения 360Airwalk. Это встраивает токен Anti Forgery в функцию javascript, поэтому @Html.AntiForgeryToken() больше не нужно включать в каждое представление.

$(document).ready(function () {
    var securityToken = $('@Html.AntiForgeryToken()').attr('value');
    $('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);
            }
        }
    });
});

Ответ 15

function DeletePersonel(id) {

    var data = new FormData();
    data.append("__RequestVerificationToken", "@HtmlHelper.GetAntiForgeryToken()");

    $.ajax({
        type: 'POST',
        url: '/Personel/Delete/' + id,
        data: data,
        cache: false,
        processData: false,
        contentType: false,
        success: function (result) {
        }
    });
}

public static class HtmlHelper {
    public static string GetAntiForgeryToken() {
        System.Text.RegularExpressions.Match value = 
                System.Text.RegularExpressions.Regex.Match(System.Web.Helpers.AntiForgery.GetHtml().ToString(), 
                        "(?:value=\")(.*)(?:\")");
        if (value.Success) {
            return value.Groups[1].Value;
        }
        return "";
    }
}

Ответ 16

Я использую пост ajax для запуска метода удаления (бывает, это временная шкала visjs, но это не соответствует). Вот что я понимаю:

Это мой index.cshtml

@Scripts.Render("~/bundles/schedule")
@Styles.Render("~/bundles/visjs")
@Html.AntiForgeryToken()

<!-- div to attach schedule to -->
<div id='schedule'></div>

<!-- div to attach popups to -->
<div id='dialog-popup'></div>

Все, что я добавил здесь, было @Html.AntiForgeryToken(), чтобы сделать маркер на странице

Затем в моем сообщении ajax я использовал:

$.ajax(
    {
        type: 'POST',
        url: '/ScheduleWorks/Delete/' + item.id,
        data: {
            '__RequestVerificationToken': 
            $("input[name='__RequestVerificationToken']").val()
              }
     }
);

Что добавляет значение токена, соскоблило страницу, в поля, помеченные

До этого я попытался поместить значение в заголовки, но я получил ту же ошибку

Не стесняйтесь публиковать улучшения. Это, безусловно, кажется простым подходом, который я могу понять.

Ответ 17

сначала используйте @Html.AntiForgeryToken() в html

 $.ajax({
        url: "@Url.Action("SomeMethod", "SomeController")",
        type: 'POST',
        data: JSON.stringify(jsonObject),
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        async: false,
        beforeSend: function (request) {
            request.setRequestHeader("RequestVerificationToken", $("[name='__RequestVerificationToken']").val());
        },
        success: function (msg) {
            alert(msg);
        }

Ответ 18

Ладно, здесь много постов, ни одно из них не помогло мне, дни и дни Google, и все равно я больше не дошел до того, что написал все приложение с нуля, а потом заметил этот маленький самородок в моем Web.confg.

 <httpCookies requireSSL="false" domain="*.localLookup.net"/>

Теперь я не знаю, почему я добавил его, однако с тех пор заметил, что он игнорируется в режиме отладки, а не в рабочем режиме (IE установлен где-то в IIS)

Для меня решение было одним из 2 вариантов, так как я не помню, почему я добавил его, я не могу быть уверен, что другие вещи не зависят от него, и во-вторых, доменное имя должно быть все в нижнем регистре и TLD не так, как я сделал в *.localLookup.net

Может быть, это помогает, может быть, нет. Я надеюсь, что это поможет кому-то

Ответ 19

AntiforgeryToken все еще боль, ни один из приведенных выше примеров не работал дословно для меня. Слишком много там. Поэтому я объединил их всех. Нужна @Html.AntiforgeryToken в форме, висящей вокруг iirc

Решено как таковое:

function Forgizzle(eggs) {
    eggs.__RequestVerificationToken =  $($("input[name=__RequestVerificationToken]")[0]).val();
    return eggs;
}

$.ajax({
            url: url,
            type: 'post',
            data: Forgizzle({ id: id, sweets: milkway }),
});

В случае сомнений добавьте еще $sign