dołączanie fałszywego tokena w AJAX po ASP.NET MVC

168

Mam problem z AntiForgeryTokenem z ajaxem. Korzystam z ASP.NET MVC 3. Próbowałem rozwiązania w wywołaniach jQuery Ajax i Html.AntiForgeryToken () . Korzystając z tego rozwiązania, token jest teraz przekazywany:

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

Kiedy usuwam [ValidateAntiForgeryToken]atrybut, aby sprawdzić, czy dane (z tokenem) są przekazywane jako parametry do kontrolera, widzę, że są przekazywane. Ale z jakiegoś powodu A required anti-forgery token was not supplied or was invalid.komunikat nadal pojawia się, gdy przywracam atrybut.

Jakieś pomysły?

EDYTOWAĆ

W formularzu generowany jest żeton przeciw fałszerstwu, ale nie używam akcji przesyłania, aby go przesłać. Zamiast tego po prostu pobieram wartość tokena za pomocą jquery, a następnie próbuję opublikować to ajax.

Oto formularz, który zawiera token i znajduje się na górnej stronie wzorcowej:

<form id="__AjaxAntiForgeryForm" action="#" method="post">
    @Html.AntiForgeryToken()
</form>
OJ Raqueño
źródło

Odpowiedzi:

289

Podałeś nieprawidłowo contentTypeto application/json.

Oto przykład, jak to może działać.

Kontroler:

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

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

Widok:

@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>
Darin Dimitrov
źródło
Cześć, dzięki za szybką odpowiedź. Przepraszam, że nie wspomniałem o tym w pytaniu; W tej chwili nie używam akcji przesyłania. (Token jest w formie, ale nie używam przycisku przesyłania, aby go przesłać). Czy można po prostu zmienić typ treści na inny?
OJ Raqueño
13
Fakt, że nie używasz akcji przesyłania, nie zmienia znacząco mojej odpowiedzi. Wszystko, co musisz zrobić, to zasubskrybować jakieś inne wydarzenie (kliknięcie przycisku, kliknięcie kotwicy lub cokolwiek innego i po prostu przeczytać wartość ukrytego pola). Jeśli chodzi o wysyłanie zapytania AJAX, możesz skorzystać z przykładu podanego w mojej odpowiedzi. Nie należy używać contentTypeto, application/jsonponieważ serwer oczekuje, że __RequestVerificationTokenparametr będzie częścią ładunku żądania POST przy użyciu application/x-www-form-urlencoded.
Darin Dimitrov
jak ten kod $(this).data('url'),może zrozumieć, jaki byłby adres URL mojego kontrolera i akcji. proszę wytłumacz. dzięki
Mou
2
Pierwotne pytanie dotyczyło contentType: „application / json”. W przypadku zwykłych postów ajax, w tym __RequestVerificationToken w postu formularza, będzie oczywiście działać, ponieważ jest jak zwykły post formularza. Jednak gdy chcesz opublikować json (stąd typ zawartości), to nie działa. Jest to więc przypadek błędnego przyjęcia powyższego jako odpowiedzi.
John
Czy muszę używać „ModelState.IsValid”? Jak mogę stwierdzić, że to działa?
Moran Monovich
61

Inne (mniej javascriptowe) podejście, które zrobiłem, wygląda mniej więcej tak:

Najpierw pomocnik 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));
}

która zwróci ciąg

__RequestVerificationToken:"P5g2D8vRyE3aBn7qQKfVVVAsQc853s-naENvpUAPZLipuw0pa_ffBf9cINzFgIRPwsf7Ykjt46ttJy5ox5r3mzpqvmgNYdnKc1125jphQV0NnM5nGFtcXXqoY3RpusTH_WcHPzH4S4l1PmB8Uu7ubZBftqFdxCLC5n-xT0fHcAY1"

więc możemy tego używać w ten sposób

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

I wydaje się, że działa!

Maks
źródło
5
+1, fajnie. Po prostu podzieliłem na @Html.AntiForgeryTokenForAjaxPostdwie części, aby uzyskać nazwę tokena w jednej ręce i jego wartość w drugiej. W przeciwnym razie podświetlenie składni jest pomieszane. Kończy się w ten sposób (usunięto również pojedyncze cudzysłowy z zwróconego wyniku, aby zachowywał się jak każdy pomocnik MVC, na przykład @Url):'@Html.AntiForgeryTokenName' : '@Html.AntiForgeryTokenValue'
Askolein
4
nic miło. Dzięki temu masz plik AJAX call n cshtm .... moim zdaniem nie powinieneś tak często używać js z maszynką do golenia.
bunny1985
Odrzuciłem to pytanie, ponieważ uważam, że prostszym podejściem jest użycie statycznej klasy AntiForgery. Pobieranie kodu HTML i zastępowanie go zamiast bezpośredniego pobierania wartości tokena jest złą praktyką. ASP.NET jest w pełni open source: github.com/ASP-NET-MVC/aspnetwebstack/blob/… (ale teraz warto byłoby napisać kolejną odpowiedź z niestandardową metodą rozszerzenia, która pobiera tylko token)
usr-local- ΕΨΗΕΛΩΝ
4
Czystszym sposobem uzyskania samej wartości tokenu byłoby użycie XElement. XElement.Parse(antiForgeryInputTag).Attribute("value").Value
darrunategui
3
@transformervar antiForgeryInputTag = helper.AntiForgeryToken().ToString(); return XElement.Parse(antiForgeryInputTag).Attribute("value").Value
darrunategui
45

to takie proste! kiedy używasz @Html.AntiForgeryToken()w swoim kodzie html, oznacza to, że serwer podpisał tę stronę, a każde żądanie wysyłane do serwera z tej konkretnej strony ma znak uniemożliwiający wysłanie fałszywego żądania przez hakerów. więc aby ta strona została uwierzytelniona przez serwer, należy wykonać dwa kroki:

1. wyślij parametr o nazwie __RequestVerificationTokeni pobierz jego wartość użyj poniższych kodów:

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

na przykład weź wywołanie 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,
});

i krok 2 po prostu udekoruj swoją metodę działania przez [ValidateAntiForgeryToken]

Abolfazl
źródło
Dzięki, działa świetnie dla postu json ... brakowało mi contentType :(
Snziv Gupta
9

W Asp.Net Core możesz bezpośrednio zażądać tokenu, zgodnie z dokumentacją :

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

I użyj go w javascript:

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

Możesz dodać zalecany filtr globalny, zgodnie z dokumentacją :

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

Aktualizacja

Powyższe rozwiązanie działa w skryptach, które są częścią pliku .cshtml. Jeśli tak nie jest, nie możesz tego użyć bezpośrednio. Moim rozwiązaniem było użycie ukrytego pola do przechowywania wartości w pierwszej kolejności.

Moje obejście, nadal używam GetAntiXsrfRequestToken:

Gdy nie ma formularza:

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

nameAtrybut może zostać pominięte, ponieważ używam idatrybut.

Każdy formularz zawiera ten token. Zamiast więc dodawać kolejną kopię tego samego tokena w ukrytym polu, możesz również wyszukać istniejące pole według name. Uwaga: dokument może zawierać wiele formularzy, więc namew tym przypadku nie jest on unikalny. W przeciwieństwie do idatrybutu, który powinien być unikalny.

W skrypcie znajdź według identyfikatora:

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

Alternatywą, bez konieczności odwoływania się do tokena, jest przesłanie formularza za pomocą skryptu.

Przykładowy formularz:

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

Token jest automatycznie dodawany do formularza jako ukryte pole:

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

I prześlij w scenariuszu:

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

Lub za pomocą metody wysyłania:

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

    $.post("/something/todo/create", form.serialize());
}
Ruard van Elburg
źródło
Myślę, że to rozwiązanie działa tylko wtedy, gdy twój javascript znajduje się również w pliku cshtml.
carlin.scott
6

W Asp.Net MVC, gdy używasz @Html.AntiForgeryToken()Razor, tworzy ukryte pole wejściowe z nazwą __RequestVerificationTokendo przechowywania tokenów. Jeśli chcesz napisać implementację AJAX, musisz samodzielnie pobrać ten token i przekazać go jako parametr do serwera, aby można go było zweryfikować.

Krok 1: Zdobądź token

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

Krok 2: Przekaż token w wywołaniu 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')
      }
})
};

Uwaga : typ zawartości powinien być'application/x-www-form-urlencoded; charset=utf-8'

Wrzuciłem projekt na Github; możesz go pobrać i wypróbować.

https://github.com/lambda2016/AjaxValidateAntiForgeryToken

Frank Odoom
źródło
Jak mogę użyć formularza serializuj tutaj student: $ ('# frm-student'). Serialize (),
LittleDragon
6

        function DeletePersonel (id) {

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

                $ .ajax ({
                    typ: 'POST',
                    url: '/ Personel / Delete /' + id,
                    dane: dane,
                    cache: false,
                    processData: false,
                    contentType: false,
                    sukces: funkcja (wynik) {

                    }
                });

        }
    

        publiczna klasa statyczna HtmlHelper
        {
            publiczny ciąg statyczny GetAntiForgeryToken ()
            {
                System.Text.RegularExpressions.Match value = System.Text.RegularExpressions.Regex.Match (System.Web.Helpers.AntiForgery.GetHtml (). ToString (), "(?: Value = \") (. *) (? : \ ")");
                if (value.Success)
                {
                    zwracana wartość.Grupy [1] .Wartość;
                }
                powrót "";
            }
        }
ismail eski
źródło
3

Wiem, że to stare pytanie. Ale i tak dodam swoją odpowiedź, może pomóc komuś takiemu jak ja.

Jeśli nie chcesz przetwarzać wyniku z akcji post kontrolera, na przykład wywoływania LoggOffmetody Accountskontrolera, możesz zrobić następującą wersję odpowiedzi @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>
Aamir
źródło
3

W kontrolerze konta:

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

Z uwagi:

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

Ważne jest, aby zestaw contentTypedo 'application/x-www-form-urlencoded; charset=utf-8'lub po prostu pominąć contentTypez obiektu ...

Adel Mourad
źródło
niezbyt praktyczne, oznacza, że ​​musisz zakodować każdy formularz, a jeśli formularze mają dużo elementów, może to być uciążliwe :(
djack109
0

Wypróbowałem wiele sposobów obejścia tego problemu i żadne z nich nie działało dla mnie. Wyjątkiem było „Wymagane pole formularza zapobiegające fałszowaniu” __RequestVerificationToken ”.

Pomogło mi przejście z formularza .ajax na .post:

$.post(
    url,
    $(formId).serialize(),
    function (data) {
        $(formId).html(data);
    });
Stefan Michev
źródło
0

Zapraszam do skorzystania z poniższej funkcji:

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

}

Komal Narang
źródło
0

Token nie zadziała, jeśli został dostarczony przez inny kontroler. Np. To nie zadziała, jeśli widok został zwrócony przez Accountskontroler, a Ty POSTdo Clientskontrolera.

Zaległy rachunek
źródło