Предоставьте токен 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);