我有一个使用 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 识别用于路由请求的自签名证书,但对需要授权的 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 计算机上运行相同的设置,并且一切正常。什么可能导致不同的行为?我如何获得有关它实际尝试验证的证书以及它如何确定错误的更多信息?
最佳答案
我设法找到原因并解决它。
原因
Microsoft.AspNetCore.Authentication.JwtBearer
实际上对 IdentityServer4 进行了 2 次而非 1 次调用:其中一次调用/.well-known/openid-configuration
来获取配置并然后调用上一个响应的 jwks_uri 返回的端点。第一个调用是使用自签名证书对localhost:port
端点进行调用,并且工作正常,但第二个调用是使用 Let's Encrypt 证书对realdomain
端点进行调用,但失败并出现错误在OP中。- 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,其他发行版可能有不同的处理方式。还建议进行备份。
- 确保 openssl 版本高于 1.0.1 或 1.0.2,因为这些版本可能存在问题(运行
openssl version
进行检查) - 运行
ls -l | grep ISRG
并删除您找到的内容 - 这一步可能不是必需的,但这就是我所做的 - 转到
/etc/ssl/certs
- 运行
ls -l | grep DST
并删除找到的内容 - 下载这些文件:isrgrootx1 isrgrootx2 letsencrytptr3
- 将它们复制到
/etc/ssl/certs
请注意,运行update-ca-certificate
等证书更新命令可能会恢复此状态。
对此可能有更好的解决方案,也许只需更新到较新的操作系统版本就可以修复它。此问题仅出现在 Linux Mint 20.1 上运行的代码对 API 进行 http 调用时,在 Windows 上一切正常,并且通过浏览器访问没有显示任何 SSL 证书错误。
关于c# - NET5 JWT 承载身份验证无法识别 SSL 证书,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69606080/