ASP.NET Core에 대한 클라이언트 IP 안전 목록

작성자: Damien BowdenTom Dykstra

이 문서에서는 ASP.NET Core 앱에서 IP 주소 안전 목록(허용 목록이라고도 함)을 구현하는 세 가지 방법을 보여 줍니다. 함께 제공되는 샘플 앱은 세 가지 방법을 모두 보여 줍니다. 다음을 사용할 수 있습니다.

  • 모든 요청의 원격 IP 주소를 확인하는 미들웨어입니다.
  • MVC 작업 필터는 특정 컨트롤러 또는 작업 메서드에 대한 요청의 원격 IP 주소를 확인합니다.
  • Razor Pages 필터는 Razor 페이지에 대한 요청의 원격 IP 주소를 확인합니다.

각각의 경우 승인된 클라이언트 IP 주소를 포함하는 문자열은 앱 설정에 저장됩니다. 미들웨어 또는 필터:

  • 문자열을 배열로 구문 분석합니다.
  • 원격 IP 주소가 배열에 있는지 확인합니다.

배열에 IP 주소가 포함된 경우 액세스가 허용됩니다. 그렇지 않으면 HTTP 403 금지됨 상태 코드가 반환됩니다.

샘플 코드 보기 및 다운로드(다운로드 방법)

IP 주소 안전 목록

샘플 앱에서 IP 주소 안전 목록은 다음과 같습니다.

{
  "AdminSafeList": "127.0.0.1;192.168.1.5;::1",
  "Logging": {

앞의 예제에서는 127.0.0.1192.168.1.5의 IPv4 주소와 ::1의 IPv6 루프백 주소(0:0:0:0:0:0:0:1의 압축된 형식)가 허용됩니다.

미들웨어

Startup.Configure 메서드는 사용자 지정 AdminSafeListMiddleware 미들웨어 형식을 앱의 요청 파이프라인에 추가합니다. 안전 목록은 .NET Core 구성 공급자를 통해 검색되고 생성자 매개 변수로 전달됩니다.

app.UseMiddleware<AdminSafeListMiddleware>(Configuration["AdminSafeList"]);

미들웨어는 문자열을 배열로 구문 분석하고 배열에서 원격 IP 주소를 검색합니다. 원격 IP 주소를 찾을 수 없는 경우 미들웨어는 HTTP 403 금지됨을 반환합니다. 이 유효성 검사 프로세스는 HTTP GET 요청에 대해 무시됩니다.

public class AdminSafeListMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<AdminSafeListMiddleware> _logger;
    private readonly byte[][] _safelist;

    public AdminSafeListMiddleware(
        RequestDelegate next,
        ILogger<AdminSafeListMiddleware> logger,
        string safelist)
    {
        var ips = safelist.Split(';');
        _safelist = new byte[ips.Length][];
        for (var i = 0; i < ips.Length; i++)
        {
            _safelist[i] = IPAddress.Parse(ips[i]).GetAddressBytes();
        }

        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        if (context.Request.Method != HttpMethod.Get.Method)
        {
            var remoteIp = context.Connection.RemoteIpAddress;
            _logger.LogDebug("Request from Remote IP address: {RemoteIp}", remoteIp);

            var bytes = remoteIp.GetAddressBytes();
            var badIp = true;
            foreach (var address in _safelist)
            {
                if (address.SequenceEqual(bytes))
                {
                    badIp = false;
                    break;
                }
            }

            if (badIp)
            {
                _logger.LogWarning(
                    "Forbidden Request from Remote IP address: {RemoteIp}", remoteIp);
                context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
                return;
            }
        }

        await _next.Invoke(context);
    }
}

작업 필터

특정 MVC 컨트롤러 또는 작업 메서드에 대한 안전 목록 기반 액세스 제어를 원하는 경우 작업 필터를 사용합니다. 예시:

public class ClientIpCheckActionFilter : ActionFilterAttribute
{
    private readonly ILogger _logger;
    private readonly string _safelist;

    public ClientIpCheckActionFilter(string safelist, ILogger logger)
    {
        _safelist = safelist;
        _logger = logger;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var remoteIp = context.HttpContext.Connection.RemoteIpAddress;
        _logger.LogDebug("Remote IpAddress: {RemoteIp}", remoteIp);
        var ip = _safelist.Split(';');
        var badIp = true;
        
        if (remoteIp.IsIPv4MappedToIPv6)
        {
            remoteIp = remoteIp.MapToIPv4();
        }
        
        foreach (var address in ip)
        {
            var testIp = IPAddress.Parse(address);
            
            if (testIp.Equals(remoteIp))
            {
                badIp = false;
                break;
            }
        }

        if (badIp)
        {
            _logger.LogWarning("Forbidden Request from IP: {RemoteIp}", remoteIp);
            context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
            return;
        }

        base.OnActionExecuting(context);
    }
}

Startup.ConfigureServices에서 MVC 필터 컬렉션에 작업 필터를 추가합니다. 다음 예제에서는 ClientIpCheckActionFilter 작업 필터가 추가됩니다. 안전 목록 및 콘솔 로거 인스턴스는 생성자 매개 변수로 전달됩니다.

services.AddScoped<ClientIpCheckActionFilter>(container =>
{
    var loggerFactory = container.GetRequiredService<ILoggerFactory>();
    var logger = loggerFactory.CreateLogger<ClientIpCheckActionFilter>();

    return new ClientIpCheckActionFilter(
        Configuration["AdminSafeList"], logger);
});
services.AddScoped<ClientIpCheckActionFilter>(_ =>
{
    var logger = _loggerFactory.CreateLogger<ClientIpCheckActionFilter>();
    
    return new ClientIpCheckActionFilter(
        Configuration["AdminSafeList"], logger);
});

그러면 [ServiceFilter] 특성을 사용하여 컨트롤러 또는 작업 메서드에 작업 필터를 적용할 수 있습니다.

[ServiceFilter(typeof(ClientIpCheckActionFilter))]
[HttpGet]
public IEnumerable<string> Get()

샘플 앱에서 작업 필터는 컨트롤러의 Get 작업 메서드에 적용됩니다. 다음을 전송하여 앱을 테스트하는 경우:

  • HTTP GET 요청인 [ServiceFilter] 특성은 클라이언트 IP 주소의 유효성을 검사합니다. Get 작업 메서드에 대한 액세스가 허용되는 경우 작업 필터 및 작업 메서드에 의해 다음 콘솔 출력의 변형이 생성됩니다.

    dbug: ClientIpSafelistComponents.Filters.ClientIpCheckActionFilter[0]
          Remote IpAddress: ::1
    dbug: ClientIpAspNetCore.Controllers.ValuesController[0]
          successful HTTP GET    
    
  • GET 이외의 HTTP 요청 동사인 AdminSafeListMiddleware 미들웨어는 클라이언트 IP 주소의 유효성을 검사합니다.

Razor Pages 필터

Razor Pages 앱에 대한 안전 목록 기반 액세스 제어를 원하는 경우 Razor Pages 필터를 사용합니다. 예시:

public class ClientIpCheckPageFilter : IPageFilter
{
    private readonly ILogger _logger;
    private readonly IPAddress[] _safelist;

    public ClientIpCheckPageFilter(
        string safelist,
        ILogger logger)
    {
        var ips = safelist.Split(';');
        _safelist = new IPAddress[ips.Length];
        for (var i = 0; i < ips.Length; i++)
        {
            _safelist[i] = IPAddress.Parse(ips[i]);
        }

        _logger = logger;
    }

    public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
    {
        var remoteIp = context.HttpContext.Connection.RemoteIpAddress;
        if (remoteIp.IsIPv4MappedToIPv6)
        {
            remoteIp = remoteIp.MapToIPv4();
        }
        _logger.LogDebug(
            "Remote IpAddress: {RemoteIp}", remoteIp);

        var badIp = true;
        foreach (var testIp in _safelist)
        {
            if (testIp.Equals(remoteIp))
            {
                badIp = false;
                break;
            }
        }

        if (badIp)
        {
            _logger.LogWarning(
                "Forbidden Request from Remote IP address: {RemoteIp}", remoteIp);
            context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
            return;
        }
    }

    public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
    {
    }

    public void OnPageHandlerSelected(PageHandlerSelectedContext context)
    {
    }
}

Startup.ConfigureServices에서 Razor Pages 필터를 MVC 필터 컬렉션에 추가하여 활성화합니다. 다음 예제에서는 ClientIpCheckPageFilterRazor Pages 필터가 추가됩니다. 안전 목록 및 콘솔 로거 인스턴스는 생성자 매개 변수로 전달됩니다.

services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        var logger = LoggerFactory.Create(builder => builder.AddConsole())
                        .CreateLogger<ClientIpCheckPageFilter>();
        var filter = new ClientIpCheckPageFilter(
            Configuration["AdminSafeList"], logger);
        
        options.Filters.Add(filter);
    });
services.AddMvc(options =>
{
    var logger = _loggerFactory.CreateLogger<ClientIpCheckPageFilter>();
    var clientIpCheckPageFilter = new ClientIpCheckPageFilter(
        Configuration["AdminSafeList"], logger);
    
    options.Filters.Add(clientIpCheckPageFilter);
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

샘플 앱의 인덱스Razor 페이지가 요청되면 페이지 필터는 Razor 클라이언트 IP 주소의 유효성을 검사합니다. 필터는 다음 콘솔 출력의 변형을 생성합니다.

dbug: ClientIpSafelistComponents.Filters.ClientIpCheckPageFilter[0]
      Remote IpAddress: ::1

추가 리소스