Docker部署IdentityServer4报IDX20803连接拒绝配置获取失败错误
问题背景
- 正在学习IdentityServer4,此前已开发完成包含API、客户端、IdentityServer4、API网关的简易测试项目,近期尝试将项目迁移至Docker容器环境部署。
- 预期流程:客户端应用启动后自动跳转至IdentityServer登录页面
- 实际现象:运行时抛出连接拒绝异常,无法完成跳转
错误信息
SocketException: Connection refused System.Net.Sockets.Socket+AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken) HttpRequestException: Connection refused (identityserver4:9001) System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(string host, int port, HttpRequestMessage initialRequest, bool async, CancellationToken cancellationToken) IOException: IDX20804: Unable to retrieve document from: 'http://identityserver4:9001/.well-known/openid-configuration'. Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(string address, CancellationToken cancel) InvalidOperationException: IDX20803: Unable to obtain configuration from: 'http://identityserver4:9001/.well-known/openid-configuration'. Microsoft.IdentityModel.Protocols.ConfigurationManager<T>.GetConfigurationAsync(CancellationToken cancel)
现有核心配置
1. IdentityServer中Config.cs的客户端配置
new Client { ClientId = "razorClient", ClientName = "RAZOR Client App", AllowedGrantTypes= GrantTypes.Hybrid, RequirePkce = false, AllowRememberConsent = false, RedirectUris = new List<string>() { "http://clientapp:5001/signin-oidc" }, PostLogoutRedirectUris = new List<string>() { "http://clientapp:5001/signout-callback-oidc" }, ClientSecrets = new List<Secret> { new Secret("secret".Sha256()) }, AllowedScopes = new List<string>() { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "MYAPI" }, AllowAccessTokensViaBrowser = true }
2. IdentityServer服务Program.cs配置
using IdentityServer; var builder = WebApplication.CreateBuilder(args); Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; builder.Services.AddControllersWithViews(); builder.Services.AddIdentityServer() .AddInMemoryClients(Config.Clients) .AddInMemoryIdentityResources(Config.IdentityResources) //.AddInMemoryApiResources(Config.ApiResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddTestUsers(Config.TestUsers) .AddDeveloperSigningCredential(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); } app.UseStaticFiles(); app.UseRouting(); app.UseIdentityServer(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); }); app.Run();
3. 客户端应用Program.cs配置
using ClientApp.Data; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Mvc.Authorization; var builder = WebApplication.CreateBuilder(args); Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; // 向容器添加服务 builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddSingleton<WeatherForecastService>(); builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { options.Authority = "http://identityserver4:9001"; options.MetadataAddress = "http://identityserver4:9001/.well-known/openid-configuration"; //options.Authority = "http://localhost:9001"; //options.MetadataAddress = "http://localhost:9001/.well-known/openid-configuration"; options.RequireHttpsMetadata = false; options.ClientId = "razorClient"; options.ClientSecret = "secret"; options.ResponseType = "code id_token"; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("MYAPI"); options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; }); builder.Services.AddMvcCore(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }); var app = builder.Build(); // 配置HTTP请求管道 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHttpsRedirection(); // 默认HSTS值为30天,生产场景可根据需求调整 app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); app.Run();
4. docker-compose.override配置
version: '3.4' services: identityserver4: container_name: identityserver4 environment: - ASPNETCORE_ENVIRONMENT=Development ports: - "9001:80" clientapp: container_name: clientapp environment: - ASPNETCORE_ENVIRONMENT=Development ports: - "5001:80" depends_on: - identityserver4 apiresource: container_name: apiresource environment: - ASPNETCORE_ENVIRONMENT=Development ports: - "8001:80"
问题原因
核心是Docker网络端口映射规则混淆+内外网访问地址不统一:
- docker-compose中
ports字段配置格式为[宿主机端口]:[容器内部监听端口],当前配置9001:80代表仅宿主机的9001端口会映射到identityserver4容器的80端口。容器之间通过Docker内部网桥通信时,直接通过服务名访问,走容器内部监听端口,不会经过宿主机的端口映射。当前客户端容器内配置的IdentityServer地址是http://identityserver4:9001,但identityserver4容器内部实际只监听80端口,9001端口无服务监听,直接触发连接拒绝错误,这是当前报错的直接原因。 - 即使修复容器内访问的端口问题,默认配置下还会遇到浏览器无法解析容器服务名、回调地址不匹配的问题:用户通过宿主机浏览器访问服务时,浏览器无法识别
identityserver4、clientapp这类Docker内部服务域名,同时IdentityServer返回的跳转地址、元数据签发者默认使用容器内地址,会导致跳转失败、令牌验证不通过。
修复方案
两种方案二选一即可,推荐开发环境使用方案一,配置成本最低。
方案一:统一端口配置(开发环境推荐)
通过统一容器内监听端口和宿主机映射端口,配合本地hosts配置,实现容器内、宿主机访问地址完全一致,不需要额外编写跳转逻辑:
- 修改docker-compose.override.yml,将三个容器的内部监听端口和宿主机映射端口设为一致,通过
ASPNETCORE_URLS指定容器内监听端口:
version: '3.4' services: identityserver4: container_name: identityserver4 environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:9001 ports: - "9001:9001" clientapp: container_name: clientapp environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:5001 ports: - "5001:5001" depends_on: - identityserver4 apiresource: container_name: apiresource environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:8001 ports: - "8001:8001"
- 修改宿主机hosts文件(Windows路径为
C:\Windows\System32\drivers\etc\hosts,Linux/macOS路径为/etc/hosts),添加以下条目,让宿主机可以将Docker服务名解析到本地:
127.0.0.1 identityserver4 127.0.0.1 clientapp 127.0.0.1 apiresource
- 现有代码中的客户端OIDC配置、IdentityServer客户端回调地址不需要修改,重新构建镜像启动容器,直接在浏览器访问
http://clientapp:5001即可正常跳转登录。
方案二:分离内外网访问地址(无需修改hosts)
如果不想修改本地hosts,可以将后端服务间通信地址和前端浏览器访问地址分开配置:
- 保持原有docker-compose的端口映射不变,给IdentityServer配置固定的签发者地址(即浏览器能访问的宿主机地址),修改IdentityServer的Program.cs,添加IssuerUri配置:
builder.Services.AddIdentityServer(options => { options.IssuerUri = "http://localhost:9001"; }) .AddInMemoryClients(Config.Clients) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) .AddTestUsers(Config.TestUsers) .AddDeveloperSigningCredential();
- 修改客户端Program.cs的OIDC配置,将后端访问IdentityServer的地址改为容器内可访问的80端口,同时添加跳转事件,将给浏览器的跳转地址改为宿主机可访问的9001端口:
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { // 后端服务间通信走容器内网络,使用内部80端口 options.Authority = "http://identityserver4"; options.MetadataAddress = "http://identityserver4/.well-known/openid-configuration"; options.RequireHttpsMetadata = false; options.ClientId = "razorClient"; options.ClientSecret = "secret"; options.ResponseType = "code id_token"; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("MYAPI"); options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; // 修改跳转逻辑,给浏览器返回宿主机可访问的IdentityServer地址 options.Events.OnRedirectToIdentityProvider = context => { context.ProtocolMessage.IssuerAddress = context.ProtocolMessage.IssuerAddress.Replace("http://identityserver4", "http://localhost:9001"); return Task.CompletedTask; }; });
- 修改IdentityServer项目Config.cs中的客户端回调、登出回调地址为浏览器可访问的宿主机地址:
RedirectUris = new List<string>() { "http://localhost:5001/signin-oidc" }, PostLogoutRedirectUris = new List<string>() { "http://localhost:5001/signout-callback-oidc" },
- 重新构建镜像启动容器,在浏览器访问
http://localhost:5001即可正常跳转登录。
内容的提问来源于stack exchange,提问作者Rouzbeh Zarandi
相关产品推荐
相关产品推荐

