RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1178026
Accepted
aepot
aepot
Asked:2020-09-14 18:35:48 +0000 UTC2020-09-14 18:35:48 +0000 UTC 2020-09-14 18:35:48 +0000 UTC

在 HttpClient 中使用 Cookie 进行授权解析

  • 772

我一直想知道如何让HttpClientcookies像浏览器一样工作,然后在退出应用程序时保存,重启后继续使用。然后,最后,它找到了我,我做到了。

让它HttpClient像这样工作:

检查代码

public static class HttpManager
{
    private static readonly HttpClientHandler handler = new HttpClientHandler()
    {
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
        AllowAutoRedirect = true
    };

    private static readonly HttpClient client = new HttpClient(handler)
    {
        DefaultRequestVersion = new Version(2, 0)
    };

    static HttpManager()
    {
        client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0");
        client.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, br");
    }

    // GET
    public static async Task<string> GetPageAsync(string url)
    {
        using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
        return await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
    }

    // POST
    public static async Task<string> PostFormAsync(string url, Dictionary<string, string> data)
    {
        using HttpContent content = new FormUrlEncodedContent(data);
        using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
        using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
        return await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
    }

    // Загрузить и расшифровать Cookie из файла
    public static async Task LoadCookiesAsync(string filename)
    {
        if (File.Exists(filename))
        {
            using FileStream fs = File.OpenRead(filename);
            byte[] IV = new byte[16];
            fs.Read(IV);
            byte[] protectedKey = new byte[178];
            fs.Read(protectedKey);
            using AesManaged aes = new AesManaged
            {
                Key = ProtectedData.Unprotect(protectedKey, IV, DataProtectionScope.CurrentUser),
                IV = IV
            };
            using CryptoStream cs = new CryptoStream(fs, aes.CreateDecryptor(), CryptoStreamMode.Read, true);
            CookieCollection cookies = await JsonSerializer.DeserializeAsync<CookieCollection>(cs);
            foreach (Cookie cookie in cookies)
            {
                // не загружать, если кука заэкспайрилась
                if (!cookie.Expired && (cookie.Expires == DateTime.MinValue || cookie.Expires > DateTime.Now))
                    handler.CookieContainer.Add(cookie);
            }
        }
    }

    // Зашифровать и сохранить Cookie в файл
    public static async Task SaveCookiesAsync(string filename)
    {
        using AesManaged aes = new AesManaged();
        using FileStream fs = File.Create(filename);
        fs.Write(aes.IV);
        fs.Write(ProtectedData.Protect(aes.Key, aes.IV, DataProtectionScope.CurrentUser));
        using CryptoStream cs = new CryptoStream(fs, aes.CreateEncryptor(), CryptoStreamMode.Write, true);
        await JsonSerializer.SerializeAsync(cs, handler.CookieContainer.GetAllCookies());
    }
}

public static class CookieContainerExtensions
{
    // Забирает все куки из контейнера
    public static CookieCollection GetAllCookies(this CookieContainer container)
    {
        CookieCollection allCookies = new CookieCollection();
        IDictionary domains = (IDictionary)container.GetType()
            .GetRuntimeFields()
            .FirstOrDefault(x => x.Name == "m_domainTable")
            .GetValue(container);

        foreach (object field in domains.Values)
        {
            IDictionary values = (IDictionary)field.GetType()
                .GetRuntimeFields()
                .FirstOrDefault(x => x.Name == "m_list")
                .GetValue(field);

            foreach (CookieCollection cookies in values.Values)
            {
                allCookies.Add(cookies);
            }
        }
        return allCookies;
    }
}

Cookie 被序列化为 Json,通过 DPAPI 使用密钥保护加密,并保存到磁盘。


使用已检查代码的可重现示例

使用登录名和密码对 StackOverflow 进行授权

using HtmlAgilityPack;
using Fizzler.Systems.HtmlAgilityPack;
private const string filename = "cookies.bin";

static async Task Main(string[] args)
{  
    await HttpManager.LoadCookiesAsync(filename);

    string html = await HttpManager.GetPageAsync("https://ru.stackoverflow.com/");
    HtmlDocument doc = new HtmlDocument();
    doc.LoadHtml(html);
    if (doc.DocumentNode.QuerySelector(".top-bar ol").HasClass("user-logged-out"))
    {
        html = await HttpManager.GetPageAsync("https://ru.stackoverflow.com/users/login?ssrc=head&returnurl=https%3a%2f%2fru.stackoverflow.com%2f");
        doc = new HtmlDocument();
        doc.LoadHtml(html);
        string fkey = doc.DocumentNode.QuerySelector("form#login-form input[name=fkey]").Attributes["value"].Value;
        string ssrc = doc.DocumentNode.QuerySelector("form#login-form input[name=ssrc]").Attributes["value"].Value;
        Console.Write("Login: ");
        string login = Console.ReadLine();
        Console.Write("Password: ");
        string password = ReadPassword();
        Dictionary<string, string> formData = new Dictionary<string, string>
        {
            { "fkey", fkey },
            { "ssrc", ssrc },
            { "email", login },
            { "password", password },
            { "oauth_version", "" },
            { "oauth_server", "" }
        };
        html = await HttpManager.PostFormAsync("https://ru.stackoverflow.com/users/login?ssrc=head&returnurl=https%3a%2f%2fru.stackoverflow.com%2f", formData);
        doc = new HtmlDocument();
        doc.LoadHtml(html);
    }

    string user = doc.DocumentNode.QuerySelector(".top-bar ol .my-profile span.v-visible-sr").InnerText;
    Console.WriteLine(user);
    string rep = HtmlEntity.DeEntitize(doc.DocumentNode.QuerySelector(".top-bar ol .my-profile div.-rep").Attributes["title"].Value);
    Console.WriteLine(rep);
    string badges = string.Join(Environment.NewLine, doc.DocumentNode.QuerySelectorAll(".top-bar ol .my-profile div.-badges span.v-visible-sr").Select(x => x.InnerText));
    Console.WriteLine(badges);

    await HttpManager.SaveCookiesAsync(filename);
    Console.ReadKey();
}

private static string ReadPassword()
{
    string password = string.Empty;
    ConsoleKey key;
    do
    {
        ConsoleKeyInfo keyInfo = Console.ReadKey(true);
        key = keyInfo.Key;

        if (key == ConsoleKey.Backspace && password.Length > 0)
        {
            Console.Write("\b \b");
            password = password[0..^1];
        }
        else if (!char.IsControl(keyInfo.KeyChar))
        {
            Console.Write("*");
            password += keyInfo.KeyChar;
        }
    } while (key != ConsoleKey.Enter);
    Console.WriteLine();
    return password;
}

首次运行时的控制台输出要求提供凭据

Login: <my_email>@<censored>.ru
Password: ********************
aepot
ваша репутация: 4,904
2 золотых знака
5 серебряных знаков
25 бронзовых знаков

应用重启时输出到控制台,自动使用cookies进行授权

aepot
ваша репутация: 4,904
2 золотых знака
5 серебряных знаков
25 бронзовых знаков

无需检查可重现的示例,我知道它不考虑密码错误情况或帐户不支持密码登录。

顺便一提!为此,您需要在 StackExchange 帐户的安全设置中启用登录名和密码。

请看课程代码HttpManager,有什么需要改进或做不同的吗?我以前没有使用过 Cookie。


更新- 使用BinaryFormatter

在@ヒミコ的建议下,我尝试了一个使用. 它也有效。好处 - 现在您不需要额外的方法来提取 cookie,但它会“按原样”保存。我还没有弄清楚哪个更好,但微软斥责它不安全。BinaryFormatterCookieContainer BinaryFormatter

BinaryFormatter是不安全的,不能保证安全。有关详细信息,请参阅BinaryFormatter 安全指南。

public static class HttpManager
{
    private static readonly HttpClientHandler handler = new HttpClientHandler()
    {
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
        AllowAutoRedirect = true
    };

    private static readonly HttpClient client = new HttpClient(handler)
    {
        DefaultRequestVersion = new Version(2, 0)
    };

    static HttpManager()
    {
        client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0");
        client.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, br");
    }

    public static async Task<string> GetPageAsync(string url)
    {
        using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
        return await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
    }

    public static async Task<string> PostFormAsync(string url, Dictionary<string, string> data)
    {
        using HttpContent content = new FormUrlEncodedContent(data);
        using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
        using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
        return await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
    }

    public static async Task LoadCookiesAsync(string filename)
    {
        if (File.Exists(filename))
        {
            using FileStream fs = File.OpenRead(filename);
            byte[] IV = new byte[16];
            await fs.ReadAsync(IV);
            byte[] protectedKey = new byte[178];
            await fs.ReadAsync(protectedKey);
            using AesManaged aes = new AesManaged
            {
                Key = ProtectedData.Unprotect(protectedKey, IV, DataProtectionScope.CurrentUser),
                IV = IV
            };
            using CryptoStream cs = new CryptoStream(fs, aes.CreateDecryptor(), CryptoStreamMode.Read, true);
            handler.CookieContainer = new BinaryFormatter().Deserialize(cs) as CookieContainer ?? new CookieContainer();
        }
    }

    public static async Task SaveCookiesAsync(string filename)
    {
        using AesManaged aes = new AesManaged();
        using FileStream fs = File.Create(filename);
        await fs.WriteAsync(aes.IV);
        await fs.WriteAsync(ProtectedData.Protect(aes.Key, aes.IV, DataProtectionScope.CurrentUser));
        using CryptoStream cs = new CryptoStream(fs, aes.CreateEncryptor(), CryptoStreamMode.Write, true);
        new BinaryFormatter().Serialize(cs, handler.CookieContainer);
    }
}
c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    aepot
    2021-12-11T21:11:07Z2021-12-11T21:11:07Z

    结果,这个决定变成了这样。

    远离静态以使解决方案与 IoC 模式兼容。

    public class HttpManager : IDisposable
    {
        private readonly HttpClientHandler handler;
        private readonly HttpClient client;
    
        public HttpManager(string baseAddress = null)
        {
            handler = new HttpClientHandler()
            {
                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
                AllowAutoRedirect = true
            };
            client = new HttpClient(handler)
            {
                DefaultRequestVersion = new Version(2, 0)
            };
            client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0");
            client.DefaultRequestHeaders.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
            client.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, br");
            client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US;q=0.7,en;q=0.3");
    
            if (baseAddress != null)
            {
                client.BaseAddress = new Uri(baseAddress);
            }
        }
    
        public async Task<string> GetPageAsync(string url)
        {
            CheckDisposed();
            using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
            return await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
        }
    
        public async Task<string> PostFormAsync(string url, Dictionary<string, string> data)
        {
            CheckDisposed();
            using HttpContent content = new FormUrlEncodedContent(data);
            using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
            using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
            return await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
        }
    
        public async Task LoadCookiesAsync(string fileName)
        {
            CheckDisposed();
            if (File.Exists(filename))
            {
                using FileStream fs = File.OpenRead(fileName);
                byte[] IV = new byte[16];
                await fs.ReadAsync(IV);
                byte[] protectedKey = new byte[178];
                await fs.ReadAsync(protectedKey);
                using AesManaged aes = new AesManaged
                {
                    Key = ProtectedData.Unprotect(protectedKey, IV, DataProtectionScope.CurrentUser),
                    IV = IV
                };
                using CryptoStream cs = new CryptoStream(fs, aes.CreateDecryptor(), CryptoStreamMode.Read, true);
                CookieCollection cookies = await JsonSerializer.DeserializeAsync<CookieCollection>(cs);
                CookieContainer container = new CookieContainer();
                container.Add(cookies);
                handler.CookieContainer = container;
            }
        }
    
        public async Task SaveCookiesAsync(string fileName)
        {
            CheckDisposed();
            using AesManaged aes = new AesManaged();
            using FileStream fs = File.Create(fileName);
            await fs.WriteAsync(aes.IV);
            await fs.WriteAsync(ProtectedData.Protect(aes.Key, aes.IV, DataProtectionScope.CurrentUser));
            using CryptoStream cs = new CryptoStream(fs, aes.CreateEncryptor(), CryptoStreamMode.Write, true);
            await JsonSerializer.SerializeAsync(cs, handler.CookieContainer.GetAllCookies());
        }
    
        private bool disposed;
    
        private void CheckDisposed()
        {
            if (disposed)
                throw new ObjectDisposedException(nameof(HttpManager));
        }
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            CheckDisposed();
            if (disposing)
                client.Dispose();
            disposed = true;
        }
    
        ~HttpManager() => Dispose(false);
    }
    

    无法摆脱反射,这是一种提取 cookie 的方法:

    public static class CookieContainerExtensions
    {
        public static CookieCollection GetAllCookies(this CookieContainer container)
        {
            CookieCollection allCookies = new CookieCollection();
            Hashtable domainTable = (Hashtable)container.GetType()
                .GetRuntimeFields()
                .First(x => x.Name == "m_domainTable")
                .GetValue(container);
    
            FieldInfo pathListField = null;
            foreach (object domain in domainTable.Values)
            {
                SortedList pathList = (SortedList)(pathListField ??= domain.GetType()
                    .GetRuntimeFields()
                    .First(x => x.Name == "m_list"))
                    .GetValue(domain);
    
                foreach (CookieCollection cookies in pathList.GetValueList())
                    allCookies.Add(cookies);
            }
            return allCookies;
        }
    }
    

    用法

    string fileName = "cookies.bin";
    using HttpManager manager = new HttpManager("https://<адрес сайта>/");
    await manager.LoadCookiesAsync(fileName);
    string html = await manager.GetPageAsync("/admin");
    //...
    
    await manager.SaveCookiesAsync(filename);
    Console.WriteLine("Done.");
    

    .NET 6 中的新功能

    .NET 6 添加了一个方法CookieContainer- GetAllCookies()。

    • 4

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5