AbpVnext 阿里云ssl证书多个生产环境自动更新

一个站点对应多个域名,所以要有多个证书

打开Host下面Program.cs,加入代码,如下图

Kestrel 的证书支持热更新机制,新的证书文件覆盖后会自动更新

cs 复制代码
 //下面代码作用是证书文件更新后,自动更新证书,不需要重启应用
 builder.WebHost.ConfigureKestrel(serverOptions =>
 {
     serverOptions.ConfigureHttpsDefaults(httpsOptions =>
     {
         httpsOptions.ServerCertificateSelector = (connectionContext, name) =>
         {
             return name?.ToLower() switch
             {
                 "域名1.com" or "www.域名1.com" => new X509Certificate2("证书1.pfx", "密码"),
                 "域名2.net" or "www.域名2.net" => new X509Certificate2("证书2.pfx", "密码"),
                 _ => throw new NotSupportedException($"No certificate available for {name}")
             };
         };
     });
 });

如果你只有一个域名,代码如下:

cs 复制代码
 httpsOptions.ServerCertificateSelector = (ctx, host) =>
    {
        // 可用代码动态读取最新的证书
        return new X509Certificate2("/path/to/your/cert.pfx", "your_cert_password");
    };

自动更新算法

1、程序启动时判断证书过期,程序启动后每天定时检查证书过期

2、过期后从阿里云自动下载文件覆盖本地文件即可。

自动下载证书参考我上次的文章,参考地址如下

net9阿里云自动申请ssl证书,下载证书pfx格式-CSDN博客

判断证书是否过期代码

cs 复制代码
 var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2("证书文件", "密码");
 DateTime expirationDate = DateTime.Parse(cert.GetExpirationDateString());
 if (expirationDate.AddDays(-1).Date <= DateTime.Now.Date)
 {
     //下载证书
 }

多个证书过期判断

cs 复制代码
 // 搜索所有.pfx文件
 string[] pfxFiles = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.pfx", SearchOption.AllDirectories);
 foreach (var filePath in pfxFiles)
 {
     var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(filePath, "密码");
     DateTime expirationDate = DateTime.Parse(cert.GetExpirationDateString());
     if (expirationDate.AddDays(-1).Date <= DateTime.Now.Date)
     {
         //下载证书
     }
 }

完整的代码如下:

接口IOpenSSLAppService,目的是为了兼容其它云平台域名证书

cs 复制代码
/// <summary>
/// 检查证书是否过期
/// </summary>
/// <returns></returns>
public async Task IsCertificateExpiredAsync()
{
    // 搜索所有.pfx文件
    string[] pfxFiles = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.pfx", SearchOption.AllDirectories);
    string password = _configuration["Aliyun:password"];
    foreach (var filePath in pfxFiles)
    {
        var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(filePath, password);
        DateTime expirationDate = DateTime.Parse(cert.GetExpirationDateString());
        if (expirationDate.AddDays(-1).Date <= DateTime.Now.Date)
        {
           //更新证书
            await GetPfxAsync(filePath.GetFileNameWithoutExtension(), password);
        }
    }
}
/// <summary>
/// 获取域名证书
/// </summary>
/// <param name="domain"></param>
/// <param name="password"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task GetPfxAsync(string domain,string password)
{
    var akConfig = new Aliyun.Credentials.Models.Config
    {
        Type = "access_key",
        AccessKeyId = _configuration["Aliyun:accessKeyId"],
        AccessKeySecret = _configuration["Aliyun:secret"]
    };
    var akCredential = new Aliyun.Credentials.Client(akConfig);
    AlibabaCloud.OpenApiClient.Models.Config config = new AlibabaCloud.OpenApiClient.Models.Config
    {
        Credential = akCredential,
        RegionId = _configuration["Aliyun:regionId"]
    };
    AlibabaCloud.SDK.Cas20200407.Client client = new AlibabaCloud.SDK.Cas20200407.Client(config);

    try
    {
        var request = new CreateCertificateRequestRequest
        {
            Username = _configuration["Aliyun:mail"],
            Phone = _configuration["Aliyun:phone"],
            Email = _configuration["Aliyun:mail"],
            Domain = domain,
            ValidateType = "DNS"
        };

        var runtime = new RuntimeOptions();
        var certificateResponse = await client.CreateCertificateRequestAsync(request);
        DescribeCertificateStateResponse certificateStateResponse;
        if (certificateResponse.StatusCode == 200 && certificateResponse.Body != null)
        {
            var orderId = certificateResponse.Body.OrderId;
            certificateStateResponse = await client.DescribeCertificateStateAsync(new DescribeCertificateStateRequest { OrderId = orderId });
            while (certificateStateResponse.Body.PrivateKey == null)
            {
                Thread.Sleep(30000);//休息30秒
                certificateStateResponse = await client.DescribeCertificateStateAsync(new DescribeCertificateStateRequest { OrderId = orderId });
            }
            if (certificateStateResponse.Body.PrivateKey != null)
            {
                //保存证书
                string certPem = certificateStateResponse.Body.Certificate;      // 公钥证书 PEM 字符串
                string keyPem = certificateStateResponse.Body.PrivateKey; // 私钥 PEM 字符串                        
                CryptorHelper.PemToPfx(certPem, keyPem, $"{domain}.pfx", password);
            }
        }
    }
    catch (TeaException ex)
    {
        throw new Exception($"阿里云API错误: {ex.Message}", ex);
    }
}

添加程序启动更新代码

加入到hangfire定时任务,每天1点检查,定时更新

相关推荐
茶杯梦轩2 分钟前
从零起步学习RabbitMQ || 第二章:RabbitMQ 深入理解概念 Producer、Consumer、Exchange、Queue 与企业实战案例
服务器·后端·消息队列
YuMiao2 天前
gstatic连接问题导致Google Gemini / Studio页面乱码或图标缺失问题
服务器·网络协议
Sinclair5 天前
简单几步,安卓手机秒变服务器,安装 CMS 程序
android·服务器
Rockbean6 天前
用40行代码搭建自己的无服务器OCR
服务器·python·deepseek
茶杯梦轩6 天前
CompletableFuture 在 项目实战 中 创建异步任务 的核心优势及使用场景
服务器·后端·面试
海天鹰7 天前
【免费】PHP主机=域名+解析+主机
服务器
不是二师兄的八戒7 天前
Linux服务器挂载OSS存储的完整实践指南
linux·运维·服务器
芝士雪豹只抽瑞克五7 天前
Nginx 高性能Web服务器笔记
服务器·nginx
失重外太空啦7 天前
Tomcat
java·服务器·tomcat
Henry Zhu1237 天前
数据库:并发控制基本概念
服务器·数据库