Asp.net教程:设计IP地址屏蔽功能

出于安全考虑,几乎每一个动态网站都具有IP 地址屏蔽功能,而网上流传的不少关于该功能的教程大都采用字符串保存和验证IP 地址,我认为这是不太科学的,我试图找到最佳的设计方案。
IP 地址的长度为32 位,分为4 段,每段8 位,用十进制数字表示,每段数字范围为0 255 ,段与段之间用句点隔开。”
由此咱们了解到,IP 地址其实是一个32 位正整数,在C# 中可使用 uint 类型来表示,但SQLServer 数据库里好像没有对应的类型;转而使用数据库支持的 int 类型的话,则会出现溢出的状况;所以咱们作出妥协:使用 long( bigint) 类型。
TIP:
int 取值范围:-2,147,483,648 到 2,147,483,647
uint 取值范围:0 到 4,294,967,295
long 取值范围:-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
 
那么如何将IP 地址转为整数呢?咱们看到IPAddress 类中有一个“[ 否决的] ”实例属性Address ,这个属性的确能够返回一个 long 值,可是测试一下,获得的数据确实这样的:
127.0.0.1 -> 16777343
127.0.0.2 –> 33554559
的确该让它“否决”,这样的整数对咱们来讲毫无心义,咱们是没法经过这样的方法比较传入的IP 是否介于两个IP 值之间的。
那么只有本身动手了,咱们将经过IPAddress 类的GetAddressBytes() 实例方法获取IP 4 个段的值,而后将它们组合为一个整数,下面将提供这个扩展方法:
/// <summary>
/// IP 地址转为整数形式
/// </summary>
/// <returns> 整数</returns>
public static long 转换为整数( this IPAddress ip)
{
    int x = 3;
    long o = 0;
    foreach (byte f in ip.GetAddressBytes())
    {
        o += (long)f << 8 * x--;
    }
    return o;
}
你能够这样使用这个扩展方法:
IPAddress .Parse("127.0.0.1"). 转换为整数()
这里还有一个用于逆转换的扩展方法,用于将 long 转回IPAddress
/// <summary>
/// 将整数转为 IP 地址
/// </summary>
/// <returns> IP 地址</returns>
public static IPAddress 转换为IP 地址( this long l)
{
    var b = new byte[4];
    for (int i = 0; i < 4; i++)
    {
        b[3 - i] = (byte)(l >> 8 * i & 255);
    }
    return new IPAddress(b);
}
这样咱们就能够经过计算获得正确并有意义的整数了:
127.0.0.1 -> 2130706433
127.0.0.2 –> 2130706434
OK ,确立了方案核心,下面开始设计SQLServer 数据表:

这样设计后,在添加时将起始和终止IP 地址转为long 类型并存入,并指定一个过时时间。
在验证时只须要获取全部未过时的条目,比较传入的IP 地址是否介于起始值和终止值之间便可。
以往经过字符串存储和验证的方案中,屏蔽时要么屏蔽一个精确的IP 地址,要么就屏蔽一段或两段IP ,如“192.168.*.* ”,要想屏蔽“192.168.1.200 ”到“192.168.4.64 ”之间的IP 的话,将会很是麻烦;
而咱们这样设计就能够轻松实现:“192.168.1.200 ”在数据库里存储的是“3232235976 ”,“192.168.4.64 ”在数据库中是“3232236608 ”,即便使用肉眼也能极快地判断传入的地址是否介于它们之间,更不要说计算机查询了。
下面为数据表生成EDM 模型:

添加IP 屏蔽记录的代码:
/// <summary>
/// 添加一个新的 IP 屏蔽区段
/// </summary>
/// <param name="IP 区段起始值"> 起始 IP ,如 61.51.200.0</param>
/// <param name="IP 区段终止值"> 终止 IP ,如 61.51.255.255</param>
/// <param name=" 过时时间"> 屏蔽截止时间</param>
/// <returns> ID </returns>
public static Guid 添加( string IP 区段起始值, string IP 区段终止值, DateTime 过时时间)
{
    var id = Guid.NewGuid();
    var sip = IPAddress.Parse(IP 区段起始值) . 转换为整数();
    var eip = IPAddress.Parse(IP 区段终止值) . 转换为整数();
    using (var c = new SiteMainEntities())
    {
        // 检测是否已存在相同的 IP 屏蔽记录
        var a = c.IP 地址屏蔽 .Where(f => f. 区段起始值 == sip && f. 区段终止值 == eip);
        // 若是存在则更新其过时时间
        if (a.Count()>0)
        {
            var l = a.First();
            if (l. 过时时间 < 过时时间 ) l. 过时时间 = 过时时间;
        }
        // 不存在则正常添加一个新的屏蔽记录
        else c.AddToIP 地址屏蔽( new IP 地址屏蔽 { ID = id, 过时时间 = 过时时间 , 区段起始值 = sip, 区段终止值 = eip });
        c.SaveChanges();
    }
    return id;
}
检测指定IP 地址是否被屏蔽的代码:
/// <summary>
/// 检测指定 IP 地址是否已受到屏蔽
/// </summary>
/// <param name="IP 地址"> 要检测的 IP 地址</param>
/// <returns> 是否属于已屏蔽的 IP</returns>
public static bool 检测是否被屏蔽( string IP 地址)
{
    var ip = IPAddress.Parse(IP 地址) . 转换为整数();
    using (var c = new SiteMainEntities())
    {
        return c.IP 地址屏蔽 .Count(f => f. 过时时间 > DateTime.Now && ip >= f. 区段起始值 && ip <= f. 区段终止值) > 0;
    }
}
这种方案比起以往的字符串验证方案来讲优雅了许多,并能够提升数据库查询的效率,建议各位在往后的网站开发中都采用此方案。
相关文章
相关标签/搜索