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;
    }
}
這種方案比起以往的字符串驗證方案來講優雅了許多,並能夠提升數據庫查詢的效率,建議各位在往後的網站開發中都採用此方案。
相關文章
相關標籤/搜索