Я использую следующий код этого сообщения:
Сначала я заполню переменную массива с правильными значениями для действия контроллера.
Используя приведенный ниже код, я думаю, что это должно быть очень просто, просто добавив следующую строку в код 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(); }
});
}
Неправильно то, что действие контроллера, которое должно обрабатывать этот запрос и которое помечено знаком [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
.
Вам не нужно решение 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);
}
ValidateJsonAntiForgeryTokenAttribute
?
Я только что реализовал эту актуальную проблему в своем текущем проекте. Я сделал это для всех 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.
application/x-www-form-urlencoded
. ОП хотел отправить свой запрос данных полезной нагрузки как application/json
. Добавление &__Request...
к полезной нагрузке JSON должно завершиться неудачно. (Он просил не ответ JSON, как это делает ваш пример кода, а запрос JSON.)
Вы можете установить атрибут $. 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)
{
}
Вам никогда не придется проверять AntiForgeryToken, когда вы получаете сообщение JSON.
Причина в том, что AntiForgeryToken создан для предотвращения CSRF. Поскольку вы не можете отправлять данные AJAX другому хосту, а HTML-формы не могут отправлять JSON в качестве тела запроса, вам не нужно защищать ваше приложение от опубликованного JSON.
Я держу маркер в моем объекте JSON, и я закончил модификацию класса ValidateAntiForgeryToken, чтобы проверить InputStream Request объект, когда сообщение json. Я написал сообщение в блоге об этом, надеюсь, вам может показаться полезным.
Вы не можете проверить содержимое типа contentType: 'application/json; charset = utf-8 ', потому что ваша дата будет загружена не в свойстве формы запроса, а в свойстве InputStream, и у вас никогда не будет этого Request.Form [ "__ RequestVerificationToken" ].
Это будет всегда пусто, и проверка не завершится.
Посмотрите Dixin Blog за отличную запись о том, как это сделать.
Кроме того, почему бы не использовать $.post вместо $.ajax?
Наряду с плагином jQuery на этой странице вы можете сделать что-то простое:
data = $.appendAntiForgeryToken(data,null);
$.post(url, data, function() { refresh(); }, "json");
Я разрешил его глобально с помощью 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);
}
}
Эта работа для меня:)
Я должен был быть немного теневым, чтобы проверять токены анти-подделки при публикации 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), который я забыл в пользу краткости. Я могу добавить его при необходимости.
К сожалению, для меня другие ответы основываются на некотором форматировании запроса, обрабатываемом 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);
}
});
`` `
Вы должны поместить 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]
Надеюсь, это поможет.