You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 17:17:52