프록시 서버 및 부하 분산 장치를 사용하도록 ASP.NET Core 구성

작성자: Chris Ross

ASP.NET Core 권장 구성에서 앱은 IIS용 ANCM(ASP.NET Core 모듈), Nginx 또는 Apache를 사용하여 호스트됩니다. 프록시 서버, 부하 분산 장치 및 기타 네트워크 어플라이언스는 종종 앱에 도달하기 전에 요청에 대한 정보를 숨깁니다.

  • HTTPS 요청이 HTTP를 통해 프록시된 경우 원래 스키마(HTTPS)가 손실되므로 헤더에서 전달되어야 합니다.
  • 앱은 프록시에서 요청을 수신하고 인터넷 또는 회사 네트워크의 실제 소스를 수신하는 것이 아니므로 원래 클라이언트 IP 주소도 헤더에서 전달되어야 합니다.

이 정보는 요청 처리 시 중요할 수 있습니다(예: 리디렉션, 인증, 링크 생성, 정책 평가 및 클라이언트 지리적 위치).

웹 팜에서 실행하려는 앱은 웹 팜의 Host ASP.NET Core를어야 합니다.

전달된 헤더

규칙에 따라 프록시는 HTTP 헤더에서 정보를 전달합니다.

헤더 설명
X-Forwarded-For(XFF) 요청 및 프록시 체인의 후속 프록시를 시작한 클라이언트에 대한 정보를 포함합니다. 이 매개 변수에는 IP 주소(및 선택적으로 포트 번호)가 포함될 수 있습니다. 프록시 서버의 체인에서 첫 번째 매개 변수는 요청이 처음 만들어진 클라이언트를 나타냅니다. 그 뒤에 후속 프록시 식별자가 추가됩니다. 체인의 마지막 프록시는 매개 변수 목록에 없습니다. 마지막 프록시의 IP 주소 및 선택적으로 포트 번호는 전송 계층에서 원격 IP 주소로 사용할 수 있습니다.
X-Forwarded-Proto(XFP) 원래 체계의 값입니다(HTTP 또는 HTTPS). 요청이 여러 프록시를 트래버스한 경우에는 값이 체계 목록일 수도 있습니다.
X-Forwarded-Host(XFH) 호스트 헤더 필드의 원래 값입니다. 일반적으로 프록시는 호스트 헤더를 수정하지 않습니다. 프록시가 호스트 헤더를 유효성 검사하거나 알려진 정상 값으로 제한하지 않는 시스템에 영향을 미치는 권한 상승 취약성에 대한 자세한 내용은 Microsoft 보안 공지 CVE-2018-0787을 참조하세요.

전달된 헤더 미들웨어(ForwardedHeadersMiddleware)는 이러한 헤더를 읽고 HttpContext의 연결된 필드를 채웁니다.

미들웨어가 다음을 업데이트합니다.

이전 내용에 대한 자세한 정보는 이 GitHub 문제를 참조하세요.

전달된 헤더 미들웨어 기본 설정을 구성할 수 있습니다. 기본 설정의 경우:

  • 앱과 요청 소스 사이에는 하나의 프록시만 있습니다.
  • 알려진 프록시 및 알려진 네트워크의 경우 루프백 주소만 구성됩니다.
  • 전달된 헤더의 이름은 X-Forwarded-ForX-Forwarded-Proto입니다.
  • ForwardedHeaders 값이 ForwardedHeaders.None인 경우 미들웨어를 사용하도록 설정하려면 여기에서 원하는 전달자를 설정해야 합니다.

일부 네트워크 어플라이언스는 X-Forwarded-ForX-Forwarded-Proto 헤더를 추가하려면 추가 구성이 필요합니다. 프록시된 요청이 앱에 도달할 때 이러한 헤더를 포함하지 않는 경우에는 어플라이언스 제조업체의 지침을 참조하세요. 어플라이언스가 X-Forwarded-ForX-Forwarded-Proto와는 다른 헤더 이름을 사용하는 경우 ForwardedForHeaderNameForwardedProtoHeaderName 옵션을 어플라이언스에서 사용하는 헤더 이름과 일치하도록 설정합니다. 자세한 내용은 전달된 헤더 미들웨어 옵션다른 헤더 이름을 사용하는 프록시 구성을 참조하세요.

IIS/IIS Express 및 ASP.NET Core 모듈

앱이 IIS 및 IIS용 ANCM(ASP.NET Core 모듈) 뒤에서 호스트되는 Out of Process인 경우 전달된 헤더 미들웨어가 IIS 통합 미들웨어에 의해 기본적으로 사용하도록 설정됩니다. 전달된 헤더 미들웨어는 ASP.NET Core 모듈에 고유한 제한된 구성으로 미들웨어 파이프라인에서 먼저 실행되도록 활성화됩니다. 제한된 구성은 전달된 헤더(예: IP 스푸핑)의 신뢰 문제로 인해 발생합니다. 미들웨어는 X-Forwarded-ForX-Forwarded-Proto 헤더를 전달하도록 구성되고 단일 localhost 프록시로 제한됩니다. 추가 구성이 필요한 경우 전달된 헤더 미들웨어 옵션을 참조하세요.

기타 프록시 서버 및 부하 분산 장치 시나리오

Out of Process 호스트할 때 IIS 통합 사용 외에는 전달된 헤더 미들웨어가 기본적으로 사용하도록 설정되지 않습니다. 앱에서 UseForwardedHeaders를 사용하여 전달된 헤더를 처리하려면 전달된 헤더 미들웨어를 사용하도록 설정해야 합니다. 미들웨어를 사용하도록 설정한 후 미들웨어에 ForwardedHeadersOptions를 지정하지 않은 경우 기본 ForwardedHeadersOptions.ForwardedHeadersForwardedHeaders.None입니다.

ForwardedHeadersOptions를 사용하여 X-Forwarded-ForX-Forwarded-Proto 헤더를 전달하도록 미들웨어를 구성합니다.

전달된 헤더 미들웨어 순서

전달된 헤더 미들웨어는 다른 미들웨어보다 먼저 실행해야 합니다. 이 순서를 지정하면 전달된 헤더 정보에 따라 달라지는 미들웨어는 처리하기 위해 헤더 값을 사용할 수 있습니다. 전달된 헤더 미들웨어는 진단 및 오류 처리 뒤에 실행될 수 있지만 UseHsts를 호출하기 전에 실행되어야 합니다.

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseForwardedHeaders();
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseForwardedHeaders();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

또는 진단 전에 UseForwardedHeaders를 호출합니다.

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

참고 항목

ForwardedHeadersOptions를 지정하지 않거나 UseForwardedHeaders를 사용하여 확장 메서드에 직접 적용하지 않은 경우 전달할 기본 헤더는 ForwardedHeaders.None입니다. 전달할 헤더를 사용하여 ForwardedHeaders 속성을 구성해야 합니다.

Nginx 구성

X-Forwarded-ForX-Forwarded-Proto 헤더를 전달하려면 Nginx를 사용하여 Linux에서 ASP.NET Core 호스트를 참조하세요. 자세한 내용은 NGINX: 전달된 헤더 사용을 참조하세요.

Apache 구성

X-Forwarded-For 가 자동으로 추가됩니다. 자세한 내용은 Apache 모듈 mod_proxy: 역방향 프록시 요청 헤더를 참조하세요. X-Forwarded-Proto 헤더를 전달하는 방법에 대한 자세한 내용은 Apache를 사용하여 Linux에서 ASP.NET Core 호스트를 참조하세요.

전달된 헤더 미들웨어 옵션

ForwardedHeadersOptions전달된 헤더 미들웨어의 동작을 제어합니다. 다음 예제에서는 기본값을 변경합니다.

  • 전달된 헤더의 항목 수를 2로 제한합니다.
  • 127.0.10.1의 알려진 프록시 주소를 추가합니다.
  • 전달된 헤더 이름을 기본값 X-Forwarded-For에서 X-Forwarded-For-My-Custom-Header-Name으로 변경합니다.
using System.Net;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardLimit = 2;
    options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
    options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});

var app = builder.Build();

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();
옵션 설명
AllowedHosts X-Forwarded-Host 헤더에 의한 호스트를 제공된 값으로 제한합니다.
  • 값은 ordinal-ignore-case를 사용하여 비교됩니다.
  • 포트 번호는 제외해야 합니다.
  • 목록이 비어 있으면 모든 호스트가 허용됩니다.
  • 최상위 수준 와일드카드 *는 비어 있지 않은 모든 호스트를 허용합니다.
  • 하위 도메인 와일드카드는 허용되지만 루트 도메인과 일치하지 않습니다. 예를 들어 *.contoso.com은 하위 도메인 foo.contoso.com과 일치하지만 루트 도메인 contoso.com과 일치하지 않습니다.
  • 유니코드 호스트 이름은 허용되지만 일치시킬 Punycode로 변환됩니다.
  • IPv6 주소는 경계 대괄호를 포함하고 기존 형식이어야 합니다(예: [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]). IPv6 주소는 서로 다른 형식 간에 논리적 같음을 확인하기 위해 특별히 처리되지 않으며 정규화가 수행되지 않습니다.
  • 허용된 호스트를 제한하지 못하면 공격자가 서비스에서 생성된 링크를 스푸핑할 수 있습니다.
기본값은 빈 IList<string>입니다.
ForwardedForHeaderName ForwardedHeadersDefaults.XForwardedForHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다. 이 옵션은 프록시/전달자가 X-Forwarded-For 헤더를 사용하지 않고 일부 다른 헤더를 사용하여 정보를 전달할 때 사용됩니다.

기본값은 X-Forwarded-For입니다.
ForwardedHeaders 처리해야 할 전달자를 알려줍니다. 적용되는 필드 목록은 ForwardedHeaders 열거형을 참조하세요. 이 속성에 할당된 일반적인 값은 ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto입니다.

기본값은 ForwardedHeaders.None입니다.
ForwardedHostHeaderName ForwardedHeadersDefaults.XForwardedHostHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다. 이 옵션은 프록시/전달자가 X-Forwarded-Host 헤더를 사용하지 않고 일부 다른 헤더를 사용하여 정보를 전달할 때 사용됩니다.

기본값은 X-Forwarded-Host입니다.
ForwardedProtoHeaderName ForwardedHeadersDefaults.XForwardedProtoHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다. 이 옵션은 프록시/전달자가 X-Forwarded-Proto 헤더를 사용하지 않고 일부 다른 헤더를 사용하여 정보를 전달할 때 사용됩니다.

기본값은 X-Forwarded-Proto입니다.
ForwardLimit 처리되는 헤더의 항목 수를 제한합니다. 제한을 사용하지 않도록 null로 설정하지만, KnownProxies 또는 KnownNetworks가 구성된 경우에만 사용해야 합니다. 비-null 값을 설정하면 잘못 구성된 프록시 및 네트워크에서 측면 채널에서 오는 악성 요청을 예방하지만 보증하지는 않습니다.

전달된 헤더 미들웨어는 헤더를 역순으로 오른쪽에서 왼쪽으로 처리합니다. 기본값(1)만 사용된다면 ForwardLimit의 값을 늘리지 않는 한, 헤더에서 맨 오른쪽 값만 처리됩니다.

기본값은 1입니다.
KnownNetworks 전달된 헤더를 허용하기 위한 알려진 네트워크의 주소 범위입니다. CIDR(Classless Interdomain Routing) 표기법을 사용하여 IP 범위를 제공합니다.

서버에서 이중 모드 소켓을 사용하는 경우 IPv4 주소가 IPv6 형식(예: IPv4에서 10.0.0.1은 IPv6에서 ::ffff:10.0.0.1로 표시됨)으로 제공됩니다. IPAddress.MapToIPv6를 참조하세요. HttpContext.Connection.RemoteIpAddress를 참조하여 이 형식이 필요한지 확인하세요.

기본값은 IPAddress.Loopback에 대한 단일 항목을 포함하는 IList<IPNetwork>입니다.
KnownProxies 전달된 헤더를 허용하기 위한 알려진 프록시의 주소입니다. KnownProxies를 사용하여 정확한 IP 주소 일치 항목을 지정합니다.

서버에서 이중 모드 소켓을 사용하는 경우 IPv4 주소가 IPv6 형식(예: IPv4에서 10.0.0.1은 IPv6에서 ::ffff:10.0.0.1로 표시됨)으로 제공됩니다. IPAddress.MapToIPv6를 참조하세요. HttpContext.Connection.RemoteIpAddress를 참조하여 이 형식이 필요한지 확인하세요.

기본값은 IPAddress.IPv6Loopback에 대한 단일 항목을 포함하는 IList<IPAddress>입니다.
OriginalForHeaderName ForwardedHeadersDefaults.XOriginalForHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다.

기본값은 X-Original-For입니다.
OriginalHostHeaderName ForwardedHeadersDefaults.XOriginalHostHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다.

기본값은 X-Original-Host입니다.
OriginalProtoHeaderName ForwardedHeadersDefaults.XOriginalProtoHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다.

기본값은 X-Original-Proto입니다.
RequireHeaderSymmetry 처리 중인 ForwardedHeadersOptions.ForwardedHeaders 간에 동기화할 헤더 값 수가 필요합니다.

ASP.NET Core 1.x의 기본값은 true입니다. ASP.NET Core 2.0 이상의 기본값은 false입니다.

시나리오 및 사용 사례

전달된 헤더를 추가할 수 없고 모든 요청이 안전한 경우

일부 경우에는 앱으로 프록시된 요청에 전달된 헤더를 추가할 수 없습니다. 모든 공용 외부 요청이 HTTPS가 되도록 프록시가 적용되는 경우 미들웨어를 사용하기 전에 체계를 수동으로 설정할 수 있습니다.

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

app.Use((context, next) =>
{
    context.Request.Scheme = "https";
    return next(context);
});

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

이 코드는 개발 또는 스테이징 환경에서 환경 변수 또는 기타 구성 설정을 통해 사용하지 않도록 설정될 수 있습니다.

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

if (!app.Environment.IsProduction())
{
    app.Use((context, next) =>
    {
        context.Request.Scheme = "https";
        return next(context);
    });
}

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

요청 경로를 변경하는 경로 기준 및 프록시 작업

일부 프록시는 경로를 그대로 전달하지만 라우팅이 제대로 작동하도록 제거해야 하는 앱 기본 경로를 포함합니다. UsePathBaseExtensions.UsePathBase 미들웨어는 경로를 HttpRequest.Path로 분할하고 앱 기본 경로를 HttpRequest.PathBase로 분할합니다.

/foo/foo/api/1로 전달되는 프록시 경로의 앱 기본 경로인 경우 미들웨어는 다음 명령을 사용하여 Request.PathBase/foo로 설정하고 Request.Path/api/1로 설정합니다.

app.UsePathBase("/foo");
// ...
app.UseRouting();

참고 항목

WebApplication(ASP.NET Core 5.0에서 6.0으로 마이그레이션 참조)을 사용할 때 라우팅 미들웨어가 경로 일치 전에 수정된 경로를 관찰할 수 있도록 UsePathBase 이후 app.UseRouting을 호출해야 합니다. 그렇지 않은 경우 미들웨어 순서 지정라우팅 문서에서 설명하는 것과 같이 UsePathBase에서 경로를 다시 작성하기 전에 경로가 일치합니다.

미들웨어가 역방향으로 다시 호출되면 원래 경로 및 경로 기준이 다시 적용됩니다. 미들웨어 순서 처리에 대한 자세한 내용은 ASP.NET Core 미들웨어를 참조하세요.

프록시가 경로를 자르는 경우(예: /foo/api/1/api/1에 전달) 요청의 PathBase 속성을 설정하여 리디렉션 및 링크를 수정합니다.

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString("/foo");
    return next(context);
});

프록시가 경로 데이터를 추가하는 경우 경로의 일부를 삭제한 다음, StartsWithSegments를 사용하고 Path 속성에 할당하여 리디렉션 및 링크를 수정합니다.

app.Use((context, next) =>
{
    if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
    {
        context.Request.Path = remainder;
    }

    return next(context);
});

다른 헤더 이름을 사용하는 프록시에 대한 구성

프록시가 X-Forwarded-ForX-Forwarded-Proto라는 헤더를 사용하여 프록시 주소/포트 및 원래 체계 정보를 전달하지 않는 경우, ForwardedForHeaderNameForwardedProtoHeaderName 옵션을 프록시에서 사용하는 헤더 이름과 일치하도록 설정합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "HeaderNamUsedByProxy_X-Forwarded-For_Header";
    options.ForwardedProtoHeaderName = "HeaderNamUsedByProxy_X-Forwarded-Proto_Header";
});

var app = builder.Build();

app.UseForwardedHeaders();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Linux용 스키마 및 비 IIS 역방향 프록시 전달

UseHttpsRedirectionUseHsts을(를) 호출하는 앱은 Azure Linux App Service, Azure Linux VM(가상 머신) 또는 IIS 이외의 다른 역방향 프록시 뒤에 배포된 경우 사이트를 무한 루프에 배치합니다. TLS는 역방향 프록시에 의해 종료되며, Kestrel은 올바른 요청 체계를 인식하지 못합니다. OAuth 및 OIDC도 잘못된 리디렉션을 생성하므로 이 구성에서 실패합니다. UseIISIntegration은 IIS 뒤에서 실행되는 경우 전달된 헤더 미들웨어를 추가하고 구성하지만 Linux에 대한 일치하는 자동 구성이 없습니다(Apache 또는 Nginx 통합).

비 IIS 시나리오에서 프록시의 스키마를 전달하려면, ASPNETCORE_FORWARDEDHEADERS_ENABLEDtrue로 설정하여 전달된 헤더 미들웨어를 사용하도록 설정합니다. 경고: 이 플래그는 클라우드 환경용으로 설계된 설정을 사용하며, 허용되는 IP 전달자를 제한하기 위해 KnownProxies option 등의 기능을 사용하도록 설정하지 않습니다.

인증서 전달

Azure

인증서 전달에 대한 Azure App Service를 구성하려면 Azure App Service에 대한 TLS 상호 인증 구성을 참조하세요. 다음 지침은 ASP.NET Core 앱 구성과 관련됩니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
    options.CertificateHeader = "X-ARR-ClientCert");

var app = builder.Build();

app.UseCertificateForwarding();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();
app.UseAuthentication();

app.MapRazorPages();

app.Run();

다른 웹 프록시

IIS 또는 Azure App Service의 ARR(애플리케이션 요청 라우팅)이 아닌 프록시를 사용하는 경우 HTTP 헤더에서 받은 인증서를 전달하도록 프록시를 구성합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
    options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");

var app = builder.Build();

app.UseCertificateForwarding();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();
app.UseAuthentication();

app.MapRazorPages();

app.Run();

프록시가 Nginx에서와 같이 인증서를 base64 인코딩하지 않는 경우 HeaderConverter 옵션을 설정합니다. 다음 예제를 참조하세요.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME";
    options.HeaderConverter = (headerValue) =>
    {
        // Conversion logic to create an X509Certificate2.
        var clientCertificate = ConversionLogic.CreateAnX509Certificate2();
        return clientCertificate;
    };
});

var app = builder.Build();

app.UseCertificateForwarding();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();
app.UseAuthentication();

app.MapRazorPages();

app.Run();

문제 해결

헤더가 예상대로 전달되지 않으면 debug 수준 로깅 및 HTTP 요청 로깅을 사용하도록 설정합니다. UseHttpLoggingUseForwardedHeaders 이후에 호출해야 합니다.

using Microsoft.AspNetCore.HttpLogging;
using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddHttpLogging(options =>
{
    options.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders;
});

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

var app = builder.Build();

app.UseForwardedHeaders();
app.UseHttpLogging();

app.Use(async (context, next) =>
{
    // Connection: RemoteIp
    app.Logger.LogInformation("Request RemoteIp: {RemoteIpAddress}",
        context.Connection.RemoteIpAddress);

    await next(context);
});

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

제공된 헤더에 여러 값이 있는 경우 전달된 헤더 미들웨어는 오른쪽에서 왼쪽으로 역순으로 헤더를 처리합니다. 기본 ForwardLimit1(일)이므로 ForwardLimit의 값을 늘리지 않는 한, 헤더에서 맨 오른쪽 값만 처리됩니다.

요청의 원래 원격 IP는 전달된 헤더가 처리되기 전에 KnownProxies 또는 KnownNetworks 목록의 항목과 일치해야 합니다. 이렇게 하면 신뢰할 수 없는 프록시에서 전달자가 허용되지 않아 헤더 스푸핑이 제한됩니다. 알 수 없는 프록시가 검색되면 로그에 프록시 주소가 기록됩니다.

September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321

앞의 예제에서 10.0.0.100은 프록시 서버입니다. 서버가 신뢰할 수 있는 프록시인 경우 서버의 IP 주소를 KnownProxies에 추가합니다. 또는 신뢰할 수 있는 네트워크를 KnownNetworks에 추가합니다. 자세한 내용은 전달된 헤더 미들웨어 옵션 섹션을 참조하세요.

using Microsoft.AspNetCore.HttpOverrides;
using System.Net;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseForwardedHeaders();
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseForwardedHeaders();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

로그를 표시하기 위해 "Microsoft.AspNetCore.HttpLogging": "Information"appsettings.Development.json 파일에 추가합니다.

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.HttpLogging": "Information"
    }
  }
}

Important

신뢰할 수 있는 프록시 및 네트워크만 헤더를 전달할 수 있습니다. 그렇지 않을 경우 IP 스푸핑 공격이 가능합니다.

추가 리소스

ASP.NET Core의 권장 구성에서 앱은 IIS/ASP.NET Core 모듈, Nginx 또는 Apache를 사용하여 호스트됩니다. 프록시 서버, 부하 분산 장치 및 기타 네트워크 어플라이언스는 종종 앱에 도달하기 전에 요청에 대한 정보를 숨깁니다.

  • HTTPS 요청이 HTTP를 통해 프록시된 경우 원래 스키마(HTTPS)가 손실되므로 헤더에서 전달되어야 합니다.
  • 앱은 프록시에서 요청을 수신하고 인터넷 또는 회사 네트워크의 실제 소스를 수신하는 것이 아니므로 원래 클라이언트 IP 주소도 헤더에서 전달되어야 합니다.

이 정보는 요청 처리 시 중요할 수 있습니다(예: 리디렉션, 인증, 링크 생성, 정책 평가 및 클라이언트 지리적 위치).

전달된 헤더

규칙에 따라 프록시는 HTTP 헤더에서 정보를 전달합니다.

헤더 설명
X-Forwarded-For 요청 및 프록시 체인의 후속 프록시를 시작한 클라이언트에 대한 정보를 포함합니다. 이 매개 변수에는 IP 주소(및 선택적으로 포트 번호)가 포함될 수 있습니다. 프록시 서버의 체인에서 첫 번째 매개 변수는 요청이 처음 만들어진 클라이언트를 나타냅니다. 그 뒤에 후속 프록시 식별자가 추가됩니다. 체인의 마지막 프록시는 매개 변수 목록에 없습니다. 마지막 프록시의 IP 주소 및 선택적으로 포트 번호는 전송 계층에서 원격 IP 주소로 사용할 수 있습니다.
X-Forwarded-Proto 원래 체계(HTTP/HTTPS)의 값입니다. 요청이 여러 프록시를 트래버스한 경우에는 값이 체계 목록일 수도 있습니다.
X-Forwarded-Host 호스트 헤더 필드의 원래 값입니다. 일반적으로 프록시는 호스트 헤더를 수정하지 않습니다. 프록시가 호스트 헤더를 유효성 검사하거나 알려진 정상 값으로 제한하지 않는 시스템에 영향을 미치는 권한 상승 취약성에 대한 자세한 내용은 Microsoft 보안 공지 CVE-2018-0787을 참조하세요.

전달된 헤더 미들웨어(ForwardedHeadersMiddleware)는 헤더를 읽고 HttpContext의 관련 필드를 채웁니다.

미들웨어가 다음을 업데이트합니다.

  • HttpContext.Connection.RemoteIpAddress: X-Forwarded-For 헤더 값을 사용하여 설정합니다. 추가 설정은 미들웨어가 RemoteIpAddress를 설정하는 방법에 영향을 줍니다. 자세한 내용은 전달된 헤더 미들웨어 옵션을 참조하세요. 사용된 값은 X-Forwarded-For에서 제거되고 이전 값은 X-Original-For에 유지됩니다. 동일한 패턴이 다른 헤더 HostProto에 적용됩니다.
  • HttpContext.Request.Scheme: X-Forwarded-Proto 헤더 값을 사용하여 설정합니다.
  • HttpContext.Request.Host: X-Forwarded-Host 헤더 값을 사용하여 설정합니다.

이전 내용에 대한 자세한 정보는 이 GitHub 문제를 참조하세요.

전달된 헤더 미들웨어 기본 설정을 구성할 수 있습니다. 기본 설정의 경우:

  • 앱과 요청 소스 사이에는 하나의 프록시만 있습니다.
  • 알려진 프록시 및 알려진 네트워크의 경우 루프백 주소만 구성됩니다.
  • 전달된 헤더의 이름은 X-Forwarded-ForX-Forwarded-Proto입니다.
  • ForwardedHeaders 값이 ForwardedHeaders.None인 경우 미들웨어를 사용하도록 설정하려면 여기에서 원하는 전달자를 설정해야 합니다.

일부 네트워크 어플라이언스는 X-Forwarded-ForX-Forwarded-Proto 헤더를 추가하려면 추가 구성이 필요합니다. 프록시된 요청이 앱에 도달할 때 이러한 헤더를 포함하지 않는 경우에는 어플라이언스 제조업체의 지침을 참조하세요. 어플라이언스가 X-Forwarded-ForX-Forwarded-Proto와는 다른 헤더 이름을 사용하는 경우 ForwardedForHeaderNameForwardedProtoHeaderName 옵션을 어플라이언스에서 사용하는 헤더 이름과 일치하도록 설정합니다. 자세한 내용은 전달된 헤더 미들웨어 옵션다른 헤더 이름을 사용하는 프록시 구성을 참조하세요.

IIS/IIS Express 및 ASP.NET Core 모듈

앱이 IIS 및 ASP.NET Core 모듈 뒤에서 호스트되는 Out of Process인 경우 전달된 헤더 미들웨어가 IIS 통합 미들웨어에 의해 기본적으로 사용하도록 설정됩니다. 전달된 헤더 미들웨어는 전달된 헤더 관련 신뢰 문제(예: IP 스푸핑)로 인해 ASP.NET Core 모듈에 특정한 제한된 구성을 사용하여 미들웨어 파이프라인에서 첫 번째로 실행될 수 있도록 활성화됩니다. 미들웨어는 X-Forwarded-ForX-Forwarded-Proto 헤더를 전달하도록 구성되고 단일 localhost 프록시로 제한됩니다. 추가 구성이 필요한 경우 전달된 헤더 미들웨어 옵션을 참조하세요.

기타 프록시 서버 및 부하 분산 장치 시나리오

Out of Process를 호스트할 때 IIS 통합 사용 외에는 전달된 헤더 미들웨어가 기본적으로 사용하도록 설정되지 않습니다. 앱에서 UseForwardedHeaders를 사용하여 전달된 헤더를 처리하려면 전달된 헤더 미들웨어를 사용하도록 설정해야 합니다. 미들웨어를 사용하도록 설정한 후 미들웨어에 ForwardedHeadersOptions를 지정하지 않은 경우 기본 ForwardedHeadersOptions.ForwardedHeadersForwardedHeaders.None입니다.

ForwardedHeadersOptions를 사용하여 Startup.ConfigureServicesX-Forwarded-ForX-Forwarded-Proto 헤더를 전달하도록 미들웨어를 구성합니다.

전달된 헤더 미들웨어 순서

전달된 헤더 미들웨어는 다른 미들웨어보다 먼저 실행해야 합니다. 이 순서를 지정하면 전달된 헤더 정보에 따라 달라지는 미들웨어는 처리하기 위해 헤더 값을 사용할 수 있습니다. 전달된 헤더 미들웨어는 진단 및 오류 처리 뒤에 실행될 수 있지만 UseHsts를 호출하기 전에 실행되어야 합니다.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        services.Configure<ForwardedHeadersOptions>(options =>
        {
            options.ForwardedHeaders =
                ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseForwardedHeaders();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseForwardedHeaders();
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

또는 진단 전에 UseForwardedHeaders를 호출합니다.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseForwardedHeaders();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

참고 항목

Startup.ConfigureServices에서 ForwardedHeadersOptions를 지정하지 않거나 UseForwardedHeaders를 사용하여 확장 메서드에 직접 지정하지 않은 경우 전달할 기본 헤더는 ForwardedHeaders.None입니다. 전달할 헤더를 사용하여 ForwardedHeaders 속성을 구성해야 합니다.

Nginx 구성

X-Forwarded-ForX-Forwarded-Proto 헤더를 전달하려면 Nginx를 사용하여 Linux에서 ASP.NET Core 호스트를 참조하세요. 자세한 내용은 NGINX: 전달된 헤더 사용을 참조하세요.

Apache 구성

X-Forwarded-For가 자동으로 추가됩니다( Apache 모듈 mod_proxy: 역방향 프록시 요청 헤더 참조). X-Forwarded-Proto 헤더를 전달하는 방법에 대한 자세한 내용은 Apache를 사용하여 Linux에서 ASP.NET Core 호스트를 참조하세요.

전달된 헤더 미들웨어 옵션

ForwardedHeadersOptions는 전달된 헤더 미들웨어의 동작을 제어합니다. 다음 예제에서는 기본값을 변경합니다.

  • 전달된 헤더의 항목 수를 2로 제한합니다.
  • 127.0.10.1의 알려진 프록시 주소를 추가합니다.
  • 전달된 헤더 이름을 기본값 X-Forwarded-For에서 X-Forwarded-For-My-Custom-Header-Name으로 변경합니다.
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardLimit = 2;
    options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
    options.ForwardedForHeaderName = "X-Forwarded-For-My-Custom-Header-Name";
});
옵션 설명
AllowedHosts X-Forwarded-Host 헤더에 의한 호스트를 제공된 값으로 제한합니다.
  • 값은 ordinal-ignore-case를 사용하여 비교됩니다.
  • 포트 번호는 제외해야 합니다.
  • 목록이 비어 있으면 모든 호스트가 허용됩니다.
  • 최상위 수준 와일드카드 *는 비어 있지 않은 모든 호스트를 허용합니다.
  • 하위 도메인 와일드카드는 허용되지만 루트 도메인과 일치하지 않습니다. 예를 들어 *.contoso.com은 하위 도메인 foo.contoso.com과 일치하지만 루트 도메인 contoso.com과 일치하지 않습니다.
  • 유니코드 호스트 이름은 허용되지만 일치시킬 Punycode로 변환됩니다.
  • IPv6 주소는 경계 대괄호를 포함하고 기존 형식이어야 합니다(예: [ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]). IPv6 주소는 서로 다른 형식 간에 논리적 같음을 확인하기 위해 특별히 처리되지 않으며 정규화가 수행되지 않습니다.
  • 허용된 호스트를 제한하지 못하면 공격자가 서비스에서 생성된 링크를 스푸핑할 수 있습니다.
기본값은 빈 IList<string>입니다.
ForwardedForHeaderName ForwardedHeadersDefaults.XForwardedForHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다. 이 옵션은 프록시/전달자가 X-Forwarded-For 헤더를 사용하지 않고 일부 다른 헤더를 사용하여 정보를 전달할 때 사용됩니다.

기본값은 X-Forwarded-For입니다.
ForwardedHeaders 처리해야 할 전달자를 알려줍니다. 적용되는 필드 목록은 ForwardedHeaders 열거형을 참조하세요. 이 속성에 할당된 일반적인 값은 ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto입니다.

기본값은 ForwardedHeaders.None입니다.
ForwardedHostHeaderName ForwardedHeadersDefaults.XForwardedHostHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다. 이 옵션은 프록시/전달자가 X-Forwarded-Host 헤더를 사용하지 않고 일부 다른 헤더를 사용하여 정보를 전달할 때 사용됩니다.

기본값은 X-Forwarded-Host입니다.
ForwardedProtoHeaderName ForwardedHeadersDefaults.XForwardedProtoHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다. 이 옵션은 프록시/전달자가 X-Forwarded-Proto 헤더를 사용하지 않고 일부 다른 헤더를 사용하여 정보를 전달할 때 사용됩니다.

기본값은 X-Forwarded-Proto입니다.
ForwardLimit 처리되는 헤더의 항목 수를 제한합니다. 제한을 사용하지 않도록 null로 설정하지만, KnownProxies 또는 KnownNetworks가 구성된 경우에만 사용해야 합니다. 비-null 값을 설정하면 잘못 구성된 프록시 및 네트워크에서 측면 채널에서 오는 악성 요청을 예방하지만 보증하지는 않습니다.

전달된 헤더 미들웨어는 헤더를 역순으로 오른쪽에서 왼쪽으로 처리합니다. 기본값(1)만 사용된다면 ForwardLimit의 값을 늘리지 않는 한, 헤더에서 맨 오른쪽 값만 처리됩니다.

기본값은 1입니다.
KnownNetworks 전달된 헤더를 허용하기 위한 알려진 네트워크의 주소 범위입니다. CIDR(Classless Interdomain Routing) 표기법을 사용하여 IP 범위를 제공합니다.

서버에서 이중 모드 소켓을 사용하는 경우 IPv4 주소가 IPv6 형식(예: IPv4에서 10.0.0.1은 IPv6에서 ::ffff:10.0.0.1로 표시됨)으로 제공됩니다. IPAddress.MapToIPv6를 참조하세요. HttpContext.Connection.RemoteIpAddress를 참조하여 이 형식이 필요한지 확인하세요.

기본값은 IPAddress.Loopback에 대한 단일 항목을 포함하는 IList<IPNetwork>입니다.
KnownProxies 전달된 헤더를 허용하기 위한 알려진 프록시의 주소입니다. KnownProxies를 사용하여 정확한 IP 주소 일치 항목을 지정합니다.

서버에서 이중 모드 소켓을 사용하는 경우 IPv4 주소가 IPv6 형식(예: IPv4에서 10.0.0.1은 IPv6에서 ::ffff:10.0.0.1로 표시됨)으로 제공됩니다. IPAddress.MapToIPv6를 참조하세요. HttpContext.Connection.RemoteIpAddress를 참조하여 이 형식이 필요한지 확인하세요.

기본값은 IPAddress.IPv6Loopback에 대한 단일 항목을 포함하는 IList<IPAddress>입니다.
OriginalForHeaderName ForwardedHeadersDefaults.XOriginalForHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다.

기본값은 X-Original-For입니다.
OriginalHostHeaderName ForwardedHeadersDefaults.XOriginalHostHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다.

기본값은 X-Original-Host입니다.
OriginalProtoHeaderName ForwardedHeadersDefaults.XOriginalProtoHeaderName에서 지정된 헤더 대신 이 속성에서 지정된 헤더를 사용합니다.

기본값은 X-Original-Proto입니다.
RequireHeaderSymmetry 처리 중인 ForwardedHeadersOptions.ForwardedHeaders 간에 동기화할 헤더 값 수가 필요합니다.

ASP.NET Core 1.x의 기본값은 true입니다. ASP.NET Core 2.0 이상의 기본값은 false입니다.

시나리오 및 사용 사례

전달된 헤더를 추가할 수 없고 모든 요청이 안전한 경우

일부 경우에는 앱으로 프록시된 요청에 전달된 헤더를 추가할 수 없습니다. 모든 공용 외부 요청이 HTTPS가 되도록 프록시가 적용되는 경우 미들웨어를 사용하기 전에 Startup.Configure에서 체계를 수동으로 설정할 수 있습니다.

app.Use((context, next) =>
{
    context.Request.Scheme = "https";
    return next();
});

이 코드는 개발 또는 스테이징 환경에서 환경 변수 또는 기타 구성 설정을 통해 사용하지 않도록 설정될 수 있습니다.

요청 경로를 변경하는 프록시 및 경로 기준을 처리합니다.

일부 프록시는 경로를 그대로 전달하지만 라우팅이 제대로 작동하도록 제거해야 하는 앱 기본 경로를 포함합니다. UsePathBaseExtensions.UsePathBase 미들웨어는 경로를 HttpRequest.Path로 분할하고 앱 기본 경로를 HttpRequest.PathBase로 분할합니다.

/foo/foo/api/1로 전달되는 프록시 경로의 앱 기본 경로인 경우 미들웨어는 다음 명령을 사용하여 Request.PathBase/foo로 설정하고 Request.Path/api/1로 설정합니다.

app.UsePathBase("/foo");

미들웨어가 역방향으로 다시 호출되면 원래 경로 및 경로 기준이 다시 적용됩니다. 미들웨어 순서 처리에 대한 자세한 내용은 ASP.NET Core 미들웨어를 참조하세요.

프록시가 경로를 자르는 경우(예: /foo/api/1/api/1에 전달) 요청의 PathBase 속성을 설정하여 리디렉션 및 링크를 수정합니다.

app.Use((context, next) =>
{
    context.Request.PathBase = new PathString("/foo");
    return next();
});

프록시가 경로 데이터를 추가하는 경우 경로의 일부를 삭제한 다음, StartsWithSegments를 사용하고 Path 속성에 할당하여 리디렉션 및 링크를 수정합니다.

app.Use((context, next) =>
{
    if (context.Request.Path.StartsWithSegments("/foo", out var remainder))
    {
        context.Request.Path = remainder;
    }

    return next();
});

다른 헤더 이름을 사용하는 프록시에 대한 구성

프록시가 X-Forwarded-ForX-Forwarded-Proto라는 헤더를 사용하여 프록시 주소/포트 및 원래 체계 정보를 전달하지 않는 경우, ForwardedForHeaderNameForwardedProtoHeaderName 옵션을 프록시에서 사용하는 헤더 이름과 일치하도록 설정합니다.

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-For_Header";
    options.ForwardedProtoHeaderName = "Header_Name_Used_By_Proxy_For_X-Forwarded-Proto_Header";
});

Linux용 스키마 및 비 IIS 역방향 프록시 전달

UseHttpsRedirectionUseHsts을(를) 호출하는 앱은 Azure Linux App Service, Azure Linux VM(가상 머신) 또는 IIS 이외의 다른 역방향 프록시 뒤에 배포된 경우 사이트를 무한 루프에 배치합니다. TLS는 역방향 프록시에 의해 종료되며, Kestrel은 올바른 요청 체계를 인식하지 못합니다. OAuth 및 OIDC도 잘못된 리디렉션을 생성하므로 이 구성에서 실패합니다. UseIISIntegration은 IIS 뒤에서 실행되는 경우 전달된 헤더 미들웨어를 추가하고 구성하지만 Linux에 대한 일치하는 자동 구성이 없습니다(Apache 또는 Nginx 통합).

비 IIS 시나리오에서 프록시의 스키마를 전달하려면 전달된 헤더 미들웨어를 추가하고 구성합니다. Startup.ConfigureServices에서 다음 코드를 사용합니다.

// using Microsoft.AspNetCore.HttpOverrides;

if (string.Equals(
    Environment.GetEnvironmentVariable("ASPNETCORE_FORWARDEDHEADERS_ENABLED"), 
    "true", StringComparison.OrdinalIgnoreCase))
{
    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | 
            ForwardedHeaders.XForwardedProto;
        // Only loopback proxies are allowed by default.
        // Clear that restriction because forwarders are enabled by explicit 
        // configuration.
        options.KnownNetworks.Clear();
        options.KnownProxies.Clear();
    });
}

인증서 전달

Azure

인증서 전달에 대한 Azure App Service를 구성하려면 Azure App Service에 대한 TLS 상호 인증 구성을 참조하세요. 다음 지침은 ASP.NET Core 앱 구성과 관련됩니다.

Startup.Configure에서 app.UseAuthentication(); 호출 앞에 다음 코드를 추가합니다.

app.UseCertificateForwarding();

인증서 전달 미들웨어를 구성하여 Azure를 사용하는 헤더 이름을 지정합니다. Startup.ConfigureServices에서 다음 코드를 추가하여 미들웨어가 인증서를 빌드하는 헤더를 구성합니다.

services.AddCertificateForwarding(options =>
    options.CertificateHeader = "X-ARR-ClientCert");

다른 웹 프록시

IIS 또는 Azure App Service의 ARR(애플리케이션 요청 라우팅)이 아닌 프록시를 사용하는 경우 HTTP 헤더에서 받은 인증서를 전달하도록 프록시를 구성합니다. Startup.Configure에서 app.UseAuthentication(); 호출 앞에 다음 코드를 추가합니다.

app.UseCertificateForwarding();

인증서 전달 미들웨어를 구성하여 헤더 이름을 지정합니다. Startup.ConfigureServices에서 다음 코드를 추가하여 미들웨어가 인증서를 빌드하는 헤더를 구성합니다.

services.AddCertificateForwarding(options =>
    options.CertificateHeader = "YOUR_CERTIFICATE_HEADER_NAME");

프록시가 Nginx에서와 같이 인증서를 base64 인코딩하지 않는 경우 HeaderConverter 옵션을 설정합니다. Startup.ConfigureServices에서 다음 예제를 참조하세요.

services.AddCertificateForwarding(options =>
{
    options.CertificateHeader = "YOUR_CUSTOM_HEADER_NAME";
    options.HeaderConverter = (headerValue) => 
    {
        var clientCertificate = 
           /* some conversion logic to create an X509Certificate2 */
        return clientCertificate;
    }
});

문제 해결

헤더가 예상대로 전달되지 않으면 로깅을 사용하도록 설정합니다. 로그가 문제를 해결하기에 충분한 정보를 제공하지 않으면 서버가 수신하는 요청 헤더를 열거합니다. 인라인 미들웨어를 사용하여 앱 응답에 요청 헤더를 쓰거나 헤더를 기록합니다.

앱 응답에 헤더를 기록하려면 Startup.Configure에서 UseForwardedHeaders 호출 바로 뒤에 다음 터미널 인라인 미들웨어를 넣습니다.

app.Run(async (context) =>
{
    context.Response.ContentType = "text/plain";

    // Request method, scheme, and path
    await context.Response.WriteAsync(
        $"Request Method: {context.Request.Method}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Scheme: {context.Request.Scheme}{Environment.NewLine}");
    await context.Response.WriteAsync(
        $"Request Path: {context.Request.Path}{Environment.NewLine}");

    // Headers
    await context.Response.WriteAsync($"Request Headers:{Environment.NewLine}");

    foreach (var header in context.Request.Headers)
    {
        await context.Response.WriteAsync($"{header.Key}: " +
            $"{header.Value}{Environment.NewLine}");
    }

    await context.Response.WriteAsync(Environment.NewLine);

    // Connection: RemoteIp
    await context.Response.WriteAsync(
        $"Request RemoteIp: {context.Connection.RemoteIpAddress}");
});

응답 본문 대신 로그에 기록할 수 있습니다. 로그에 기록하면 디버깅 중 사이트가 정상적으로 작동할 수 있습니다.

응답 본문이 아닌 로그를 기록하려면:

app.Use(async (context, next) =>
{
    // Request method, scheme, and path
    _logger.LogDebug("Request Method: {Method}", context.Request.Method);
    _logger.LogDebug("Request Scheme: {Scheme}", context.Request.Scheme);
    _logger.LogDebug("Request Path: {Path}", context.Request.Path);

    // Headers
    foreach (var header in context.Request.Headers)
    {
        _logger.LogDebug("Header: {Key}: {Value}", header.Key, header.Value);
    }

    // Connection: RemoteIp
    _logger.LogDebug("Request RemoteIp: {RemoteIpAddress}", 
        context.Connection.RemoteIpAddress);

    await next();
});

처리 시 X-Forwarded-{For|Proto|Host} 값이 X-Original-{For|Proto|Host}로 이동됩니다. 제공된 헤더에 여러 값이 있는 경우 전달된 헤더 미들웨어는 오른쪽에서 왼쪽으로 역순으로 헤더를 처리합니다. 기본 ForwardLimit1(일)이므로 ForwardLimit의 값을 늘리지 않는 한, 헤더에서 맨 오른쪽 값만 처리됩니다.

요청의 원래 원격 IP는 전달된 헤더가 처리되기 전에 KnownProxies 또는 KnownNetworks 목록의 항목과 일치해야 합니다. 이렇게 하면 신뢰할 수 없는 프록시에서 전달자가 허용되지 않아 헤더 스푸핑이 제한됩니다. 알 수 없는 프록시가 검색되면 로그에 프록시 주소가 기록됩니다.

September 20th 2018, 15:49:44.168 Unknown proxy: 10.0.0.100:54321

앞의 예제에서 10.0.0.100은 프록시 서버입니다. 서버가 신뢰할 수 있는 프록시인 경우 서버의 IP 주소를 Startup.ConfigureServicesKnownProxies에 추가합니다(또는 신뢰할 수 있는 네트워크를 KnownNetworks에 추가). 자세한 내용은 전달된 헤더 미들웨어 옵션 섹션을 참조하세요.

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

Important

신뢰할 수 있는 프록시 및 네트워크만 헤더를 전달할 수 있습니다. 그렇지 않을 경우 IP 스푸핑 공격이 가능합니다.

추가 리소스