.NET 6 Docker Web API部分端点SSL连接失败问题排查
原始问题
在Windows主机上开发面向Linux的.NET 6 Web API Docker容器,本地通过dotnet run或发布生产版本运行时,所有端点均可正常访问。但部署到Docker容器后,仅/api/weatherforecast端点可访问,其余两个端点/api/users?userId=1、/api/cart?userId=1&cartId=1均出现Error: The SSL connection could not be established, see inner exception.错误,且无法找到内部异常信息。
已完成的开发证书配置步骤:
- 执行
dotnet dev-certs https --clean,通过certmgr.exe确认并删除旧证书; - 执行
dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p {password here}生成新证书; - 执行
dotnet dev-certs https --trust信任证书。
Dockerfile内容:
FROM myprivaterepo/dotnet/sdk:6.0.202-bullseye-slim-amd64 AS build WORKDIR /app COPY *.csproj . RUN dotnet restore COPY . . RUN dotnet publish -c Release -o out FROM myprivaterepo/dotnet/aspnet:6.0.4-bullseye-slim-amd64 WORKDIR /app EXPOSE 80 EXPOSE 443 EXPOSE 5000 EXPOSE 5001 ENV ASPNETCORE_URLS=https://+:5001;http://+:5000 COPY --from=build /app/out . ENTRYPOINT ["dotnet", "mantis-server.dll"]
构建命令:docker build -t my-app .
运行命令:
docker run --rm -it -p 5001:5001 -e ASPNETCORE_HTTPS_PORT=5001 -e ASPNETCORE_Kestrel__Certificates__Default__Password="password here" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx -e ASPNETCORE_ENVIRONMENT=Development -v ~/.aspnet/https:/https:ro -v ~/devsecrets:/.microsoft/usersecrets my-app
Insomnia中显示* Re-using existing connection! (#10) with host localhost,不确定其含义。
更新信息
添加以下代码到API控制器后请求可正常执行,推测是证书问题:
[ApiController] [Route("api/[controller]")] public class MyController : ControllerBase { private HttpClientHandler handler = new HttpClientHandler(); private static HttpClient _httpClient; public MyController() { handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; _httpClient = new HttpClient(handler); } ... }
但希望移除对HttpClientHandler.DangerousAcceptAnyServerCertificateValidator的依赖,恢复正常的自签名证书验证流程。
原因分析
本地运行时,Windows系统已经通过dotnet dev-certs https --trust将自签名证书加入了系统信任根,所以HttpClient调用外部HTTPS服务时会信任该证书。但Docker容器内的Linux系统默认没有这个信任记录,导致容器内的HttpClient无法验证自签名证书的合法性,从而抛出SSL连接错误。
DangerousAcceptAnyServerCertificateValidator是跳过所有证书验证的危险操作,虽然能解决问题,但会带来安全风险,必须替换为合法的验证方式。
解决方案
1. 将自签名证书导入Docker容器的系统信任存储
针对Debian/Ubuntu系的Linux容器(bullseye-slim属于Debian),可以在Dockerfile中添加步骤,将自签名证书导入系统信任根:
FROM myprivaterepo/dotnet/sdk:6.0.202-bullseye-slim-amd64 AS build WORKDIR /app COPY *.csproj . RUN dotnet restore COPY . . RUN dotnet publish -c Release -o out FROM myprivaterepo/dotnet/aspnet:6.0.4-bullseye-slim-amd64 WORKDIR /app EXPOSE 80 EXPOSE 443 EXPOSE 5000 EXPOSE 5001 # 安装证书工具并导入自签名证书 RUN apt-get update && apt-get install -y --no-install-recommends openssl ca-certificates \ # 从PFX导出PEM格式证书(替换{password here}为你的证书密码) && openssl pkcs12 -in /https/aspnetapp.pfx -clcerts -nokeys -out /usr/local/share/ca-certificates/aspnetapp.crt -passin pass:"{password here}" \ # 更新系统信任证书 && update-ca-certificates \ # 清理安装包,减小镜像体积 && apt-get clean && rm -rf /var/lib/apt/lists/* ENV ASPNETCORE_URLS=https://+:5001;http://+:5000 COPY --from=build /app/out . ENTRYPOINT ["dotnet", "mantis-server.dll"]
注意:运行容器时要确保/https/aspnetapp.pfx路径已通过挂载卷正确映射。
2. 在HttpClient中仅信任指定的自签名证书
不跳过所有验证,只信任我们的自签名证书,更安全:
[ApiController] [Route("api/[controller]")] public class MyController : ControllerBase { private static HttpClient _httpClient; public MyController() { var handler = new HttpClientHandler(); // 加载自签名证书(路径对应容器内的挂载路径) using var trustedCert = new X509Certificate2("/https/aspnetapp.pfx", "{password here}"); handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { // 如果没有SSL错误,直接通过 if (sslPolicyErrors == SslPolicyErrors.None) return true; // 验证服务器证书是否与我们信任的证书一致 return cert.Thumbprint.Equals(trustedCert.Thumbprint, StringComparison.OrdinalIgnoreCase); }; _httpClient = new HttpClient(handler); } // ...控制器其他代码 }
也可以预先获取证书的指纹(本地运行dotnet dev-certs https --check --trust查看,或用openssl x509 -in aspnetapp.crt -noout -fingerprint命令),直接对比指纹:
var trustedThumbprint = "ABC123..."; // 替换为你的证书指纹 handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { if (sslPolicyErrors == SslPolicyErrors.None) return true; return cert.Thumbprint.Equals(trustedThumbprint, StringComparison.OrdinalIgnoreCase); };
3. 开发环境临时使用HTTP(仅测试用)
如果只是开发阶段测试,可以暂时将外部服务的调用改为HTTP,避免SSL验证问题,但生产环境必须使用HTTPS。
内容的提问来源于stack exchange,提问作者usagibear

