first
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user