ASP.NET IP地址阻止






4.83/5 (34投票s)
如何通过阻止 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 地址现在不区分大小写。