有时候我们的网站需要根据不同的域名,展示不同的内容,同时域名还得是https协议。在不使用nginx类似代理的情况下,如何动态的响应不同的SSL证书。
需要解决的问题
1.域名证书及需要定制化内容的配置(OEM)
2.程序启动时,加载域名证书
3.请求时,根据域名做出动态响应
关键代码
1.程序启动后,需要在后台任务中读取域名证书的配置加载到内存中
[JobDetail("job_loadhttpscerts", Description = "加载https证书", GroupName = "default", Concurrent = false)]
[PeriodSeconds(1, TriggerId = "trigger_loadhttpscerts", Description = "加载https证书", MaxNumberOfRuns = 1, RunOnStart = true)]
public class LoadHttpsCerts : IJob
{
private readonly IServiceScopeFactory _scopeFactory;
public LoadHttpsCerts(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
var wwwroot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot");
using var serviceScope = _scopeFactory.CreateScope();
// 在库中查询出域名和证书配置
var platConfig = serviceScope.ServiceProvider.GetService<SqlSugarRepository<PlatformConfig>>();
var list = await platConfig.CopyNew().AsQueryable().ToListAsync();
foreach (var input in list)
{
if (!string.IsNullOrEmpty(input.WebUrl) && !string.IsNullOrEmpty(input.SslPwd) && !string.IsNullOrEmpty(input.SslUrl))
{
try
{
// 去除域名
var uri = new Uri(input.SslUrl);
var path = uri.LocalPath;
path = path[1..];
var sslPath = Path.Combine(wwwroot, path);
if (File.Exists(sslPath))
{
var bytearry = File.ReadAllBytes(sslPath);
X509Certificate2 x509Certificate2 = new X509Certificate2(bytearry, input.SslPwd);
UtilCertDic.SetCertDic(input.WebUrl, x509Certificate2);
Console.WriteLine($"{input.SslUrl}证书加载成功,{sslPath}");
}
else
{
Console.WriteLine($"{input.SslUrl}证书找不到,{sslPath}");
}
}
catch (Exception ex)
{
Console.WriteLine($"{input.SslUrl}证书加载失败,{ex.Message} {ex.StackTrace}");
}
}
}
}
}
其中UtilCertDic的定义如下
public class UtilCertDic
{
private static Dictionary<string, X509Certificate2> UtilDic = new Dictionary<string, X509Certificate2>();
private static object lockdic = new object();
public static bool GetCertDic(string key, out X509Certificate2 certificate2)
{
return UtilDic.TryGetValue(key, out certificate2);
}
/// <summary>
/// 新增
/// </summary>
/// <param name="key"></param>
/// <param name="x509Certificate2"></param>
/// <param name="delCert"></param>
public static void SetCertDic(string key, X509Certificate2 x509Certificate2, string delCert = null)
{
lock (lockdic)
{
if (delCert != null)
{
UtilDic.Remove(delCert);
}
if (UtilDic.ContainsKey(key))
{
UtilDic.Remove(key);
}
UtilDic.Add(key, x509Certificate2);
}
}
}
2.请求时根据域名做出动态响应
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ListenAnyIP(443, listenOptions =>
{
string rootUrl = AppDomain.CurrentDomain.BaseDirectory + Path.DirectorySeparatorChar + "ssl";
X509Certificate2 defaultmodel = new X509Certificate2(rootUrl + Path.DirectorySeparatorChar + "xxxx.pfx", "Aa123456");
listenOptions.UseHttps(httpsOptions =>
{
httpsOptions.ServerCertificateSelector = (connectionContext, name) =>
{
if (name is not null && UtilCertDic.GetCertDic(name, out var cert))
{
return cert;
}
return defaultmodel;
};
});
});
});
下一步,实现免费证书自动续期