78 lines
2.6 KiB
C#
78 lines
2.6 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using Csob.Configuration;
|
|
|
|
namespace Csob.Client;
|
|
|
|
/// <summary>
|
|
/// Provides <see cref="HttpClient"/> instances configured for mutual TLS with a per-request eIDAS
|
|
/// client certificate. ČSOB requires the client certificate at the transport layer, so a single
|
|
/// shared client cannot be used across tenants.
|
|
///
|
|
/// Clients are cached by certificate thumbprint and reused for connection pooling. In practice the
|
|
/// certificate identifies the TPP application (not the PSU), so the number of distinct certificates
|
|
/// is small and bounded by the set of calling tenants. Certificates and clients live in memory only
|
|
/// and never touch disk.
|
|
/// </summary>
|
|
public sealed class CsobHttpClientProvider : IDisposable
|
|
{
|
|
private readonly CsobSettings _settings;
|
|
private readonly ConcurrentDictionary<string, HttpClient> _clients = new();
|
|
private readonly object _buildLock = new();
|
|
|
|
public CsobHttpClientProvider(CsobSettings settings) => _settings = settings;
|
|
|
|
/// <summary>
|
|
/// Returns a (cached) mutual-TLS <see cref="HttpClient"/> presenting <paramref name="certificate"/>.
|
|
/// A fresh certificate instance is built per request; if an equivalent one (same thumbprint) is
|
|
/// already cached, the redundant instance is disposed so it does not leak.
|
|
/// </summary>
|
|
public HttpClient GetClient(X509Certificate2 certificate)
|
|
{
|
|
var thumbprint = certificate.Thumbprint;
|
|
|
|
if (_clients.TryGetValue(thumbprint, out var existing))
|
|
{
|
|
certificate.Dispose();
|
|
return existing;
|
|
}
|
|
|
|
lock (_buildLock)
|
|
{
|
|
if (_clients.TryGetValue(thumbprint, out existing))
|
|
{
|
|
certificate.Dispose();
|
|
return existing;
|
|
}
|
|
|
|
var client = BuildClient(certificate);
|
|
_clients[thumbprint] = client;
|
|
return client;
|
|
}
|
|
}
|
|
|
|
private HttpClient BuildClient(X509Certificate2 certificate)
|
|
{
|
|
var handler = new SocketsHttpHandler
|
|
{
|
|
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
|
|
};
|
|
handler.SslOptions.ClientCertificates = new X509CertificateCollection { certificate };
|
|
|
|
return new HttpClient(handler, disposeHandler: true)
|
|
{
|
|
Timeout = TimeSpan.FromSeconds(_settings.RequestTimeoutSeconds),
|
|
};
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (var client in _clients.Values)
|
|
{
|
|
client.Dispose();
|
|
}
|
|
|
|
_clients.Clear();
|
|
}
|
|
}
|