Nguon: Microsoft Learn · .NET 8.0

Cấu hình Endpoints cho Kestrel web server

Nguồn: Configure endpoints for Kestrel web server

Tổng quan

Kestrel endpoints (điểm cuối) cung cấp cơ sở hạ tầng để lắng nghe các request đến và định tuyến chúng đến middleware phù hợp. Một endpoint kết hợp:

Cấu hình Endpoint mặc định

Các project ASP.NET Core mới bind vào các endpoint mặc định:

Các port này được lưu trong file Properties/launchSettings.json (chỉ dùng cho phát triển local).

Nếu không có cấu hình endpoint nào, Kestrel bind vào http://localhost:5000.

Cấu hình Endpoints

Chỉ định endpoints bằng URLs

Bạn có thể cấu hình endpoints bằng:

Ví dụ định dạng URL

code
# Địa chỉ IPv4 với port
http://65.55.39.10:80/

# Địa chỉ IPv6 với port
http://[0:0:0:0:0:ffff:4137:270a]:80/

# Wildcard với port
http://*:80/
http://contoso.com:80/

# Localhost
http://localhost:5000/
http://127.0.0.1:5000/
http://[::1]:5000/

# Nhiều endpoints (phân tách bằng dấu chấm phẩy)
http://*:5000;http://localhost:5001;https://hostname:5002

Chỉ định chỉ ports

Dùng các khóa cấu hình HTTP_PORTSHTTPS_PORTS như biến môi trường hoặc trong file cấu hình:

json
ASPNETCORE_HTTP_PORTS=80;8080
ASPNETCORE_HTTPS_PORTS=443;8081

Đây là cách viết tắt của:

json
ASPNETCORE_URLS=http://*:80/;http://*:8080/;https://*:443/;https://*:8081/

Tạo endpoints trong appsettings.json

Cấu hình endpoints trong phần Kestrel:Endpoints:

json
{
  "Kestrel": {
    "Endpoints": {
      "MyHttpEndpoint": {
        "Url": "http://localhost:8080"
      }
    }
  }
}

Tải lại endpoints từ configuration

Mặc định, cấu hình endpoint có thể được tải lại khi source cấu hình thay đổi. Điều này có thể bị vô hiệu hóa bằng phương thức Configure(IConfiguration, Boolean).

Khi có thay đổi được báo hiệu:

Lưu ý: Các client kết nối đến endpoints được sửa đổi có thể bị ngắt kết nối hoặc bị từ chối trong quá trình khởi động lại.

ConfigurationLoader

Phương thức Configure trả về một KestrelConfigurationLoader cho phép bổ sung cài đặt endpoint:

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
    var kestrelSection = context.Configuration.GetSection("Kestrel");

    serverOptions.Configure(kestrelSection)
        .Endpoint("HTTPS", listenOptions =>
        {
            // ...
        });
});

Định nghĩa endpoints trong code

Sử dụng các phương thức KestrelServerOptions:

Lưu ý: Khi cả ListenUseUrls được sử dụng đồng thời, các endpoint Listen ghi đè các endpoint UseUrls.

Bind vào TCP socket

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
    serverOptions.Listen(IPAddress.Loopback, 5000);
    serverOptions.Listen(IPAddress.Loopback, 5001, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
});

Bind vào Unix socket

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
    serverOptions.ListenUnixSocket("/tmp/kestrel-test.sock");
});

Trong cấu hình Nginx:

code
proxy_pass http://unix:/tmp/kestrel-test.sock:/;

Đảm bảo socket có thể ghi:

bash
chmod go+w /tmp/kestrel-test.sock

Cấu hình endpoint defaults

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ConfigureEndpointDefaults(listenOptions =>
    {
        // ...
    });
});

Lưu ý: Các endpoints được tạo bằng cách gọi Listen trước khi gọi ConfigureEndpointDefaults sẽ không áp dụng các defaults.

Sử dụng dynamic port binding

Khi port 0 được chỉ định, Kestrel tự động bind vào một port khả dụng:

csharp
app.Run(async (context) =>
{
    var serverAddressFeature = context.Features.Get<IServerAddressesFeature>();

    if (serverAddressFeature is not null)
    {
        var listenAddresses = string.Join(", ", serverAddressFeature.Addresses);
        // ...
    }
});

Giới hạn: Dynamic port binding không khả dụng với:

Cấu hình HTTPS

HTTPS bảo mật các endpoints bằng Transport Layer Security (TLS). Kestrel yêu cầu một chứng chỉ TLS.

Cấu hình HTTPS trong appsettings.json

json
{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://localhost:5000"
      },
      "HttpsInlineCertFile": {
        "Url": "https://localhost:5001",
        "Certificate": {
          "Path": "<đường dẫn đến file .pfx>",
          "Password": "$CREDENTIAL_PLACEHOLDER$"
        }
      },
      "HttpsInlineCertAndKeyFile": {
        "Url": "https://localhost:5002",
        "Certificate": {
          "Path": "<đường dẫn đến file .pem/.crt>",
          "KeyPath": "<đường dẫn đến file .key>",
          "Password": "$CREDENTIAL_PLACEHOLDER$"
        }
      },
      "HttpsInlineCertStore": {
        "Url": "https://localhost:5003",
        "Certificate": {
          "Subject": "<subject; bắt buộc>",
          "Store": "<certificate store; bắt buộc>",
          "Location": "<location; mặc định là CurrentUser>",
          "AllowInvalid": "<true hoặc false; mặc định là false>"
        }
      },
      "HttpsDefaultCert": {
        "Url": "https://localhost:5004"
      }
    },
    "Certificates": {
      "Default": {
        "Path": "<đường dẫn đến file .pfx>",
        "Password": "$CREDENTIAL_PLACEHOLDER$"
      }
    }
  }
}

⚠️ Cảnh báo: Mật khẩu chứng chỉ không nên lưu dưới dạng plain text trong appsettings.json. Hãy dùng:

Ghi chú về Schema

Nguồn chứng chỉ

Chứng chỉ có thể được tải từ:

Cấu hình client certificates trong appsettings.json

json
{
  "Kestrel": {
    "Endpoints": {
      "MyHttpsEndpoint": {
        "Url": "https://localhost:5001",
        "ClientCertificateMode": "AllowCertificate",
        "Certificate": {
          "Path": "<đường dẫn đến file .pfx>",
          "Password": "$CREDENTIAL_PLACEHOLDER$"
        }
      }
    }
  }
}

Mặc định: ClientCertificateMode.NoCertificate (không yêu cầu client certificate)

Cấu hình SSL/TLS protocols trong appsettings.json

json
{
  "Kestrel": {
    "Endpoints": {
      "MyHttpsEndpoint": {
        "Url": "https://localhost:5001",
        "SslProtocols": ["Tls12", "Tls13"],
        "Certificate": {
          "Path": "<đường dẫn đến file .pfx>",
          "Password": "$CREDENTIAL_PLACEHOLDER$"
        }
      }
    }
  }
}

Mặc định: SslProtocols.None (dùng OS defaults để có giao thức tốt nhất)

Cấu hình HTTPS trong code

Dùng API Listen với UseHttps:

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
    serverOptions.Listen(IPAddress.Loopback, 5000);
    serverOptions.Listen(IPAddress.Loopback, 5001, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
});

Cấu hình client certificates trong code

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ConfigureHttpsDefaults(listenOptions =>
    {
        listenOptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
    });
});

Cấu hình HTTPS defaults trong code

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ConfigureHttpsDefaults(listenOptions =>
    {
        // Cấu hình HTTPS defaults cho tất cả endpoints
    });
});

Lưu ý: Các endpoints được tạo bằng cách gọi Listen trước khi gọi ConfigureHttpsDefaults sẽ không áp dụng defaults.

Cấu hình SSL/TLS protocols trong code

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ConfigureHttpsDefaults(listenOptions =>
    {
        listenOptions.SslProtocols = SslProtocols.Tls13;
    });
});

Cấu hình TLS cipher suites filter trong code

Trên Linux, dùng CipherSuitesPolicy để lọc TLS handshakes theo từng kết nối:

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
    serverOptions.ConfigureHttpsDefaults(listenOptions =>
    {
        listenOptions.OnAuthenticate = (context, sslOptions) =>
        {
            sslOptions.CipherSuitesPolicy = new CipherSuitesPolicy(
                new[]
                {
                    TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
                    TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
                });
        };
    });
});

Cấu hình Server Name Indication (SNI)

SNI (Chỉ thị tên server) cho phép lưu trữ nhiều domain trên cùng IP và port. Client gửi tên host trong quá trình TLS handshake, cho phép server cung cấp chứng chỉ đúng.

Cấu hình SNI trong appsettings.json

json
{
  "Kestrel": {
    "Endpoints": {
      "MySniEndpoint": {
        "Url": "https://*",
        "SslProtocols": ["Tls11", "Tls12"],
        "Sni": {
          "a.example.org": {
            "Protocols": "Http1AndHttp2",
            "SslProtocols": ["Tls11", "Tls12", "Tls13"],
            "Certificate": {
              "Subject": "<subject; bắt buộc>",
              "Store": "<certificate store; bắt buộc>"
            },
            "ClientCertificateMode": "NoCertificate"
          },
          "*.example.org": {
            "Certificate": {
              "Path": "<đường dẫn đến file .pfx>",
              "Password": "$CREDENTIAL_PLACEHOLDER$"
            }
          },
          "*": {
            "Protocols": "Http1"
          }
        }
      }
    },
    "Certificates": {
      "Default": {
        "Path": "<đường dẫn đến file .pfx>",
        "Password": "$CREDENTIAL_PLACEHOLDER$"
      }
    }
  }
}

SNI có thể ghi đè:

Wildcard matching

Cấu hình SNI với code

SNI với ServerCertificateSelector

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ListenAnyIP(5005, listenOptions =>
    {
        listenOptions.UseHttps(httpsOptions =>
        {
            var localhostCert = CertificateLoader.LoadFromStoreCert(
                "localhost", "My", StoreLocation.CurrentUser, allowInvalid: true);
            var exampleCert = CertificateLoader.LoadFromStoreCert(
                "example.com", "My", StoreLocation.CurrentUser, allowInvalid: true);
            var subExampleCert = CertificateLoader.LoadFromStoreCert(
                "sub.example.com", "My", StoreLocation.CurrentUser, allowInvalid: true);
            
            var certs = new Dictionary<string, X509Certificate2>(
                StringComparer.OrdinalIgnoreCase)
            {
                ["localhost"] = localhostCert,
                ["example.com"] = exampleCert,
                ["sub.example.com"] = subExampleCert
            };

            httpsOptions.ServerCertificateSelector = (connectionContext, name) =>
            {
                if (name is not null && certs.TryGetValue(name, out var cert))
                {
                    return cert;
                }
                return exampleCert;
            };
        });
    });
});

SNI với TlsHandshakeCallbackOptions

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ListenAnyIP(5005, listenOptions =>
    {
        var localhostCert = CertificateLoader.LoadFromStoreCert(
            "localhost", "My", StoreLocation.CurrentUser, allowInvalid: true);
        var exampleCert = CertificateLoader.LoadFromStoreCert(
            "example.com", "My", StoreLocation.CurrentUser, allowInvalid: true);

        listenOptions.UseHttps(new TlsHandshakeCallbackOptions
        {
            OnConnection = context =>
            {
                if (string.Equals(context.ClientHelloInfo.ServerName, "localhost",
                    StringComparison.OrdinalIgnoreCase))
                {
                    context.AllowDelayedClientCertificateNegotation = true;

                    return new ValueTask<SslServerAuthenticationOptions>(
                        new SslServerAuthenticationOptions
                        {
                            ServerCertificate = localhostCert
                        });
                }

                return new ValueTask<SslServerAuthenticationOptions>(
                    new SslServerAuthenticationOptions
                    {
                        ServerCertificate = exampleCert
                    });
            }
        });
    });
});

Cấu hình HTTP protocols

Kestrel hỗ trợ tất cả các phiên bản HTTP phổ biến được cấu hình qua enum HttpProtocols.

Giá trị HttpProtocolsGiao thức được phépGhi chú
Http1HTTP/1.1Có thể dùng có hoặc không có TLS
Http2HTTP/2Không có TLS chỉ khi client hỗ trợ Prior Knowledge mode
Http3HTTP/3Yêu cầu TLS
Http1AndHttp2HTTP/1.1 & HTTP/2HTTP/2 yêu cầu TLS ALPN negotiation; mặc định về HTTP/1.1 nếu không có
Http1AndHttp2AndHttp3HTTP/1.1, HTTP/2 & HTTP/3Request đầu tiên dùng HTTP/1.1 hoặc HTTP/2; header alt-svc kích hoạt nâng cấp HTTP/3

Mặc định: HttpProtocols.Http1AndHttp2

Giới hạn TLS cho HTTP/2

Cấu hình HTTP protocols trong appsettings.json

Đặt HTTP/1.1 cho endpoint cụ thể:

json
{
  "Kestrel": {
    "Endpoints": {
      "HttpsDefaultCert": {
        "Url": "https://localhost:5001",
        "Protocols": "Http1"
      }
    }
  }
}

Đặt giao thức mặc định cho tất cả endpoints:

json
{
  "Kestrel": {
    "EndpointDefaults": {
      "Protocols": "Http1"
    }
  }
}

Lưu ý: Các giao thức được chỉ định trong code ghi đè giá trị configuration.

Cấu hình HTTP protocols trong code

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
    serverOptions.Listen(IPAddress.Any, 8000, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
    });
});

Tùy chỉnh named pipe endpoints của Kestrel (ASP.NET Core 10.0+)

Thuộc tính CreateNamedPipeServerStream trên named pipe options cho phép tùy chỉnh theo từng endpoint:

csharp
using System.IO.Pipes;
using System.Security.AccessControl;
using System.Security.Principal;

var builder = WebApplication.CreateBuilder();

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenNamedPipe("defaultPipe");
    options.ListenNamedPipe("securedPipe");
});

builder.WebHost.UseNamedPipes(options =>
{
    options.CreateNamedPipeServerStream = (context) =>
    {
        var pipeName = context.NamedPipeEndPoint.PipeName;
        
        switch (pipeName)
        {
            case "defaultPipe":
                return NamedPipeTransportOptions.CreateDefaultNamedPipeServerStream(context);
            case "securedPipe":
                var allowSecurity = new PipeSecurity();
                allowSecurity.AddAccessRule(
                    new PipeAccessRule("Users", PipeAccessRights.FullControl, AccessControlType.Allow));

                return NamedPipeServerStreamAcl.Create(
                    pipeName, PipeDirection.InOut,
                    NamedPipeServerStream.MaxAllowedServerInstances,
                    PipeTransmissionMode.Byte, context.PipeOptions,
                    inBufferSize: 0, outBufferSize: 0, allowSecurity);
            default:
                throw new InvalidOperationException($"Unexpected pipe name: {pipeName}");
        }
    };
});

var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();