次の方法で共有


HTTP 要求で SNI をカスタマイズする

クライアントとサーバーが HTTPS 接続をネゴシエートするときは、最初に TLS 接続を確立する必要があります。 TLS ハンドシェイクの一部として、クライアントは、接続しているサーバーのドメイン名を TLS 拡張機能の 1 つで送信します。 複数の (仮想) サーバーが同じコンピューター上でホストされている場合、TLS プロトコルのこの機能により、クライアントは接続先のサーバーを識別し、それに応じてサーバー証明書などの TLS 設定を構成できます。

HttpClientを使用した HTTP 要求が行われると、クライアントが接続している URL に基づいて、サーバー名表示 (SNI) 拡張機能の値が自動的に選択されます。 拡張機能をより手動で制御する必要があるシナリオでは、次のいずれかの方法を使用できます。

ホスト ヘッダー

ホスト HTTP ヘッダーは、TLS の SNI 拡張機能と同様の機能を実行します。 これにより、ターゲット サーバーは、1 つの IP アドレスで複数のホスト名の要求を区別できます。 HttpClient は要求 URI に基づいてホストヘッダーを自動入力します。 ただし、その値を手動で設定することもできます。 HttpClient では、SNI 拡張機能の新しい値も使用されます。 この効果を実現するには、 HttpRequestMessage.Headers.Host または HttpClient.DefaultRequestHeaders.Host を使用できます。

using HttpClient client = new();

client.DefaultRequestHeaders.Host = "www.microsoft.com";

using var response = await client.GetAsync("https://127.0.0.1:5001/");

System.Console.WriteLine(response);

この方法では、ホスト名を使用して URL に接続するときに、SNI を完全に送信しないようにすることはできません。 ヘッダーが空の文字列に設定されている場合、 HttpClient は URL のホスト名を代わりに使用します。

Host ヘッダーをカスタマイズすると、サーバー証明書の検証に影響します。 既定では、クライアントはサーバー証明書がホスト ヘッダー内のホスト名と一致することを想定しています。

ConnectCallback を使用した SslStream の手動認証

より複雑ですが、より強力なオプションは、 SocketsHttpHandler.ConnectCallbackを使用することです。 .NET 7 以降では、認証された SslStream を返し、TLS 接続の確立方法をカスタマイズすることができます。 コールバック内では、任意の SslClientAuthenticationOptions オプションを使用してクライアント側認証を実行できます。

var handler = new SocketsHttpHandler
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
        try
        {
            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);

            var sslStream = new SslStream(new NetworkStream(socket, ownsSocket: true));

            // When using HTTP/2, you must also keep in mind to set options like ApplicationProtocols
            await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions
            {
                TargetHost = context.DnsEndPoint.Host,

            }, cancellationToken);

            return sslStream;
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
};

using HttpClient client = new(handler);

using var response = await client.GetAsync("https://www.microsoft.com");

System.Console.WriteLine(response);