включить антифоргегокен в пост ajax ASP.NET MVC

168

У меня проблемы с AntiForgeryToken с ajax. Я использую ASP.NET MVC 3. Я пробовал решение в вызовах jQuery Ajax и Html.AntiForgeryToken () . Используя это решение, токен теперь передается:

var data = { ... } // with token, key is '__RequestVerificationToken'

$.ajax({
        type: "POST",
        data: data,
        datatype: "json",
        traditional: true,
        contentType: "application/json; charset=utf-8",
        url: myURL,
        success: function (response) {
            ...
        },
        error: function (response) {
            ...
        }
    });

Когда я удаляю [ValidateAntiForgeryToken]атрибут, чтобы посмотреть, передаются ли данные (с токеном) в качестве параметров контроллеру, я вижу, что они передаются. Но по какой-то причине A required anti-forgery token was not supplied or was invalid.сообщение все равно всплывает, когда я возвращаю атрибут.

Любые идеи?

РЕДАКТИРОВАТЬ

Antiforgerytoken создается внутри формы, но я не использую действие отправки для его отправки. Вместо этого я просто получаю значение токена с помощью jquery, а затем пытаюсь опубликовать это в ajax.

Вот форма, которая содержит токен и находится на верхней главной странице:

<form id="__AjaxAntiForgeryForm" action="#" method="post">
    @Html.AntiForgeryToken()
</form>
OJ Ракеньо
источник

Ответы:

289

Вы неправильно указали contentTypeдля application/json.

Вот пример того, как это может работать.

контроллер:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Index(string someValue)
    {
        return Json(new { someValue = someValue });
    }
}

Посмотреть:

@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
    @Html.AntiForgeryToken()
}

<div id="myDiv" data-url="@Url.Action("Index", "Home")">
    Click me to send an AJAX request to a controller action
    decorated with the [ValidateAntiForgeryToken] attribute
</div>

<script type="text/javascript">
    $('#myDiv').submit(function () {
        var form = $('#__AjaxAntiForgeryForm');
        var token = $('input[name="__RequestVerificationToken"]', form).val();
        $.ajax({
            url: $(this).data('url'),
            type: 'POST',
            data: { 
                __RequestVerificationToken: token, 
                someValue: 'some value' 
            },
            success: function (result) {
                alert(result.someValue);
            }
        });
        return false;
    });
</script>
Дарин димитров
источник
Привет, спасибо за быстрый ответ. Извините, я не упомянул об этом в вопросе; В данный момент я не использую действие отправки. (Токен находится в форме, но я не использую кнопку отправки, чтобы отправить его). Можно ли просто изменить тип контента на что-то другое?
OJ Ракеньо
13
Тот факт, что вы не используете действие отправки, не сильно меняет мой ответ. Все, что вам нужно сделать, это подписаться на какое-то другое событие (нажатие кнопки, щелчок привязки или что-то еще и просто прочитать значение скрытого поля). Что касается отправки запроса AJAX, вы можете использовать пример, приведенный в моем ответе. Вы не должны использовать , contentTypeчтобы , application/jsonпотому что сервер ожидает , что __RequestVerificationTokenпараметр , чтобы быть частью полезной нагрузки запроса POST с использованием application/x-www-form-urlencoded.
Дарин Димитров
как этот код $(this).data('url'),может понять, что будет URL моего контроллера и действия. пожалуйста, объясни. спасибо
Моу
2
Первоначальный вопрос был о contentType: 'application / json'. Для обычных сообщений Ajax, включая __RequestVerificationToken в форме сообщения, очевидно, будет работать, потому что это похоже на сообщение обычной формы. Однако, когда вы хотите опубликовать json (отсюда тип контента), это не работает. Так что это случай неправильного принятия вышесказанного в качестве ответа.
Джон
Нужно ли использовать "ModelState.IsValid"? Как я могу сказать, что это работает?
Моран Монович
61

Другой (менее javascriptish) подход, который я использовал, выглядит примерно так:

Во-первых, Html помощник

public static MvcHtmlString AntiForgeryTokenForAjaxPost(this HtmlHelper helper)
{
    var antiForgeryInputTag = helper.AntiForgeryToken().ToString();
    // Above gets the following: <input name="__RequestVerificationToken" type="hidden" value="PnQE7R0MIBBAzC7SqtVvwrJpGbRvPgzWHo5dSyoSaZoabRjf9pCyzjujYBU_qKDJmwIOiPRDwBV1TNVdXFVgzAvN9_l2yt9-nf4Owif0qIDz7WRAmydVPIm6_pmJAI--wvvFQO7g0VvoFArFtAR2v6Ch1wmXCZ89v0-lNOGZLZc1" />
    var removedStart = antiForgeryInputTag.Replace(@"<input name=""__RequestVerificationToken"" type=""hidden"" value=""", "");
    var tokenValue = removedStart.Replace(@""" />", "");
    if (antiForgeryInputTag == removedStart || removedStart == tokenValue)
        throw new InvalidOperationException("Oops! The Html.AntiForgeryToken() method seems to return something I did not expect.");
    return new MvcHtmlString(string.Format(@"{0}:""{1}""", "__RequestVerificationToken", tokenValue));
}

это вернет строку

__RequestVerificationToken:"P5g2D8vRyE3aBn7qQKfVVVAsQc853s-naENvpUAPZLipuw0pa_ffBf9cINzFgIRPwsf7Ykjt46ttJy5ox5r3mzpqvmgNYdnKc1125jphQV0NnM5nGFtcXXqoY3RpusTH_WcHPzH4S4l1PmB8Uu7ubZBftqFdxCLC5n-xT0fHcAY1"

так что мы можем использовать это так

$(function () {
    $("#submit-list").click(function () {
        $.ajax({
            url: '@Url.Action("SortDataSourceLibraries")',
            data: { items: $(".sortable").sortable('toArray'), @Html.AntiForgeryTokenForAjaxPost() },
            type: 'post',
            traditional: true
        });
    });
});

И это похоже на работу!

Максимум
источник
5
+1, приятно. Я просто разделил их @Html.AntiForgeryTokenForAjaxPostна две части, чтобы получить имя токена в одной руке и его значение в другой. Иначе подсветка синтаксиса все испортила. Это заканчивается так (убрал одинарные кавычки из возвращаемого результата, так что он ведет себя как любой помощник MVC, например, @Url):'@Html.AntiForgeryTokenName' : '@Html.AntiForgeryTokenValue'
Askolein
4
Ницца хорошо. С этим у вас есть ajax вызов файла cshtm .... вы не должны mox js с бритвой так много, по моему мнению.
bunny1985
Я опустил этот вопрос, потому что считаю, что проще использовать статический класс AntiForgery. Получение HTML и его замена вместо прямого получения значения токена - плохая практика. ASP.NET является полностью открытым исходным кодом: github.com/ASP-NET-MVC/aspnetwebstack/blob/… (но теперь может быть стоит написать еще один ответ с помощью специального метода расширения, который получает только токен)
usr-local- ΝΛΩΝ
4
Более простой способ получить значение токена - использовать XElement. XElement.Parse(antiForgeryInputTag).Attribute("value").Value
darrunategui
3
@transformervar antiForgeryInputTag = helper.AntiForgeryToken().ToString(); return XElement.Parse(antiForgeryInputTag).Attribute("value").Value
darrunategui
45

это так просто! когда вы используете @Html.AntiForgeryToken()в своем html-коде, это означает, что сервер подписал эту страницу, и каждый запрос, отправляемый на сервер с этой конкретной страницы, имеет знак, запрещающий хакерам посылать поддельный запрос. поэтому для аутентификации этой страницы сервером вы должны выполнить два шага:

1. отправить параметр с именем __RequestVerificationTokenи получить его значения используйте коды ниже:

<script type="text/javascript">
    function gettoken() {
        var token = '@Html.AntiForgeryToken()';
        token = $(token).val();
        return token;
   }
</script>

например взять вызов AJAX

$.ajax({
    type: "POST",
    url: "/Account/Login",
    data: {
        __RequestVerificationToken: gettoken(),
        uname: uname,
        pass: pass
    },
    dataType: 'json',
    contentType: 'application/x-www-form-urlencoded; charset=utf-8',
    success: successFu,
});

и шаг 2 просто украсить ваш метод действия [ValidateAntiForgeryToken]

Аболфазл
источник
Спасибо, отлично работает для поста json ... мне не хватало contentType :(
Snziv Gupta
9

В Asp.Net Core вы можете запросить токен напрямую, как описано в документации :

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf    
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Context).RequestToken;
    }
}

И используйте это в JavaScript:

function DoSomething(id) {
    $.post("/something/todo/"+id,
               { "__RequestVerificationToken": '@GetAntiXsrfRequestToken()' });
}

Вы можете добавить рекомендуемый глобальный фильтр, как описано в документации :

services.AddMvc(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
})

Обновить

Вышеупомянутое решение работает в сценариях, которые являются частью .cshtml. Если это не так, то вы не можете использовать это напрямую. Мое решение состояло в том, чтобы сначала использовать скрытое поле для хранения значения.

Мой обходной путь, все еще использующий GetAntiXsrfRequestToken:

Когда нет формы:

<input type="hidden" id="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">

nameАтрибут может быть опущен , так как я использую idатрибут.

Каждая форма включает этот токен. Таким образом, вместо добавления еще одной копии того же токена в скрытое поле, вы также можете искать существующее поле с помощью name. Обратите внимание: внутри документа может быть несколько форм, поэтому nameв этом случае он не уникален. В отличие от idатрибута, который должен быть уникальным.

В скрипте найдите по id:

function DoSomething(id) {
    $.post("/something/todo/"+id,
       { "__RequestVerificationToken": $('#RequestVerificationToken').val() });
}

Альтернативой, без ссылки на токен, является отправка формы со скриптом.

Образец формы:

<form id="my_form" action="/something/todo/create" method="post">
</form>

Токен автоматически добавляется в форму как скрытое поле:

<form id="my_form" action="/something/todo/create" method="post">
<input name="__RequestVerificationToken" type="hidden" value="Cf..." /></form>

И отправьте в сценарий:

function DoSomething() {
    $('#my_form').submit();
}

Или используя метод сообщения:

function DoSomething() {
    var form = $('#my_form');

    $.post("/something/todo/create", form.serialize());
}
Руард ван Элбург
источник
Я думаю, что это решение работает, только если ваш javascript также находится в вашем файле cshtml.
carlin.scott
6

В Asp.Net MVC при использовании @Html.AntiForgeryToken()Razor создается скрытое поле ввода с именем __RequestVerificationTokenдля хранения токенов. Если вы хотите написать реализацию AJAX, вы должны сами извлечь этот токен и передать его в качестве параметра на сервер, чтобы его можно было проверить.

Шаг 1: Получить токен

var token = $('input[name="`__RequestVerificationToken`"]').val();

Шаг 2: Передайте токен в вызове AJAX

function registerStudent() {

var student = {     
    "FirstName": $('#fName').val(),
    "LastName": $('#lName').val(),
    "Email": $('#email').val(),
    "Phone": $('#phone').val(),
};

$.ajax({
    url: '/Student/RegisterStudent',
    type: 'POST',
    data: { 
     __RequestVerificationToken:token,
     student: student,
        },
    dataType: 'JSON',
    contentType:'application/x-www-form-urlencoded; charset=utf-8',
    success: function (response) {
        if (response.result == "Success") {
            alert('Student Registered Succesfully!')

        }
    },
    error: function (x,h,r) {
        alert('Something went wrong')
      }
})
};

Примечание : тип контента должен быть'application/x-www-form-urlencoded; charset=utf-8'

Я загрузил проект на Github; Вы можете скачать и попробовать.

https://github.com/lambda2016/AjaxValidateAntiForgeryToken

Фрэнк Одом
источник
Как я могу использовать форму сериализации здесь: student: $ ('# frm-student').
Serialize
6

        function DeletePersonel (id) {

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

                $ .Ajax ({
                    тип: 'POST',
                    url: '/ Personel / Delete /' + id,
                    данные: данные,
                    кеш: ложь,
                    processData: false,
                    contentType: false,
                    success: function (result) {

                    }
                });

        }
    

        открытый статический класс HtmlHelper
        {
            публичная статическая строка GetAntiForgeryToken ()
            {
                System.Text.RegularExpressions.Match value = System.Text.RegularExpressions.Regex.Match (System.Web.Helpers.AntiForgery.GetHtml (). ToString (), "(?: Value = \") (. *) (? : \ ")");
                если (значение. Успех)
                {
                    возвращаемое значение. Группы [1]. Значение;
                }
                возвращение "";
            }
        }
Исмаил Эски
источник
3

Я знаю, что это старый вопрос. Но я все равно добавлю свой ответ, может помочь кому-то вроде меня.

Если вы не хотите обрабатывать результат от действия поста контроллера, как, например, вызов LoggOffметода Accountsконтроллера, вы можете сделать следующую версию ответа @DarinDimitrov:

@using (Html.BeginForm("LoggOff", "Accounts", FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
    @Html.AntiForgeryToken()
}

<!-- this could be a button -->
<a href="#" id="ajaxSubmit">Submit</a>

<script type="text/javascript">
    $('#ajaxSubmit').click(function () {

        $('#__AjaxAntiForgeryForm').submit();

        return false;
    });
</script>
Аамир
источник
3

В контроллере аккаунта:

    // POST: /Account/SendVerificationCodeSMS
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public JsonResult SendVerificationCodeSMS(string PhoneNumber)
    {
        return Json(PhoneNumber);
    }

Ввиду:

$.ajax(
{
    url: "/Account/SendVerificationCodeSMS",
    method: "POST",
    contentType: 'application/x-www-form-urlencoded; charset=utf-8',
    dataType: "json",
    data: {
        PhoneNumber: $('[name="PhoneNumber"]').val(),
        __RequestVerificationToken: $('[name="__RequestVerificationToken"]').val()
    },
    success: function (data, textStatus, jqXHR) {
        if (textStatus == "success") {
            alert(data);
            // Do something on page
        }
        else {
            // Do something on page
        }
    },
    error: function (jqXHR, textStatus, errorThrown) {
        console.log(textStatus);
        console.log(jqXHR.status);
        console.log(jqXHR.statusText);
        console.log(jqXHR.responseText);
    }
});

Важно , чтобы установить , contentTypeчтобы 'application/x-www-form-urlencoded; charset=utf-8'или просто опустить contentTypeиз объекта ...

Адель Мурад
источник
не очень практично, означает, что вы должны кодировать каждую форму, и если формы содержат много элементов, это может быть болезненно :(
djack109
0

Я перепробовал много обходных путей, и ни один из них не помог мне. Исключением было "Обязательное поле формы защиты от подделки" __RequestVerificationToken ".

Что помогло мне, так это переключить форму .ajax на .post:

$.post(
    url,
    $(formId).serialize(),
    function (data) {
        $(formId).html(data);
    });
Стефан Мичев
источник
0

Не стесняйтесь использовать функцию ниже:

function AjaxPostWithAntiForgeryToken(destinationUrl, successCallback) {
var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;
$.ajax({
    type: "POST",
    url: destinationUrl,
    data: { __RequestVerificationToken: token }, // Your other data will go here
    dataType: "json",
    success: function (response) {
        successCallback(response);
    },
    error: function (xhr, status, error) {
       // handle failure
    }
});

}

Комал Наранг
источник
0

Токен не будет работать, если он был предоставлен другим контроллером. Например, это не будет работать, если представление было возвращено Accountsконтроллером, а вы POST- Clientsконтроллеру.

OutstandingBill
источник