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

162

Я применил в своем приложении смягчение атак 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) {
            // ....
        }
    });
});
  • 0
    Ссылка Дэвида Хейдена теперь 404-ая, похоже, он перенес свой блог на новую CMS, но не перенес весь старый контент.
Теги:
csrf
asp.net-mvc
antiforgerytoken
asp.net-mvc-2

16 ответов

225
Лучший ответ

Я использую простую функцию 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) {
        // ....
    }
});
  • 6
    Хорошо, мне нравится инкапсуляция извлечения токена.
  • 0
    Да, это хорошо, потому что вы часто делаете посты в ajax, у вас даже нет формы на странице, чтобы получить токен от
Показать ещё 18 комментариев
22

Мне нравится решение, предоставляемое 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);
            }
        }
    });
});
  • 1
    +1 вы правы, я не задумывался о проблеме пустых звонков. спасибо за вклад. вы были правы насчет того, что мы еще не используем delete / put в нашем проекте.
  • 2
    +1 за спасение от необходимости добавлять функцию ко всем вызовам jQuery.Ajax
Показать ещё 5 комментариев
17

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

  • 0
    Для методов действия контроллера, которые связывают тип модели сервера с опубликованным AJAX JSON, для использования правильного связывателя модели требуется тип содержимого «application / json». К сожалению, это исключает использование данных формы, требуемых атрибутом [ValidateAntiForgeryToken], поэтому ваш метод - единственный способ найти его, чтобы он работал. Единственный вопрос: работает ли он в веб-ферме или в нескольких экземплярах веб-роли Azure? Вы @ Эдвард или кто-то еще знает, если это проблема?
  • 0
    @ Эдвард Брей Не могли бы вы уточнить, почему мы не должны его использовать?
Показать ещё 3 комментария
15

Я знаю, что есть много других ответов, но эта статья хороша и лаконична и заставляет вас проверять все ваши 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 () {
        ...
    }
});
  • 3
    Атрибут из статьи, которую вы тоже связали в сочетании с ответом Бронкса, является окончательным СУХИМЫМ решением этой проблемы.
  • 2
    Отличная находка. Я отредактировал ваш ответ, включив в него фрагменты кода, так что ответ стоит сам по себе, но я надеюсь, что люди прочтут и остальную часть статьи. Похоже, это очень чистое решение.
Показать ещё 1 комментарий
14

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

  • 0
    хорошее решение Спасибо.
12

Я чувствую себя продвинутым некромантом здесь, но это все еще проблема 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);
}
  • 0
    Идеальное решение, гораздо более централизованное. Спасибо
  • 0
    Можете ли вы объяснить более подробно, почему вы хотите добавить заголовок только для относительных URL? Это прошло над моей головой. Отличное решение!
Показать ещё 3 комментария
9

Я думаю, что все, что вам нужно сделать, это убедиться, что вход "__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) {
            // ....
        }
    });
});
  • 1
    После многих часов экспериментирования с публикацией jQuery AJAX со страницы MVC (Razor) это был самый простой ответ из всех, которые у меня работали. Просто включите ваши собственные поля данных (или, я полагаю, viewModel) после токена как новый фрагмент данных (но внутри исходного объекта данных).
  • 0
    Как бы я это реализовал, если бы функция AJAX была на странице .html, а не на странице Razor?
Показать ещё 1 комментарий
4

В дополнение к моему комментарию к ответу @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 С#.

4

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

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

3

Функция 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);
2

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

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

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

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

  • 0
    Этот пост длиной в милю! Я уверен, что это здорово, но д-р
  • 0
    Слишком плохо, потому что это хорошо покрывает предмет. Он не только говорит вам, как использовать функцию, но и объясняет, какую проблему она исправляет, и дает вам контекст, чтобы понять, как правильно ее использовать. Что касается безопасности, я думаю, что глубокое понимание важно.
Показать ещё 1 комментарий
0

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

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

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

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

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

Небольшое улучшение для решения 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);
            }
        }
    });
});
-1

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

Ещё вопросы

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