You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker中.NET 8与OpenSSL的SSL握手错误(SSL_ERROR_SSL)问询

Docker中的SSL握手错误:SSL_ERROR_SSL(.NET 8与OpenSSL环境)

问题描述

在Windows + Visual Studio环境运行.NET 8应用一切正常,但部署到Docker容器后,使用客户端证书发起HTTPS请求时出现错误:

The SSL connection could not be established, see inner exception.

已尝试在Dockerfile中通过update-ca-certificates手动添加证书,但问题仍未解决,需要排查。

Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER root
RUN apt-get update && apt-get install -y ca-certificates

COPY ["InsuranceApi/sosMedecin.crt", "/usr/local/share/ca-certificates/sosMedecin.crt"]
RUN update-ca-certificates

USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

COPY ["InsuranceApi/InsuranceApi.csproj", "InsuranceApi/"]
RUN dotnet restore "./InsuranceApi/InsuranceApi.csproj"
COPY . .
WORKDIR "/src/InsuranceApi"
RUN dotnet build "./InsuranceApi.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./InsuranceApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app

COPY --from=publish /app/publish .
COPY ["InsuranceApi/sosMedecin.pfx", "/app/publish/sosMedecin.pfx"]

ENTRYPOINT ["dotnet", "InsuranceApi.dll"]

AuthenticationService.cs

public class AuthenticationService
{
    private readonly CertificateOptions _certificateOptions;
    private readonly SsoOptions _ssoOptions;

    public AuthenticationService(IOptions<CertificateOptions> certificateOptions, IOptions<SsoOptions> ssoOptions)
    {
        _certificateOptions = certificateOptions.Value;
        _ssoOptions = ssoOptions.Value;
    }

    public async Task<string?> GetSsoToken()
    {
        string? ssoToken = null;
        HttpClientHandler handler = new HttpClientHandler();

        try
        {
            // Adding client certificate to HttpClientHandler
            handler.ClientCertificates.Add(new X509Certificate2(_certificateOptions.CertificatePath, _certificateOptions.CertificatePassword));

            // Making the HTTP request to get the SSO token
            using (var client = new HttpClient(handler))
            {
                var content = new FormUrlEncodedContent(new[]
                {
                    new KeyValuePair<string, string>("format", "text"),
                    new KeyValuePair<string, string>("username", _ssoOptions.Username),
                    new KeyValuePair<string, string>("password", _ssoOptions.Password),
                    new KeyValuePair<string, string>("submit", "confirm")
                });

                HttpResponseMessage response = await client.PostAsync(_ssoOptions.SsoTokenUrl, content);

                // If response contains the SSO token cookie
                if (response.Headers.Contains("Set-Cookie"))
                {
                    var cookieHeader = response.Headers
                        .GetValues("Set-Cookie")
                        .FirstOrDefault(c => c.StartsWith("SSOV2"));

                    if (!string.IsNullOrEmpty(cookieHeader))
                    {
                        ssoToken = cookieHeader.Split(';')[0].Split('=')[1];
                    }
                    else
                    {
                        throw new ArgumentNullException("Cookie with SSO token was not found.");
                    }
                }
                else
                {
                    throw new ArgumentNullException($"SSO token not found in response.\nResponse: {await response.Content.ReadAsStringAsync()}");
                }
            }
        }
        catch (Exception ex)
        {
            // Log any errors and inner exceptions
            Console.WriteLine($"Unexpected error: {ex.Message}");
            while (ex.InnerException != null)
            {
                Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
                ex = ex.InnerException;
            }
            throw;
        }

        return ssoToken;
    }
}

排查方向

  • 客户端证书加载验证

    1. 在Dockerfile的final阶段添加RUN ls -l /app/publish/,确认sosMedecin.pfx存在且$APP_UID用户有读取权限。
    2. 修改证书加载代码,添加Linux环境兼容的存储标志:
      handler.ClientCertificates.Add(new X509Certificate2(
          _certificateOptions.CertificatePath, 
          _certificateOptions.CertificatePassword,
          X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable));
      
    3. 检查PFX证书是否包含完整证书链,可通过openssl pkcs12 -in sosMedecin.pfx -info验证。
  • CA证书信任校验

    1. 确认sosMedecin.crt是PEM格式,执行openssl x509 -in /usr/local/share/ca-certificates/sosMedecin.crt -text验证格式正确性。
    2. 检查/etc/ssl/certs/目录下是否生成了对应证书的哈希链接文件,确认update-ca-certificates执行生效。
  • OpenSSL与.NET兼容性

    1. 在容器内执行openssl version,确保版本为1.1.1或更高(.NET 8推荐版本)。
    2. 添加环境变量DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0,强制使用旧的HttpClientHandler排查兼容性问题。
  • SSL握手日志分析

    1. 设置环境变量DOTNET_TRACE=1开启调试日志,捕获更详细的InnerException信息,明确是证书信任、客户端证书验证还是协议版本问题。
    2. 代码中指定TLS版本:ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;。

内容的提问来源于stack exchange,提问作者Markiian Hoinets

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 14:47:32