помощью jQuery AJAX-вызовы и HTML.AntiForgeryToken()
я реализовал в своем приложении смягчение до CSRF атакует после информации, которую я прочитал на некоторых блогах в интернете. В частности, эти сообщения были драйвером моей реализации
лучшие практики для ASP.NET MVC от ASP.NET и Web Tools Developer Content Team
Анатомия атаки подделки межсайтового запроса от Фила Хаака блог
AntiForgeryToken в ASP.NET MVC Framework-Html.Атрибут AntiForgeryToken и ValidateAntiForgeryToken из блога Дэвида Хейдена
в основном в этих статьях и рекомендациях говорится, что для предотвращения атаки CSRF кто-либо должен реализовать следующий код:
1) Добавить [ValidateAntiForgeryToken] на каждое действие, которое принимает сообщение 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 с jQuery на сервер, не имея никакой формы вообще. Это происходит, например, когда я позволяю пользователю нажать на изображение, чтобы сделать определенное действие.
Предположим у меня есть таблица с перечнем мероприятий. У меня есть изображение в столбце таблицы, в котором говорится "отметить действие как завершенное", и когда пользователь нажимает на это действие, я делаю сообщение Ajax, как показано ниже пример:
$("a.markAsDone").click(function (event) {
event.preventDefault();
$.ajax({
type: "post",
dataType: "html",
url: $(this).attr("rel"),
data: {},
success: function (response) {
// ....
}
});
});
как я могу использовать <%= Html.AntiForgeryToken() %> в этих случаях? Должен ли я включать вспомогательный вызов внутри параметра data вызова Ajax?
извините за длинный пост и спасибо за помощь
EDIT:
согласно ответу 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) {
// ....
}
});
});
18 ответов:
Я использую простую функцию 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 и т. д. Хотя вы можете использовать только сообщения в своем приложении, это лучше иметь универсальное решение и проверить, что все данные, которые вы получаете с любым глаголом, имеют маркер защиты от подделки.
$(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); } } }); });
Не используйте Html.AntiForgeryToken. Вместо этого используйте AntiForgery.GetTokens и AntiForgery.Проверить С Web API как описано в предотвращение атак на подделку межсайтовых запросов (CSRF).
Я знаю, что есть много других ответов, но эта статья приятна и лаконична и заставляет вас проверять все ваши 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 () { ... } });
Я просто реализовывал эту актуальную проблему в моем текущем проекте. я сделал это для всех ajax-сообщений, которые нуждались в аутентифицированном пользователе.
во-первых, я решил подключить свои вызовы jQuery ajax, чтобы я не повторялся слишком часто. этот фрагмент javascript гарантирует, что все вызовы ajax (post) добавят в запрос маркер проверки моего запроса. Примечание: имя _ _ RequestVerificationToken используется .NET framework, поэтому я могу использовать стандартные функции анти-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 (запрос.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.
Я чувствую себя продвинутым некромантом здесь, но это все еще проблема 4 лет спустя в MVC5.
для правильной обработки запросов ajax токен защиты от подделки должен быть передан на сервер при вызовах ajax. Интеграция его в ваши почтовые данные и модели является грязной и ненужной. Добавление токена в качестве пользовательского заголовка является чистым и многоразовым - и вы можете настроить его так, чтобы вам не приходилось помнить об этом каждый раз.
есть исключение-ненавязчивый ajax не делает нужна специальная обработка для вызовов ajax. Маркер передается как обычно в обычном скрытом поле ввода. Точно так же, как обычный пост.
файл _Layout.cshtml
In _layout.cshtml у меня есть этот блок JavaScript. Он не записывает токен в DOM, а использует jQuery для извлечения его из скрытого входного литерала, который генерирует помощник MVC. Магическая строка, которая является именем заголовка, определяется как константа в атрибуте класс.
<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, но правильно обрабатывает ненавязчивый 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); }
Я думаю, что все, что вам нужно сделать, это убедиться, что вход "__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) { // .... } }); });
вы можете сделать это:
$("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синтаксис, вы можете просто использовать<%= %>теги
В дополнение к моему комментарию против ответа @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'); } });Я оставил действие "успех", поскольку оно показывает, как обновляется частичное представление, которое содержит MvcJqGrid и как оно обновляется (очень мощная сетка jqGrid, и это блестящая оболочка MVC для нее).
мой метод контроллера выглядит так:
//Ajax SUBMIT method [ValidateAntiForgeryToken] public ActionResult SearchAjax(EstateOutlet_D model) { return View("_Grid", model); }Я должен признать, что не поклонник размещения данных всей формы в качестве модели, но если вам нужно это сделать, то это один из способов, который работает. MVC просто делает привязку данных слишком легкой, поэтому вместо подстановки 16 отдельных значения (или слабо типизированная FormCollection) это нормально, я думаю. Если вы знаете лучше, пожалуйста, дайте мне знать, как я хочу, чтобы произвести надежный код MVC C#.
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. Проверка Onserver на 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);
нашел эту очень умную идею от 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(); } } });
Я знаю, что прошло некоторое время с тех пор, как этот вопрос был опубликован, но я нашел действительно полезный ресурс, который обсуждает использование AntiForgeryToken и делает его менее хлопотным в использовании. Он также предоставляет плагин jquery для легкого включения токена antiforgery в вызовы AJAX:
рецепты запроса анти -- подделки для ASP.NET MVC и AJAX
Я не вкладываю много, но, возможно, кто-то найдет это полезным.
небольшое улучшение к разрешению 360Airwalk. Это вставляет маркер защиты от подделки в функцию 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); } } }); });
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 post для запуска метода удаления (случается, что из временной шкалы visjs, но это не relelvant). Вот что я сестренка:
Это мой индекс.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() } } );который добавляет значение токена, соскобленное со страницы, в поля, опубликованные
перед этим я попытался поместить значение в заголовки, но я получил та же ошибка
не стесняйтесь публиковать улучшения. Это, конечно, кажется простой подход, который я могу понять
сначала используйте @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); }
вот самый простой способ я видел. Примечание: убедитесь, что у вас есть "@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 }); });
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 }), });Если вы сомневаетесь, добавьте больше знаков$
Comments