C# HttpClient配置具有tls指纹发起跟浏览器一样的请求解决反爬虫防护

目录标题

    • [tls-client-sharp 介绍:](#tls-client-sharp 介绍:)
    • [bogdanfinn/tls-client 项目地址](#bogdanfinn/tls-client 项目地址)
    • 使用tls-client-sharp参考代码:
    • 参考代码:
    • [tls-client.dll 下载地址:](#tls-client.dll 下载地址:)
    • [tls-client.dll 、tls-client.so 网盘下载地址,版本 v1.15.1:](#tls-client.dll 、tls-client.so 网盘下载地址,版本 v1.15.1:)

我的使项目版本:

  • 项目使用nuget包:tls-client-sharp 1.0.0
  • 项目框架集:.NET 10

tls-client-sharp 介绍:

一个用于 bogdanfinn/tls-client 的 C# 封装,提供自定义的 HttpClientHandler。该封装可让你发起模拟主流浏览器 TLS 指纹的 HTTP 请求,从而帮助绕过反机器人保护机制。安装时,该封装会在首次运行时自动检查并从 GitHub 发布页下载所需的 tls-client 库,并将库文件存储在用户的临时文件夹中,以确保程序的可移植性。你无需手动下载 DLL 文件。

bogdanfinn/tls-client 项目地址

https://github.com/bogdanfinn/tls-client

使用tls-client-sharp参考代码:

csharp 复制代码
using TlsClientWrapperSharp.Handlers;
using TlsClientWrapperSharp.Helpers;
using TlsClientWrapperSharp.Models;

// 1. Ensure the library is downloaded (checks for updates or missing file)
await TlsLibraryLoader.EnsureLibraryExistsAsync();

// 2. Initialize the handler with a specific browser identifier
var tlsClientHandler = new TlsClientHandler
{
    TlsClientIdentifier = ClientIdentifier.Chrome133
};

// 3. Create a standard HttpClient using the handler
var httpClient = new HttpClient(tlsClientHandler);

// (Optional) Add headers as needed
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", "en-GB,en;q=0.9");

// 4. Make requests
var response = await httpClient.GetStringAsync("https://tls.browserleaks.com/tls");
Console.WriteLine(response);

参考代码:

设置了Http代理,tls-client可以设置指定浏览器版本的指纹,可以用于绕过TLS JA3 指纹风控拦截

csharp 复制代码
using ConsoleOuBo.Model;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json.Nodes;
using TlsClientWrapperSharp.Handlers;
using TlsClientWrapperSharp.Helpers;
using TlsClientWrapperSharp.Models;
using static System.Net.WebRequestMethods;

namespace WsInspectorDemo
{

    /// <summary>
    /// 封装 agg111 登录接口调用和 Cookie 管理。
    /// </summary>
    public sealed class Agg111LoginClient : IDisposable
    {
        private readonly HttpClient _http;
 

        /// <summary>
        /// 是否使用代理
        /// </summary>
        private bool _usingProxy;
        public CookieContainer Cookies { get; } = new();

        /// <summary>
        /// 初始化 Agg111LoginClient 实例。
        /// </summary>
        /// <param name="apiBase">apiBase 参数。</param>
        /// <param name="timeoutSeconds">timeoutSeconds 参数。</param>
        public Agg111LoginClient(string apiBase, int timeoutSeconds)
        {
      

            var proxySettings = CommonConfigHelpter.MyProxySettings;
            _usingProxy = proxySettings is not null
                && !string.IsNullOrWhiteSpace(proxySettings.ServerAddress);

#if DEBUG
            //调试,false 不用代理
            //_usingProxy = false;
#endif


            if (!_usingProxy)
            {
			   //不用代理
                _http = new HttpClient(new HttpClientHandler
                {
                    AutomaticDecompression = DecompressionMethods.All,
                    UseCookies = true,
                    CookieContainer = Cookies,
                    UseProxy = false
                })
                {
                    Timeout = TimeSpan.FromSeconds(timeoutSeconds)
                };
            }
            else
            {
                // HTTP代理

                // Oxylabs 这里是 HTTP 代理。不要把同一个端点当成 SOCKS5 使用,
                // 也不要在客户端里写死 host/port,否则 Apifox 与程序实际走的代理可能不同。
                var proxyUri = new Uri($"http://{proxySettings.ServerAddress!.Trim()}:{proxySettings.Port}");
                //var proxyUri = new Uri($"http://127.0.0.1:9000");
                var webProxy = new WebProxy(proxyUri)
                {
                    UseDefaultCredentials = false // 关键!不要发送windows系统凭据,否则容易403
                };

                if (!string.IsNullOrWhiteSpace(proxySettings.Account))
                {
                    webProxy.Credentials = new NetworkCredential(
                        proxySettings.Account,
                        proxySettings.Password);
                }

                //无法伪造浏览器指纹,  被遭TLS JA3 指纹风控拦截
                //var socketsHttpHandler = new SocketsHttpHandler
                //{
                //    UseProxy = true,
                //    Proxy = webProxy,
                //    CookieContainer = Cookies,
                //    UseCookies = true,

                //    // 是否允许自动重定向
                //    AllowAutoRedirect = true,

                //    AutomaticDecompression = DecompressionMethods.All,
                //    PooledConnectionLifetime = TimeSpan.FromMinutes(5),
                //    ConnectTimeout = TimeSpan.FromSeconds(15),

                //    //// TLS 配置,解决WAF 403指纹拦截
                //    SslOptions = new SslClientAuthenticationOptions
                //    {
                //        // 强制TLS12,很多CDN/WAF对TLS1.3的.NET指纹拦截报403
                //        EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13,
                //        //EnabledSslProtocols = SslProtocols.Tls12,
                //        //EnabledSslProtocols = SslProtocols.Tls13,
                //        //CertificateRevocationCheckMode = X509RevocationMode.NoCheck,

                //        //// 忽略证书(本地抓包代理调试用,生产务必关闭!)
                //        //RemoteCertificateValidationCallback = (object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) =>
                //        //{
                //        //    LogHelpter.AddLog(
                //        //                 $"TLS debug:errors={sslPolicyErrors}; subject={certificate?.Subject}",
                //        //                 fileNamePrefix: LogFileNamePrefixEnum.login_error.ToString());

                //        //    // 调试:return true;           
                //        //    return sslPolicyErrors == SslPolicyErrors.None;
                //        //}
                //    },
                //};

                //===================设置代理2============
                //// 1. 确保已下载好dll ,windows使用tls-client.dll,linux使用tls-client.so,这两个文件需要放到执行根目录中
				//EnsureLibraryExistsAsync 会检查是否有更新或缺少的文件,会自动下载需要的tls-client.dll,生产环境建议是自己先下好放到根目录
                //var task = TlsLibraryLoader.EnsureLibraryExistsAsync();
                //task.Wait();

                // 2.为处理程序指定一个特定的浏览器标识符进行初始化
                var tlsClientHandler = new TlsClientHandler
                {
                    TlsClientIdentifier = ClientIdentifier.Chrome133,

                    //代理设置
                    Proxy = webProxy,
                };

                _http = new HttpClient(tlsClientHandler)
                {
                    Timeout = TimeSpan.FromSeconds(timeoutSeconds)
                };
            }


            _http.DefaultRequestHeaders.Accept.ParseAdd("application/json, text/plain, */*");
            _http.DefaultRequestHeaders.UserAgent.ParseAdd(
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36");

            //_http.DefaultRequestHeaders.TryAddWithoutValidation("Host", "gg.vip");
            _http.DefaultRequestHeaders.Referrer = new Uri("https://gg.vip/");
            _http.DefaultRequestHeaders.TryAddWithoutValidation("Origin", "https://gg.vip");

            _http.DefaultRequestHeaders.AcceptEncoding.Clear();
            _http.DefaultRequestHeaders.TryAddWithoutValidation("accept-encoding", "gzip, deflate, br, zstd");
            _http.DefaultRequestHeaders.TryAddWithoutValidation("accept-language", "zh-CN,zh;q=0.9");
        }

        /// <summary>
        /// 执行 Login Async 的处理逻辑。
        /// </summary>
        /// <param name="username">username 参数。</param>
        /// <param name="password">password 参数。</param>
        /// <param name="lang">lang 参数。</param>
        /// <param name="algorithm">algorithm 参数。</param>
        /// <param name="cancellationToken">取消令牌。</param>
        /// <returns>表示异步操作结果的任务。</returns>
        public async Task<JsonObject> LoginAsync(
            string username,
            string password,
            string lang,
            LoginAlgorithm algorithm,
            CancellationToken cancellationToken)
        {
            // 方法流程:保留原有处理顺序,避免影响协议交互和外部行为。
            var logicalPayload = new JsonObject
            {
                ["user_name"] = username,
                ["user_pass"] = password,
                ["lang"] = NormalizeLang(lang)
            };

            using var response = await SendLoginRequestAsync(
                logicalPayload,
                algorithm,
                cancellationToken);
            var raw = await response.Content.ReadAsStringAsync(cancellationToken);
           // LogHelpter.AddLog($"登录返回原始数据:{raw}", fileNamePrefix: LogFileNamePrefixEnum.login.ToString());

            if (string.IsNullOrWhiteSpace(raw))
            {
                return new JsonObject
                {
                    ["code"] = (int)response.StatusCode,
                    ["msg"] = response.ReasonPhrase ?? "empty response",
                    ["data"] = ""
                };
            }

            JsonNode? rawNode;
            try
            {
                rawNode = JsonNode.Parse(raw);
            }
            catch
            {
                return new JsonObject
                {
                    ["code"] = (int)response.StatusCode,
                    ["msg"] = response.ReasonPhrase ?? "non-json response",
                    ["data"] = raw
                };
            }
 
            return new JsonObject
            {
                ["code"] = (int)response.StatusCode,
                ["msg"] = response.ReasonPhrase ?? "unexpected response",
                ["data"] = decrypted?.DeepClone()
            };
        }

        private async Task<HttpResponseMessage> SendLoginRequestAsync(
            JsonObject logicalPayload,
            LoginAlgorithm algorithm,
            CancellationToken cancellationToken)
        {
                var body = "{\"PacketType\":\"TableGameStatus\",\"WebsiteId\":7,\"WebSiteTableId\":\"P021\",\"State\":1}";

                using var request = new HttpRequestMessage(HttpMethod.Post, new Uri( "https://test.gg/api/login"))
                {
                    Content = new StringContent(body, Encoding.UTF8, "application/json")
                };

                // HTTP 代理通常通过 CONNECT 建立 TLS 隧道,先明确使用 HTTP/1.1,
                // 避免把直连时的 HTTP/3 协商行为带到代理链路中。
                request.Version = _usingProxy
                    ? HttpVersion.Version11
                    : HttpVersion.Version30;
                request.VersionPolicy = _usingProxy
                    ? HttpVersionPolicy.RequestVersionExact
                    : HttpVersionPolicy.RequestVersionOrLower;

                request.Content.Headers.Remove("Content-Type");
                request.Content.Headers.TryAddWithoutValidation("Content-Type", "application/json;charset=UTF-8");

                using var requestTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                requestTimeout.CancelAfter(TimeSpan.FromSeconds(12));

                try
                {                 

                    var response = await _http.SendAsync(
                        request,
                        HttpCompletionOption.ResponseHeadersRead,
                        requestTimeout.Token);

                    if (!response.IsSuccessStatusCode)
                    {
					   // 请求失败
                       // await LogUnexpectedResponseAsync(response, requestTimeout.Token);
                    }

                    // Axios treats non-2xx responses as failures and retries once.
                    if (response.IsSuccessStatusCode || attempt == 1)
                    {
                        return response;
                    }

                    response.Dispose();
                }
                catch (HttpRequestException) when (attempt == 0)
                {
                    // Retry transport failures once, matching the browser wrapper.
                }
                catch (OperationCanceledException) when (
                    attempt == 0 && !cancellationToken.IsCancellationRequested)
                {
                    // Retry a per-request timeout, but preserve caller cancellation.
                }
           

            throw new HttpRequestException("The login request failed after one retry.");
        }
 
 
        /// <summary>
        /// 释放当前对象占用的资源。
        /// </summary>
        public void Dispose() => _http.Dispose();

 
    }

 

}

tls-client.dll 下载地址:

下载好的tls-client.dll文件不管下的文件名是啥,都需要改为 tls-client.dll 放入程序执行根目录中,tls-client.so一样。

https://github.com/bogdanfinn/tls-client/releases

tls-client.so下载地址:

https://github.com/bogdanfinn/tls-client/releases

tls-client.dll 、tls-client.so 网盘下载地址,版本 v1.15.1:

通过网盘分享的文件:httpClient模拟TLS指纹数据

链接: https://pan.baidu.com/s/12OZj9OGGrsfT_ZIchMx3_A?pwd=247d 提取码: 247d

相关推荐
吴可可12327 分钟前
在CAD中点击添加多段线顶点的方法与实现
c#
全栈小51 小时前
【C#】.net core,静态方法线程安全解析,面试时经常被问的线程安全就藏在里面
安全·c#·.netcore
l1t1 小时前
DeepSeek总结的DuckDB 如何更快地运行递归 CTE
java·开发语言·数据库·mysql·duckdb
HAPPY酷1 小时前
python的对象和方法
开发语言·python
橙露1 小时前
移动端客户端原生开发语言
开发语言
Demon--hx1 小时前
设计不能被继承的类
开发语言·c++
兔兔兔兔11 小时前
记录C++ 13
开发语言·c++·算法
码行山野赴时序归途1 小时前
从暴力到最优:三道 C 语言入门题的解法思路
c语言·开发语言·数据结构·算法·leetcode·排序算法
、如果2 小时前
推文评论数据怎么拿?X(Twitter)评论分析 Skill 的工程拆解与实测
爬虫·ai编程·twitter