winforms 使用System.Net.Http.HttpClient和MVC提供防伪令牌

tkclm6bt  于 2023-03-31  发布在  .NET
关注(0)|答案(3)|浏览(155)

我有一个WPF(我猜可能是任何winform)应用程序,它试图使用HttpClient登录到标准的MVC 5网站。
通常,我可以通过调用PostAsync()成功登录,其中我在HttpContent中提供了用户名和密码参数!
但是,当我将**[ValidateAntiForgeryToken]添加到控制器的登录(POST)操作时,PostAsync()调用失败,并显示内部服务器错误。
我尝试从一个简单的GET请求中收集“__RequestVerificationToken”,并通过将其添加到POST参数,请求的Header或HttpHandler的
CookieContainer**(或三者的任何组合)来发送它,但我仍然从服务器获得错误500。
我知道这可以用HttpWebRequests(apparently)来完成,但我不知道在使用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

最后是错误:
[HttpException(0x 80004005):无法验证数据。验证提供的防伪令牌失败。Cookie“__RequestVerificationToken”和表单字段“__RequestVerificationToken”已交换。]**
谢谢!

ngynwnxp

ngynwnxp1#

您可能需要在请求中包含aspnet会话id cookie
编辑:好吧,你的权利,这不是会话ID,但你需要两个令牌发送回你的职位行动。
我认为你做错了什么是使用相同的值为两个令牌,但他们应该是不同的,altho名称的两个令牌是__RequestVerificationToken。令牌抓取从cookie应发送回cookie和令牌抓取从表单字段返回表单字段。

t1rydlwq

t1rydlwq2#

这是因为您在应用程序的POST中缺少来自HtmlHelper.AntiForgeryToken()的防伪令牌。
你需要从你的WPF应用程序中加载一个在视图中有HtmlHelper.AntiForgeryToken()的页面,然后获取名为__RequestVerificationToken的隐藏输入元素的值,并将其附加到你的登录POST请求中。

lmyy7pcs

lmyy7pcs3#

下面是我写的代码,你需要修改一些对你的应用程序有意义的变量,包括web客户端的.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);

相关问题