英語で読む

次の方法で共有


ASP.NET Core のクライアント IP セーフリスト

作成者: Damien BowdenTom Dykstra

この記事では、ASP.NET Core アプリに IP アドレスのセーフリスト (許可リストとも呼ばれます) を実装する 3 つの方法について説明します。 付属のサンプル アプリは、3 つの方法すべてを示しています。 使用できるもの:

  • ミドルウェアを使用して、すべての要求のリモート IP アドレスを確認します。
  • 特定のコントローラーまたはアクション メソッドに対する要求のリモート IP アドレスを確認する MVC アクション フィルター。
  • Razor ページに対する要求のリモート IP アドレスを確認する Razor Pages フィルター。

いずれの場合も、承認済みクライアントの IP アドレスを含む文字列はアプリの設定に格納されます。 ミドルウェアまたはフィルター:

  • 文字列を配列に解析します。
  • リモート IP アドレスが配列に存在するかどうかを確認します。

配列に IP アドレスが含まれている場合はアクセスが許可されます。 それ以外の場合は、HTTP 403 Forbidden 状態コードが返されます。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

IP アドレス セーフリスト

サンプル アプリの IP アドレス セーフリストは次のとおりです。

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

前の例では、IPv4 アドレスの 127.0.0.1192.168.1.5、IPv6 ループバック アドレスの ::1 (0:0:0:0:0:0:0:1 の圧縮形式) が許可されています。

ミドルウェア

Startup.Configure メソッドを使うと、カスタムの AdminSafeListMiddleware ミドルウェア型をアプリの要求パイプラインに追加できます。 セーフリストは、.NET Core 構成プロバイダーを使って取得され、コンストラクターのパラメーターとして渡されます。

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

ミドルウェアによって文字列は配列に解析され、配列内のリモート IP アドレスが検索されます。 リモート IP アドレスから見つからない場合、ミドルウェアから HTTP 403 Forbidden が返されます。 HTTP GET 要求の場合、この検証プロセスはバイパスされます。

C#
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 コントローラーまたはアクション メソッドに対してセーフリスト駆動型のアクセス制御が必要な場合は、アクション フィルターを使います。 次に例を示します。

C#
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 アクション フィルターが追加されています。 コンストラクター パラメーターとして、セーフリストとコンソール ロガー インスタンスが渡されます。

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

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

これで、[ServiceFilter] 属性を持つコントローラーまたはアクション メソッドにアクション フィルターを適用できるようになります。

C#
[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 フィルターを使います。 次に例を示します。

C#
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 フィルターが追加されています。 コンストラクター パラメーターとして、セーフリストとコンソール ロガー インスタンスが渡されます。

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

サンプル アプリの IndexRazor ページが要求されると、Razor Pages フィルターによってクライアントの IP アドレスが検証されます。 このフィルターによって、次のコンソール出力のバリエーションが生成されます。

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

その他の技術情報