所以你要做的是修改由生成的的证书。看起来微软可能会在.NET 5中添加这种功能,但在此期间,我们需要想办法立即实现它。
该解决方案适用于Named 和Typed 对象。
问题在于创建命名或类型化的,其中绑定到的证书集可以随时更新。问题在于我们只能为设置创建参数一次。之后,会一遍又一遍地重用这些设置。
所以,让我们首先看一下我们如何注入服务:
命名的HttpClient注入例程
services.AddTransient<IMyService, MyService>();
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient("MyCertBasedClient").
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
类型化的HttpClient注入程序
services.AddSingleton<ICertificateService, CertificateService>();
services.AddHttpClient<IMyService, MyService>().
ConfigurePrimaryHttpMessageHandler(sp =>
new CertBasedHttpClientHandler(
sp.GetRequiredService<ICertificateService>()));
我们将一个
ICertificateService
注入为单例,用于保存我们当前的证书并允许其他服务更改它。当使用Named
HttpClient
时,需要手动注入
IMyService
,而使用Typed
HttpClient
时,
IMyService
会自动注入。当
IHttpClientFactory
需要创建我们的
HttpClient
时,它会调用lambda表达式并生成一个扩展的
HttpClientHandler
,该Handler需要从我们的服务管道中获取
ICertificateService
作为构造函数参数。
下面是
ICertificateService
的源代码,该服务维护带有“id”的证书(这只是上次更新时间的时间戳)。
public interface ICertificateService
{
void UpdateCurrentCertificate(X509Certificate cert);
X509Certificate GetCurrentCertificate(out long certId);
bool HasCertificateChanged(long certId);
}
public sealed class CertificateService : ICertificateService
{
private readonly object _certLock = new object();
private X509Certificate _currentCert;
private long _certId;
private readonly Stopwatch _stopwatch = new Stopwatch();
public CertificateService()
{
_stopwatch.Start();
}
public bool HasCertificateChanged(long certId)
{
lock(_certLock)
{
return certId != _certId;
}
}
public X509Certificate GetCurrentCertificate(out long certId)
{
lock(_certLock)
{
certId = _certId;
return _currentCert;
}
}
public void UpdateCurrentCertificate(X509Certificate cert)
{
lock(_certLock)
{
_currentCert = cert;
_certId = _stopwatch.ElapsedTicks;
}
}
}
这个最后部分实现了一个自定义的HttpClientHandler
类。使用它,我们可以挂钩到客户端发出的所有HTTP请求。如果证书已更改,我们会在请求前进行替换。
CertBasedHttpClientHandler.cs
public sealed class CertBasedHttpClientHandler : HttpClientHandler
{
private readonly ICertificateService _certService;
private long _currentCertId;
public CertBasedHttpClientHandler(ICertificateService certificateService)
{
_certService = certificateService;
var cert = _certService.GetCurrentCertificate(out _currentCertId);
if(cert != null)
{
ClientCertificates.Add(cert);
}
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if(_certService.HasCertificateChanged(_currentCertId))
{
ClientCertificates.Clear();
var cert = _certService.GetCurrentCertificate(out _currentCertId);
if(cert != null)
{
ClientCertificates.Add(cert);
}
}
return base.SendAsync(request, cancellationToken);
}
}
我认为这样做的最大缺点是,如果HttpClient
正在另一个线程上进行请求,我们可能会遇到竞态条件。你可以通过在SendAsync
中使用SemaphoreSlim
或任何其他异步线程同步模式来缓解这个问题,但这可能会导致瓶颈,所以我没有尝试这样做。如果您想看到添加这个功能,我会更新这个答案。
handler.ClientCertificates.Add(cert);
上设置了断点,每次执行这个 lambda 表达式时cert
都是有效的吗? - Andy