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

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

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

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

[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) {
            // ....
        }
    });
});

21 ответ

Решение

Я использую простую функцию 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) {
        // ....
    }
});

Мне нравится решение, предоставляемое 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);
            }
        }
    });
});

Я знаю, что есть много других ответов, но эта статья хороша и лаконична и заставляет вас проверять все ваши 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);
            }
        }
    }
}

клиент

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 () {
        ...
    }
});

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

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

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

_Layout.cshtml

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

<script type="text/javascript">
    $(document).ready(function () {
        var isAbsoluteURI = new RegExp('^(?:[a-z]+:)?//', 'i');
        //http://stackru.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, но обрабатывает ненавязчивый Ajax правильно. Этот код можно спрятать в вашей общей библиотеке

[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);
}

Не используйте Html.AntiForgeryToken. Вместо этого используйте AntiForgery.GetTokens и AntiForgery.Validate из Web API, как описано в разделе Предотвращение атак подделки межсайтовых запросов (CSRF).

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

Например,

$("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) {
            // ....
        }
    });
});

Я только что реализовал эту актуальную проблему в моем текущем проекте. Я сделал это для всех 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-помощник. Вы можете добавить этот код везде, где хотите. Я поместил его в оператор 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.

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

$("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 Синтаксис вы можете также использовать <%= %> теги

Нашел эту очень умную идею из 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();
        }
    }
});

В дополнение к моему комментарию против ответа @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's 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');
    }
});

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

Мой метод контроллера выглядит так:

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

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

сначала используйте @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);
        }

1. Определить функцию для получения токена с сервера

@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. Проверка сервера на HttpRequestBase для метода, который вы обрабатываете Post/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);

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

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

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

Вот самый простой способ, который я видел. Примечание. Убедитесь, что у вас в представлении "@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
        });
    });

Большинство ответов выше относятся к приложениям MVC. Да, вам нужно добавить @Html.AntiForgeryToken() в ваш файл cshtml, а также получить токен безопасности в файле js и использовать его в объекте данных сообщения ajax.

Однако со стороны C# напоминаем: если вы используете страницы Razor, обратите внимание, что вы не можете использовать атрибут [ValidateAntiForgeryToken] поверх методов. Это не выдаст вам ошибку, но не будет иметь никакого эффекта, поскольку работает только с приложениями MVC. Для страниц Razor вам необходимо использовать атрибут поверх класса «модель».

Например;

      [ValidateAntiForgeryToken]
public class IndexModel : PageModel
{
     // rest of the class
}

Это применит атрибут ко всем методам на странице.

Решение, которое я нашел, не для ASPX, а для Razor, но вполне совместимая проблема.

Я решил это, добавив в запрос AntiForgery. Помощник HTML не создает идентификатор HTML при вызове

@Html.AntiForgeryToken()

Чтобы добавить токен в постзапрос, я просто добавил идентификатор AntiForgery в скрытое поле с помощью jquery:

$("input[name*='__RequestVerificationToken']").attr('id', '__AjaxAntiForgeryForm');

Это заставило контроллер принять запрос с атрибутом [ValidateAntiForgeryToken].

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

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

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

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

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

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 "";
    }
}

Я использую сообщение 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()
              }
     }
);

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

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

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

Незначительное улучшение решения 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);
            }
        }
    });
});

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 }),
});

Если сомневаетесь, добавьте больше знаков $

Другие вопросы по тегам