Предоставьте токен AntiForgery с System.Net.Http.HttpClient и MVC

У меня есть приложение WPF (может быть, любая winform, я думаю), которое пытается войти на стандартный веб-сайт MVC 5, используя HttpClient.

Обычно я могу успешно войти в систему с вызовом PostAsync(), где я предоставляю параметры UserName и Password в HttpContent!

Однако, когда я добавляю [ValidateAntiForgeryToken] к действию входа в систему моего контроллера (POST), вызов PostAsync() завершается с ошибкой внутреннего сервера.

Я попытался собрать "__RequestVerificationToken" из простого запроса GET и отправить его вместе с моим запросом POST, добавив его в параметры POST, заголовок запроса или CookieContainer HttpHandler (или любую комбинацию из трех), но все же я получаю ошибку 500 с сервера.

Я знаю, что это можно сделать с помощью HttpWebRequests (по- видимому), но я не знаю, чего мне не хватает при использовании HttpClient. Я также не знаю, что именно пошло не так на стороне сервера.. или как это проверить, поскольку код никогда не достигает метода моего контроллера.

Кто-то еще попробовал это случайно?

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

Я добавляю необработанные данные, отправленные браузером для GET и POST:

GET http://localhost:57457/Account/Login HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://localhost:57457/Account/Login
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
DNT: 1
Host: localhost:57457
Cookie: NavigationTreeViewState=%5b%7b%27N0_1%27%3a%27T%27%2c%27N0%27%3a%27T%27%7d%2c%27N0_1_2%27%2c%7b%7d%5d; style=default; __RequestVerificationToken=Bak42Ga5sHJitYlmut6OgvmqXNmP7kKQRNaMSsLMAUh86iHGGmz5pnNfz_soKu46Wax9sG23arPOTnSh1bvaWyWqQ9NH4GJxFmendW8VFTg1

RESPONSE:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 5.2
X-Frame-Options: SAMEORIGIN
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcRUJTLkNvZGVcUHJvamVjdHNcQ1ZSUE9TX1dlYlNpdGVcQ1ZSUE9TX1dlYlNpdGVcQWNjb3VudFxMb2dpbg==?=
X-Powered-By: ASP.NET
Date: Thu, 04 Dec 2014 10:00:00 GMT
Content-Length: 1734
[View page content]

POST http://localhost:57457/Account/Login HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://localhost:57457/Account/Login
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Content-Length: 180
DNT: 1
Host: localhost:57457
Pragma: no-cache
Cookie: NavigationTreeViewState=%5b%7b%27N0_1%27%3a%27T%27%2c%27N0%27%3a%27T%27%7d%2c%27N0_1_2%27%2c%7b%7d%5d; style=default; __RequestVerificationToken=Bak42Ga5sHJitYlmut6OgvmqXNmP7kKQRNaMSsLMAUh86iHGGmz5pnNfz_soKu46Wax9sG23arPOTnSh1bvaWyWqQ9NH4GJxFmendW8VFTg1

__RequestVerificationToken=Bak42Ga5sHJitYlmut6OgvmqXNmP7kKQRNaMSsLMAUh86iHGGmz5pnNfz_soKu46Wax9sG23arPOTnSh1bvaWyWqQ9NH4GJxFmendW8VFTg1&UserName=test&Password=test

RESPONSE:
HTTP/1.1 400 Bad request (user/password for testing purposes only)
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 5.2
X-Frame-Options: SAMEORIGIN
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcRUJTLkNvZGVcUHJvamVjdHNcQ1ZSUE9TX1dlYlNpdGVcQ1ZSUE9TX1dlYlNpdGVcQWNjb3VudFxMb2dpbg==?=
X-Powered-By: ASP.NET
Date: Thu, 04 Dec 2014 10:00:00 GMT
Content-Length: 4434
[View page content]

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

Вот что мое приложение отправляет для GET и POST:

GET http://localhost:57457/Account/Login HTTP/1.1
Host: localhost:57457
Connection: Keep-Alive

POST http://localhost:57457/Account/Login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:57457
Cookie: __RequestVerificationToken=df9nBSP_J1IiLrv84RwrkmvbYBrnH4iqv97wRvz6HMPLWBhgI4XzGeAFcschovHwD8mTtHU6xrmVxz1Ku96_BaoB79le_vLTcrgGemU4gjc1
Content-Length: 163
Expect: 100-continue

__RequestVerificationToken=df9nBSP_J1IiLrv84RwrkmvbYBrnH4iqv97wRvz6HMPLWBhgI4XzGeAFcschovHwD8mTtHU6xrmVxz1Ku96_BaoB79le_vLTcrgGemU4gjc1&UserName=test&Password=test

И, наконец, это ошибка:

[HttpAntiForgeryException (0x80004005): Ошибка проверки предоставленного токена защиты от подделки. Cookie "__RequestVerificationToken" и поле формы "__RequestVerificationToken" были заменены.]

Спасибо!

3 ответа

Решение

Вам, вероятно, нужно включить cookie с идентификатором сессии aspnet в ваши запросы

РЕДАКТИРОВАТЬ: ОК, верно, это не идентификатор сеанса, но вам нужно два токена, чтобы отправить обратно на ваш пост действий.

Я думаю, что вы делаете неправильно, используя одно и то же значение для обоих токенов, но они должны быть разными, хотя имя обоих токенов - __RequestVerificationToken. Токен, извлеченный из cookie, должен быть отправлен как cookie, а токен, извлеченный из поля формы, возвращается как поле формы.

Это потому, что вы пропускаете токен против подделки HtmlHelper.AntiForgeryToken() в вашем ПОСТ из вашего приложения.

Вам нужно будет загрузить страницу из вашего приложения WPF с HtmlHelper.AntiForgeryToken() на вид. Затем возьмите значение скрытого элемента ввода с именем __RequestVerificationToken и прикрепите его к вашему POST-запросу на сервер.

Мне просто нужно было разобраться в этом самому. Ниже приведен код, который я написал, который работает. Вам потребуется изменить некоторые переменные, которые имеют смысл для вашего приложения, включая переменные веб-клиента..BaseAddress, путь к странице входа и форму модели, которую выPOSTдля успешного входа в систему.

      #nullable enable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace **FIXME**;

public class WebClient
{
    private readonly CookieContainer _cookieContainer;
    private readonly HttpClient _client;
    private readonly string _loginPath;

    public WebClient(Uri baseAddress, string loginPath)
    {
        _cookieContainer = new CookieContainer();
        _client = new HttpClient(new HttpClientHandler { CookieContainer = _cookieContainer }) { BaseAddress = baseAddress };
        _loginPath = loginPath;
    }

    public async Task<string> GetAntiforgeryToken()
    {
        var response = await _client.GetAsync(_loginPath);
        var content = await response.Content.ReadAsStringAsync();

        var match = Regex.Match(content, "name=\"__RequestVerificationToken\" type=\"hidden\" value=\"(.*?)\"");

        return match.Success ? match.Groups[1].Value : throw new Exception("Unable to get anti-forgery token");
    }

    public async Task<HttpResponseMessage> LogIn(string username, string password, string antiforgeryToken)
    {
        _client.DefaultRequestHeaders.Accept.Clear();
        _client.DefaultRequestHeaders.Add("__RequestVerificationToken", antiforgeryToken);

        var content = new FormUrlEncodedContent
        (
            new Dictionary<string, string>
            {
                { "Username", username },
                { "Password", password },
                { "__RequestVerificationToken", antiforgeryToken }
            }
        );

        return await _client.PostAsync(_loginPath, content);
    }

    public CookieContainer CookieContainer => _cookieContainer;

    public async Task<HttpResponseMessage> NavigateTo(string uri)
    {
        return await _client.GetAsync(uri);
    }

    public Cookie? GetCookie(string cookieName)
    {
        return _cookieContainer.GetCookies(_client.BaseAddress).Cast<Cookie>().SingleOrDefault(c => c.Name == cookieName);
    }
}

Использование:

      var baseAddress = new Uri("**FIXME**");
var loginPath = "**FIXME**"; 
var username = "**FIXME**";
var password = "**FIXME**";

var webClient = new WebClient(baseAddress, loginPath);
var antiforgeryToken = await webClient.GetAntiforgeryToken();
var loginResponse = await webClient.LogIn(username, password, antiforgeryToken);
Другие вопросы по тегам