.NET 6自托管Web API(Windows服务)跨机器访问超时问题
.NET 6自托管Web API跨机器访问问题排查
问题描述
基于.NET 6开发的自托管Web API部署为Windows服务后,本地通过域名+端口可正常访问,但其他机器无法访问该API。已在防火墙中配置对应端口的入站规则。
相关代码与配置
Program.cs代码
var webApplicationOptions = new WebApplicationOptions() { ContentRootPath = AppContext.BaseDirectory, Args = args, ApplicationName = System.Diagnostics.Process.GetCurrentProcess().ProcessName }; var builder = WebApplication.CreateBuilder(webApplicationOptions); builder.Host.UseWindowsService(); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml")); c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter a valid token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "Bearer" }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, new string[]{} } }); }); // generate lowercase URLs builder.Services.Configure<RouteOptions>(options => { options.LowercaseUrls = true; }); var configurationBuilder = new ConfigurationBuilder() .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true); var configuration = configurationBuilder.Build(); var loggerConfig = new LoggerConfiguration() .ReadFrom.Configuration(configuration); var logger = loggerConfig.CreateLogger(); var key = Encoding.ASCII.GetBytes(configuration["Jwt:Secret"]!); builder.Services.AddLogging(loggingBuilder => { loggingBuilder.ClearProviders(); loggingBuilder.AddSerilog(logger); }); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) // Adding Jwt Bearer .AddJwtBearer(options => { options.SaveToken = true; options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, ValidateAudience = true, ValidAudience = configuration["JWT:ValidAudience"], ValidIssuer = configuration["JWT:ValidIssuer"], IssuerSigningKey = new SymmetricSecurityKey(key) }; options.Events = new JwtBearerEvents { OnChallenge = async context => { // Call this to skip the default logic and avoid using the default response context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "application/json"; var result = JsonConvert.SerializeObject(new { code = ResponseCode.UnauthorizedDueToInvalidToken.ToCode(), message = ResponseCode.UnauthorizedDueToInvalidToken.ToMessage(), }); context.Response.WriteAsync(result); } }; }); var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, RequireExpirationTime = false, // Allow to use seconds for expiration of token // Required only when token lifetime less than 5 minutes // THIS ONE ClockSkew = TimeSpan.Zero }; builder.Services.AddSingleton(tokenValidationParameters); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
appsettings.json中的Kestrel配置
"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:7504" }, "Https": { "Url": "https://*:7503", "Certificate": { "Path": "D:/Certificates/Cert.pfx", "Password": "********" } } }
排查步骤
1. 确认Kestrel绑定配置是否生效
- 检查配置文件加载路径:Windows服务默认工作目录可能不是程序所在目录,导致
appsettings.json未被正确加载。修改配置加载代码,确保从程序执行目录读取:var configurationBuilder = new ConfigurationBuilder() .SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true); - 验证端口监听状态:在服务器上执行
netstat -ano命令,查看7503、7504端口的监听地址是否为0.0.0.0(IPv4)或::(IPv6),而非仅127.0.0.1(仅本地可访问)。
2. 重新验证防火墙规则
- 确认入站规则覆盖了TCP协议、正确的端口(7503/7504),并应用到所有网络配置文件(域、专用、公网)。
- 临时关闭防火墙测试,若能访问则说明规则存在遗漏(如协议错误、端口范围不符)。
3. 检查Windows服务运行权限
- 默认
LocalSystem账户可能存在网络访问限制,尝试将服务登录账户改为具有管理员权限的本地或域账户,重启服务后测试。
4. 网络连通性测试
- 在客户端机器上执行
ping 服务器IP,确认网络链路通畅。 - 用
Test-NetConnection 服务器IP -Port 7504(PowerShell)或telnet 服务器IP 7504测试端口连通性,若不通则说明网络或防火墙仍有问题。
5. 排除代码层面拦截
- 若客户端能收到401响应,说明网络连通,问题出在JWT验证(需携带有效Token);若完全无响应,优先排查网络或Kestrel配置。
- 确保
app.Run()未指定特定绑定地址,避免覆盖Kestrel配置。
内容的提问来源于stack exchange,提问作者Codesmith
相关产品推荐
相关产品推荐

