65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET IP地址阻止

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (34投票s)

2014年11月15日

CPOL

4分钟阅读

viewsIcon

69643

downloadIcon

1629

如何通过阻止 IP 地址来限制站点访问。

引言

有时您可能希望阻止用户(例如,向您的网站发送垃圾邮件的用户)根据其IP地址访问您的网站。本文演示了如何在 Global.asax 的 Application_BeginRequest() 事件中通过验证请求的IP地址来实现此目的。该解决方案支持 IPv4 和 IPv6 地址,并使用缓存来避免因每次页面请求都读取被阻止的IP地址而造成的瓶颈。

您也可以通过配置 IIS 来实现此目的,如微软知识库文章:如何:按 IP 地址或域名限制站点访问 中所述。

但是,如果您的网站托管在共享主机上,您可能无法访问 IIS 配置,在这种情况下,我提出的解决方案可能会派上用场。

基本功能

IPAddressBlocker 的工作原理如下。我们有一个包含被阻止IP地址的文件。IP地址可以是以完整地址的形式给出,也可以是以掩码的形式给出,例如 86.234.*.*。当触发 Application_BeginRequest() 事件时,将读取此文件并搜索 HttpContext.Current.Request.UserHostAddress 提供的IP地址。如果文件中的某个条目与IP地址匹配,则将用户重定向到一个页面,告知他们已被禁止访问该网站。被阻止的IP地址文件内容会被缓存,以提高性能。

包含被阻止IP地址的文件可以混合包含 IPv4 和 IPv6 格式的地址。文件内容示例如下:

85.154.90.243
85.234.50.*
213.100.*
2001:0db8:85a3:0000:0000:8a2e:0370:7334
2001:cdba:0000:0000:0000:0000:*

通配符 * 表示在与用户IP地址进行比较时,可以接受任何数字。这使得用单个条目阻止IP地址范围成为可能。

关于IP地址的一些说明

IP地址(Internet Protocol address)是分配给参与使用Internet协议进行通信的计算机(设备)的网络标签。IP地址有两个主要功能:主机或网络接口标识和位置寻址。目前普遍使用两种不同的IP地址格式:IPv4 和 IPv6。

IPv4

IPv4 或 Internet Protocol version 4 是 Internet Protocol (IP) 的第四个版本。IPv4 是第一个广泛分发的版本,也是互联网主要基于的版本。

IPv4 地址由 32 位组成,将协议限制在 4,294,967,296 个唯一地址,其中许多保留给特殊用途,如多播和本地网络。IPv6 是作为 IPv4 的继任者开发的,主要是因为 IPv4 中可用 IP 地址的空间有限。

IPv4 地址由 32 位组成,通常写成四个字节,中间用点分隔,即所谓的点分十进制表示法。例如 207.142.131.235。

IPv6

IPv4 和 IPv6 之间最大的区别在于地址的长度。IPv6 地址长 128 位。IPv6 地址通常由两个逻辑部分组成:一个 64 位网络前缀和一个 64 位本地部分。后者有时会使用网卡 MAC 地址自动生成。 

IPv6 地址通常写成八组四位字符。地址通常会伴随一个斜杠,然后是前缀的长度。例如:2001: 0db8: 85a3: 08d3: 1319: 8a2e: 0370: 7334/64 是一个有效的 IPv6 地址。

零序列可以通过写两个冒号并省略中间的零来缩短,使其更易于人类阅读。一组 16 位(或 4 个十六进制数字)的前导零可以被省略。例如,地址 2001: fe0c: 0000: 0000: 0000: 0000: 00db: 1dc0 可以写成 2001: fe0c :: db: 1dc0。该过程在 RFC 4291 中有详细描述。

此软件仅 支持完全展开的 IPv6 地址,不支持缩短格式。

代码

Global.asax 文件

首先,我们在 Global.asax 文件中添加以下事件处理程序。

    protected void Application_BeginRequest(Object sender, EventArgs e)
    {

        BlockedIpHandler biph = new BlockedIpHandler();
        if (biph.IsIpBlocked(HttpContext.Current.Request.UserHostAddress))
        {
            Server.Transfer("~/banned.aspx");
        }

    }   

当从网站加载页面时,会触发此事件。如果 Request.UserHostAddress 检索到的IP地址被阻止,则会将用户重定向到 banned.aspx 页面。

BlockedIpHandler.cs 文件

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Caching;
using System.Web.Hosting;
using System.Net;

/// <summary>
/// Handler providing functionality for blocking user access 
/// to a web site based on IP-address.
/// </summary>
public class BlockedIpHandler
{
    // Define constants
    private const string FILE_PATH = "~\\banned.txt";
    private const double CACHE_EXPIRATION = 30.0; // Seconds

    // Define member variables
    private FileContents _fileContents;

    // Constructor
    public BlockedIpHandler()
    {
        _fileContents = ReadBannedIpListFile();
    }

    public bool IsIpBlocked(string ip)
    {
        IPAddress ipAddress;

        if (IPAddress.TryParse(ip, out ipAddress)) {

            if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) {
                // IPv4 address
                string[] ipParts = ip.Split('.');

                foreach (string banned in _fileContents.Ipv4Masks) {
                    string[] blockedParts = banned.Split('.');
                    if (blockedParts.Length > 4) continue; // Not valid IP mask.

                    if (IsIpBlocked(ipParts, blockedParts)) {
                        return true;
                    }
                }
            }
            else if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) {
                // IPv6 address
                string[] ipParts = ExpandIpv6Address(ipAddress).Split(':');

                foreach (string banned in _fileContents.Ipv6Masks) {
                    string bannedIP = banned.Split('/')[0]; // Take IP address part.
                    string[] blockedParts = bannedIP.Split(':');
                    if (blockedParts.Length > 8) continue; // Not valid IP mask.

                    if (IsIpBlocked(ipParts, blockedParts)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    private bool IsIpBlocked(string[] ipParts, string[] blockedIpParts)
    {
        for (int i = 0; i < blockedIpParts.Length; i++) {
            // Compare if not wildcard
            if (blockedIpParts[i] != "*") {
                // Compare IP address part
                if (ipParts[i] != blockedIpParts[i].ToLower()) {
                    return false;
                }
            }
        }

        return true;
    }

    private string ExpandIpv6Address(IPAddress ipAddress)
    {
        string expanded = "", separator = "";
        byte[] bytes = ipAddress.GetAddressBytes();

        for (int i = 0; i < bytes.Length; i += 2) {
            expanded += separator + bytes[i].ToString("x2");
            expanded += bytes[i + 1].ToString("x2");
            separator = ":";
        }

        return expanded;
    }

    private FileContents ReadBannedIpListFile()
    {

        ObjectCache cache = MemoryCache.Default;
        FileContents fileContents = cache["filecontents"] as FileContents;

        if (fileContents == null)
        {
            FileContents tempFileContents = new FileContents();

            string cachedFilePath = HostingEnvironment.MapPath(FILE_PATH);
            if (File.Exists(cachedFilePath)) 
            {
                List<string> filePaths = new List<string>();
                filePaths.Add(cachedFilePath);

                CacheItemPolicy policy = new CacheItemPolicy();
                policy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(CACHE_EXPIRATION);
                policy.ChangeMonitors.Add(new HostFileChangeMonitor(filePaths));

                List<string> tempIpv4List = new List<string>();
                List<string> tempIpv6List = new List<string>();

                // Read the file line by line.
                using (StreamReader file = new StreamReader(cachedFilePath)) {
                    string line;
                    while ((line = file.ReadLine()) != null) {
                        if (line.Contains(".")) {
                            tempIpv4List.Add(line);
                        }
                        else if (line.Contains(":")) {
                            tempIpv6List.Add(line);
                        }
                    }
                }

                tempFileContents.Ipv4Masks = tempIpv4List.ToArray();
                tempFileContents.Ipv6Masks = tempIpv6List.ToArray();

                cache.Set("filecontents", tempFileContents, policy);
            }

            fileContents = tempFileContents;
        }

        return fileContents;
    }
}

public class FileContents
{
    public string[] Ipv4Masks = new string[0];
    public string[] Ipv6Masks = new string[0];
}

代码相当 直接,但这里有一些注意事项。首先,我们声明了包含被阻止 IP 地址的文件路径和缓存超时的常量。IsIpBlocked() 方法是公共的,并从 Global.asax 文件中的事件处理程序调用。该方法首先确定给定的地址是 IPv4 还是 IPv6,然后将地址与文件中的地址掩码进行比较。

ExpandIpv6Address() 函数创建 IPv6 地址的展开版本。这意味着所有零都会被写出来,这与 .NET Framework 的IPAddress::ToString() 方法不同,该方法返回 IP 地址的压缩版本。

ReadBannedIpListFile() 读取包含被阻止 IP 地址的文件,如果文件找到的话。如果找不到,则返回一个空的FileContents对象。文件内容被存储在缓存中,因此如果在CACHE_EXPIRATION时间内读取了文件,则会返回缓存的数据。读取时,文件按 IPv4 和 IPv6 地址排序,以加快查找速度。

FileContents 类包含ReadBannedIpListFile() 函数返回的文件内容,并且也存储在缓存中。

关于代码,我能说的就这些。您可以将代码下载为网站项目并在您自己的计算机上进行测试。

历史

  • 2014年11月15日:文章首次发布。
  • 2014年11月17日:IPv6 地址现在不区分大小写。

 

© . All rights reserved.