我一直在实施 IdentityServer4 来为我的 React 应用程序提供授权。我在本地开发环境中使用它,但在部署到 Windows Server 2016 中的 IIS 后遇到问题。我能够通过 /connect/token 端点生成访问令牌,但是当我尝试使用访问受保护的 API 时令牌我得到以下异常:
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: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. (dev-drydata-auth.universal-compliance.com:443)
---> System.Net.Sockets.SocketException (10060): A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|283_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.DefaultConnectAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, 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 NSwag.AspNetCore.Middlewares.SwaggerUiIndexMiddleware.Invoke(HttpContext context)
at NSwag.AspNetCore.Middlewares.RedirectToIndexMiddleware.Invoke(HttpContext context)
at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)
at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
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.IIS.Core.IISHttpContextOfT`1.ProcessRequestAsync()
我的ConfigureServices如下:
public void ConfigureServices(IServiceCollection services)
{
ConfigureDryDataServices(services);
services.AddControllersWithViews();
services.AddCors(options =>
{
options.AddPolicy("AllOrigins",
builder =>
{
builder.AllowAnyMethod()
.AllowAnyHeader()
.AllowAnyOrigin();
});
});
services.AddScoped<IClaimsTransformation, WebAppCalimsTransformation>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = Configuration.GetValue<string>("AppSettings:Auth:ServerUrl");
o.Audience = Configuration.GetValue<string>("AppSettings:Auth:Audience");
o.RequireHttpsMetadata = false;
o.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
if (context.SecurityToken is JwtSecurityToken accessToken && context.Principal.Identity is ClaimsIdentity identity)
{
identity.AddClaim(new Claim("access_token", accessToken.RawData));
}
return Task.CompletedTask;
}
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiReader", policy => policy.RequireClaim("scope", "my_api_software"));
options.AddPolicy("admin", policy => policy.RequireClaim(ClaimTypes.Role, "admin"));
options.AddPolicy("user", policy => policy.RequireClaim(ClaimTypes.Role, "user"));
});
services.AddHttpClient("Auth", config =>
{
config.BaseAddress = new Uri(Configuration.GetValue<string>("AppSettings:Auth:ServerUrl"));
});
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
services.AddSwaggerDocument(config => {
config.OperationProcessors.Add(new OperationSecurityScopeProcessor("JWT token"));
config.AddSecurity("JWT token", new OpenApiSecurityScheme
{
Type = OpenApiSecuritySchemeType.ApiKey,
Name = "Authorization",
Description = "Copy 'Bearer ' + valid JWT token into field",
In = OpenApiSecurityApiKeyLocation.Header
});
config.PostProcess = (document) =>
{
document.Info.Version = "v1";
document.Info.Title = "My API API";
document.Info.Description = "ASP.NET 5.0 My API";
};
});
}
这在我的电脑中部署本地 IIS 时也能正常工作
问题是 API 无法从您的部署中访问您的 IdentityServer,如此处代码中所定义:
}).AddJwtBearer(o =>
{
o.Authority = Configuration
.GetValue<string>("AppSettings:Auth:ServerUrl");
因此,通过网络/DNS 确保 API 中的权限实际上可以从您的服务器内部访问。即使它们都可以从您的浏览器访问,也不意味着 API 可以从服务器端的本地网络访问您的 IdentityServer。
在我的例子中,SSL 阻止了本地访问
在航站楼
dotnet dev-certs https --trust
在我的案例中,微服务,UI 应用程序授权 URL 和 API 授权 URL 指向不同的。
只是发布这个以在将来某个时候拯救某人。问题是您使用的证书可能没有身份服务器或 Web 应用程序地址的 dns 条目。
您可以通过创建具有多个 dns 地址的证书来解决此问题。
如果您是像我这样的 visual studio 用户,您可以使用 New-SelfSignedCertificate
轻松创建证书如果像@john-hardcash 之前指出的那样,您需要为多个域创建自己的自签名证书,您可能会遇到以下问题:Visual Studio 仅使用使用 dev-certs 工具创建的证书。即使您尝试使用 dev-certs 工具导入您自己的多域证书,您也可能会收到以下错误,因为您的证书未标记为开发证书:
然后,一个可能的解决方案可能是遵循以下步骤:
Set-Location -Path "cert:\CurrentUser\My"
$OldCert = (Get-ChildItem -Path <oldCertificateThumbprint>)
New-SelfSignedCertificate -CloneCert @OldCert -DnsName "localhost","host.docker.internal" -FriendlyName "ASP.NET Core HTTPS development certificate"
使用 dev-certs clean 选项删除旧的开发证书。
打开 certmgr.msc 并通过将其移动到受信任的根证书颁发机构文件夹来信任您新克隆的证书。
将新的受信任证书导出为 PFX 文件。您需要为文件提供名称和密码,因此请将其写下来,因为之后会用到它。
使用此命令导入您的新证书,您需要提供您在之前步骤中使用的名称和密码(-v 选项将显示所有详细信息和潜在错误的其他信息):
dotnet dev-certs https --clean --import <myCertName>.pfx -p <myCertPwd> -v
现在,您可能需要重新启动 Visual Studio,但下次您再次运行应用程序时,它将使用新的多域证书。
在我的例子中,我在 appsettings.development.json 中使用了正确的 jwks url,但我忘了在 secrets.json.
中进行调整因此,当您的应用无法访问 /.well-known/openid-configuration 时,就会发生这种情况 身份服务器。在浏览器中自己尝试,它应该会失败。
在我的例子中,identityserver4 在 http 上运行,但我的其他应用程序试图在 https 上访问它。所以我在我的配置中将权限更改为“http”,它起作用了。
在我的情况下,我在 Azure App Services 中托管身份服务器
Web App下->配置->常规设置-> 传入客户端证书设置为“必需”
将其更改为“忽略”后,它可以正常工作。
注意:在浏览器中,.well-known/openid-configuration 有效,因为我接受了证书。但是,在邮递员中我得到了相同的 403。如果您在浏览器中取消证书请求,您也会得到 403。
我正在连接到由我们的 IT 托管的身份服务器。
我的问题是他们提供的端点不完全是我应该填写到 appsettings.json 中的 URL。
我改了之后错误解决
"OpenIDConnect": {
"Authority": "https://login-itg.external.companyName.com/as/authorization.oauth2",
"ClientId": "***",
"ClientSecret": "***"
}
到
"OpenIDConnect": {
"Authority": "https://login-itg.external.companyName.com",
"ClientId": "***",
"ClientSecret": "***"
}