程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> ASP.NET >> 關於ASP.NET >> Asp.net教程:設計IP地址屏蔽功能

Asp.net教程:設計IP地址屏蔽功能

編輯:關於ASP.NET

出於安全考慮,幾乎每個動態網站都具備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;

    }

}

這種方案比起以往的字符串驗證方案來說優雅了許多,並可以提高數據庫查詢的效率,建議各位在日後的網站開發中都采用此方案。

下載本文的PDF版本:http://www.box.net/shared/73kuz2bohe

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved