【问题标题】:NET5 JWT Bearer Authentication not recognizing SSL certificateNET5 JWT 不记名身份验证不识别 SSL 证书
【发布时间】:2021-12-04 22:03:36
【问题描述】:

我有一个使用 Grpc 的 .NET 5 WebApi 和一个在 YARP 反向代理后面运行的 IdentityServer4。反向代理正在使用有效的 Let's Encrypt 证书,并将请求路由到正在侦听 localhost:port 并使用自签名证书的另外两个。它们在 Linux Mint 20.1 上运行,我使用 OpenSSL 创建了自签名证书并将其添加到 /usr/local/share/ca-certificates/extra 并运行 update-ca-certificates 以更新证书存储。

一切运行良好,YARP 识别用于路由请求的 sefl 签名证书,但向需要授权的 WebApi 的请求抛出此异常:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HMCHFQCVF5UE", Request id "0HMCHFQCVF5UE:0000000B": An unhandled exception was thrown by the application.
      System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'System.String'.
       ---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'System.String'.
       ---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
       ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: NotTimeValid
         at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception)
         at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
         at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
         --- End of inner exception stack trace ---
         at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
         at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
         at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
         --- End of inner exception stack trace ---
         at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
         at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(String address, IDocumentRetriever retriever, CancellationToken cancel)
         at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
         --- End of inner exception stack trace ---
         at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
         at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
         at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
         at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
         at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Grpc.AspNetCore.Web.Internal.GrpcWebMiddleware.HandleGrpcWebRequest(HttpContext httpContext, ServerGrpcWebMode mode)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi)
         at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

我正在使用 Microsoft.AspNetCore.Authentication.JwtBear 5.0.11,这似乎就是它的来源。该错误似乎表明证书上的时间有问题,但使用 OpenSSL 检查非早于和非晚于日期都可以。 这是它在启动时的设置方式:

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.Authority = appSettings.Authorization.Authority;
                    options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
                    options.TokenValidationParameters.ValidateAudience = false;
                });

我尝试同时使用"https://localhost:port""https://real.hostname" 作为权威,结果是一样的。我也尝试过使用options.RequireHttpsMetadata = false;,但它没有效果,我也不想切换到普通的http。

我还尝试使用相同的证书在我的 Windows 机器上运行相同的设置,并且在那里一切正常。什么可能导致不同的行为,我如何才能获得有关它实际尝试验证的证书以及它如何确定错误的更多信息?

【问题讨论】:

  • 我在 github github.com/dotnet/runtime/issues/29409 上发现了这个问题,这似乎与您所拥有的类似,即使用 OpenSSL 制作的有效自签名证书上的证书过期。如问题中所述,您可以尝试在 OpenSSL 中将 keyUsage 设置为 keyCertSign。希望对您有所帮助。

标签: c# ssl .net-5


【解决方案1】:

我设法找到了原因并解决了它。

原因

  1. Microsoft.AspNetCore.Authentication.JwtBearer 实际上对 IdentityServer4 的调用不是 1 次,而是 2 次:一次调用 /.well-known/openid-configuration 以获取配置,然后调用上一个响应的 jwks_uri 返回的端点。第一次调用是使用自签名证书调用 localhost:port 端点并正常工作,但第二次调用是使用 Let's Encrypt 证书调用 realdomain 端点并因 OP 中的错误而失败。
  2. Let's Encrypt 证书链中有一个过期的证书:它使用 DST Root CA X3 而不是 ISRG Root X1(更多信息:https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/

修复使用不同域的调用

选项 1 - 在 IdentityServer4 中修复它

这可以通过更改 Startup.cs 中的 IdentityServer4 原点来实现:

        app.Use(async (ctx, next) =>
        {
            ctx.SetIdentityServerOrigin("origin");
            await next();
        });

选项 2 - 在 API jwt 授权配置中修复它

这需要实现您自己的配置管理器:

public class CustomConfigurationManager : IConfigurationManager<OpenIdConnectConfiguration>
{
    private readonly string authority;
    private readonly string authorityReturnedOrigin;

    public CustomConfigurationManager(string authority, string authorityReturnedOrigin)
    {
        this.authority = authority;
        this.authorityReturnedOrigin = authorityReturnedOrigin;
    }
    public async Task<OpenIdConnectConfiguration> GetConfigurationAsync(CancellationToken cancel)
    {
        var httpClient = new HttpClient();

        var request = new HttpRequestMessage
        {
            RequestUri = new Uri($"{authority}/.well-known/openid-configuration"),
            Method = HttpMethod.Get
        };

        var configurationResult = await httpClient.SendAsync(request, cancel);
        var resultContent = await configurationResult.Content.ReadAsStringAsync(cancel);
        if (configurationResult.IsSuccessStatusCode)
        {
            var config = OpenIdConnectConfiguration.Create(resultContent);
            var jwks = config.JwksUri.Replace(authorityReturnedOrigin, authority);
            var keyRequest = new HttpRequestMessage
            {
                RequestUri = new Uri(jwks),
                Method = HttpMethod.Get
            };
            var keysResposne = await httpClient.SendAsync(keyRequest, cancel);
            var keysResultContent = await keysResposne.Content.ReadAsStringAsync(cancel);
            if (keysResposne.IsSuccessStatusCode)
            {
                config.JsonWebKeySet = new JsonWebKeySet(keysResultContent);
                var signingKeys = config.JsonWebKeySet.GetSigningKeys();
                foreach (var key in signingKeys)
                {
                    config.SigningKeys.Add(key);
                }
            }
            else
            {
                throw new Exception($"Failed to get jwks: {keysResposne.StatusCode}: {keysResultContent}");
            }

            return config;
        }
        else
        {
            throw new Exception($"Failed to get configuration: {configurationResult.StatusCode}: {resultContent}");
        }
    }

    public void RequestRefresh()
    {
        // if you are caching the configuration this is probably where you should invalidate it
    }
}

然后替换Startup.cs中的默认配置管理器:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Authority = appSettings.Authorization.Authority;
            options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
            options.TokenValidationParameters.ValidateAudience = false;
            options.ConfigurationManager = new CustomConfigurationManager("authority", "authorityReturnedOrigin");
        }

修复过期证书链

这适用于 Linux Mint 20.1,其他发行版可能有不同的处理方式。还建议备份。

  1. 确保 openssl 版本高于 1.0.1 或 1.0.2,因为它们可能存在问题(运行 openssl version 进行检查)
  2. 运行 ls -l | grep ISRG 并删除您找到的内容 - 这一步可能没有必要,但我就是这样做的
  3. 转到/etc/ssl/certs
  4. 运行 ls -l | grep DST 并删除您找到的内容
  5. 下载这些文件:isrgrootx1isrgrootx2letsencrytptr3
  6. 将它们复制到/etc/ssl/certs 请注意,运行 update-ca-certificate 之类的证书更新命令可能会恢复此状态。

对此可能有更好的解决方案,也许只是更新到较新的操作系统版本就可以解决它。此问题仅适用于从 Linux Mint 20.1 上运行的代码对 API 进行的 http 调用,在 Windows 上一切正常,通过浏览器访问并没有显示任何 SSL 证书错误。

【讨论】:

  • 在这一行options.ConfigurationManager = new CustomConfigurationManager("authority", "authorityReturnedOrigin"); 中,参数似乎是占位符字符串。实际参数应该是多少?
  • 在 GetConfigurationAsync 方法中,我这样做是为了允许不安全的证书 ``` lang-csharp var handler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; var httpClient = new HttpClient(handler); ```
  • @BoChristianSkjøtt authority 将是身份服务器的主机 url,autorityReturnedOrigin 将是第一次调用 .well-known/openid-configuration 在配置中返回的主机 url。在可能的情况下,它会是https://localhost:42001(权威)和https://realdomain.com(autorityReturnedOrigin)。这使得两个调用都转到 https://localhost:42001 并使用自签名证书而不是 Let's Encrypt 证书。
猜你喜欢
  • 2016-02-18
  • 2013-08-04
  • 1970-01-01
  • 2019-01-03
  • 1970-01-01
  • 2017-03-21
  • 1970-01-01
  • 2022-07-14
  • 2018-05-01
相关资源
最近更新 更多