Asp.Net MVC 中的 Cookie(譯)

JoeSnail發表於2017-11-16

Asp.Net MVC 中的 Cookie(譯)

  • Cookie是請求伺服器或訪問Web頁面時攜帶的一個小的文字資訊。

Cookie為Web應用程式中提供了一種儲存特定使用者資訊的方法。Cookie的值是字串型別,且對使用者是可見的。

  • Cookie隨著每次RequestResponse在瀏覽器和伺服器之間交換資料。

如果一個使用者請求伺服器上的一個頁面,伺服器除了返回請求的頁面,也返回了一個包含日期和時間的Cookie。這個Cookie儲存在使用者硬碟上的一個資料夾上。稍後,如果使用者再次訪問伺服器,當使用者輸入URL時,瀏覽器會在本地硬碟上檢視與該URL相關聯的Cookie。如果Cookie存在,瀏覽器會將Cookie隨著請求一起傳送。然後,伺服器可以讀取傳送過來的Cookie資訊,使用者上次訪問該站點的日期和時間。您可以使用這些資訊向使用者顯示一條訊息,或者檢查一個過期日期。

  • Cookie與特定的站點關聯

Cookie與一個Web站點相關聯,而不是特定的頁面,因此不管使用者請求你伺服器的什麼頁面,瀏覽器和伺服器都會交換cookie資訊。 瀏覽器會為每個不同的Web站點分別儲存Cookie,保證每個Cookie對應特定的Web站點。

  • Cookie保持會話狀態

Cookie可以幫助伺服器儲存訪問者的資訊。通俗的說,Cookie是保持Web應用程式連續性的一種方式,即會話狀態管理。因為Http請求是無狀態的,一些列請求中伺服器並不知道請求來自哪些使用者,所以可以使用Cookie來唯一標識使用者,維護會話狀態。

Cookie用於許多目的,所有這些都與幫助網站記住使用者有關。例如,一個進行投票的站點可能會使用Cookie作為布林值來指示使用者的瀏覽器是否已經參與投票,這樣使用者就不能進行兩次投票。一個要求使用者登入的網站可能會使用Cookie來記錄使用者已經登入的情況,這樣使用者就不必繼續輸入憑證了。


Cookie限制

大多數瀏覽器支援最多4096位元組的Cookie。由於這個限制,cookie最好用於儲存少量資料,或者更好的是,像使用者ID這樣的識別符號。

瀏覽器也會限制Web網站在使用者的電腦上可以儲存多少Cookie。大多數瀏覽器只允許每個站點提供20個Cookie。如果你想儲存更多,那麼之前老的Cookie就會被丟棄。一些瀏覽器還會限制Cookie的總數量(不區分站點)。

儘管Cookie對Web應用程式非常有用,但是應用程式不應該依賴於Cookie。不要使用Cookie來支援關鍵敏感資料。


寫Cookie

瀏覽器負責管理使用者電腦上的Cookie。Cookie通過HttpResonse物件被髮送到瀏覽器,該物件公開了一個名為Cookies的集合。想要傳送到瀏覽器的任何Cookie都必須新增到這個集合中。在建立Cookie時,指定一個名稱和值。每個Cookie都必須有一個惟一的名稱,以便瀏覽器讀取時可以識別它。因為Cookie是按名稱儲存的,因此多個Cookie名稱相同時,新的Cookie值會覆蓋掉之前的。

可以設定Cookie的日期和過期時間。當使用者訪問寫Cookie的站點時,瀏覽器會刪除過期的Cookie。Cookie的有效期可以是50年。

如果沒有設定Cookie的過期時間,就建立了Cookie,那麼它並不會儲存在使用者的硬碟上。這種Cookie是作為使用者會話資訊的一部分來維護的。當使用者關閉瀏覽器時,cookie就會被丟棄。像這樣的非持久cookie對於需要在短時間記憶體儲的資訊非常有用,或者出於安全原因,不應該將其寫入到客戶機計算機上的磁碟上。例如,如果使用者正在使用公共計算機,而不希望將cookie寫入磁碟,則非永續性cookie非常有用。

您可以通過多種方式將Cookie新增到Cookie集合中。下面的例子展示了兩個編寫Cookie的方法:

//第一種
Response.Cookies["userName"].Value = "patrick";
Response.Cookies["userName"].Expires = DateTime.Now.AddDays(1);
//第二種
HttpCookie aCookie = new HttpCookie("lastVisit");
aCookie.Value = DateTime.Now.ToString();
aCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);

該示例將兩個Cookie新增到Cookie集合中,其中一個名為使用者名稱,另一個為上次訪問時間。對於第一種方法,Cookie集合的值可以直接讀寫。因為Cookie繼承自NameObjectCollectionBase集合型別,所以可以直接獲取Cookie。

對於第二種方法,建立一個HttpCookie物件的例項,設定它的屬性,然後通過Add方法將其新增到cookie集合中。Cookie的名稱通過建構函式新增。

這兩個示例都完成了相同的任務,將Cookie寫入瀏覽器。在這兩種方法中,過期值必須為DateTime型別。因為所有的Cookie值都以字串形式儲存,所以日期時間值會轉換為字串。

多個值的Cookie

Cookie可以儲存一個值,比如使用者名稱或最後一次訪問。也可以在一個Cookie中儲存多個鍵值對。鍵值對被稱為子鍵。(子鍵的佈局非常類似於URL中的查詢字串。)例如,您可以建立一個名為userInfo的cookie,它具有子健userName和lastVisit,而不是建立兩個單獨的Cookie。

子健可以將相關的或類似的資訊放入一個Cookie中。如果所有資訊都在一個Cookie中,Cookie屬性如過期將應用於所有資訊。(相反,如果您想為不同型別的資訊分配不同的過期日期,則應該將資訊儲存在單獨的cookie中。)

帶有子鍵的Cookie也可以幫助限制Cookie檔案的大小。之前提到,Cookie通常被限制為4096位元組,並且每個站點能儲存20多個Cookie。通過使用一個帶有子鍵的Cookie,可以降低站點Cookie數量的限制。此外,一個Cookie自身就佔用了大約50個字元(過期資訊,等等),加上您儲存在其中的值的長度,所有這些值都指向4096位元組的限制。如果您儲存5個子鍵而不是5個單獨的cookie,您可以節省單個Cookie的開銷,並且還可以節省大約200個位元組。

建立有子鍵的Cookie:

//第一種
Response.Cookies["userInfo"]["userName"] = "patrick";
Response.Cookies["userInfo"]["lastVisit"] = DateTime.Now.ToString();
Response.Cookies["userInfo"].Expires = DateTime.Now.AddDays(1);
//第二種
HttpCookie aCookie = new HttpCookie("userInfo");
aCookie.Values["userName"] = "patrick";
aCookie.Values["lastVisit"] = DateTime.Now.ToString();
aCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);

Cookie作用域

預設情況下,一個站點的有過期時間的所有Cookie都儲存在客戶硬碟上,請求該網站時所有的Cookie都被髮送到伺服器。換句話說,網站上的每一個頁面都能獲得該站點的所有Cookie。但是,您可以通過兩種方式來限制Cookie的範圍:

  • 將Cookie的範圍限制在伺服器上的一個資料夾中,這允許您將Cookie限制在站點上的一個應用程式中。

  • 將範圍設定為域,允許您指定域中的哪些子域可以訪問cookie。

要將cookie限制在伺服器上的一個資料夾中,可以設定cookie的路徑屬性,如下面的例子:

HttpCookie appCookie = new HttpCookie("AppCookie");
appCookie.Value = "written " + DateTime.Now.ToString();
appCookie.Expires = DateTime.Now.AddDays(1);
appCookie.Path = "/Application1";
Response.Cookies.Add(appCookie);

路徑既可以是站點根下的物理路徑,也可以是虛擬路徑。這樣做的效果是,Cookie只可用於Application1資料夾或虛擬根目錄中的頁面。例如,如果你的網站是www.contoso.com,在前面的示例中建立的Cookie可以與路徑http://www.contoso.com/Application1/頁面,該資料夾下任何頁面。然而,Cookie不會在其他應用程式中可用的頁面,比如http://www.contoso.com/Application2/或者http://www.contoso.com/

Cookie的限制和作用域

預設情況下,Cookie與特定的域相關聯。
例如,如果您的站點是www.contoso.com,那麼當使用者請求該站點的任何頁面時,您所寫的Cookie將被髮送到伺服器。
(這可能不包括帶有特定路徑值的cookie。)
如果你的站點有子域名,例如,contoso.com,sales.contoso.com,以及support.contoso.com,那麼你就可以把Cookies與特定的子域名聯絡起來。

程式碼示例:

Response.Cookies["domain"].Value = DateTime.Now.ToString();
Response.Cookies["domain"].Expires = DateTime.Now.AddDays(1);
Response.Cookies["domain"].Domain = "support.contoso.com";

當域以這種方式設定時,Cookie將只在指定子域中的頁面可用。您還可以使用域屬性來建立一個可以在多個子域中共享的Cookie,如下面的示例所示:

Response.Cookies["domain"].Value = DateTime.Now.ToString();
Response.Cookies["domain"].Expires = DateTime.Now.AddDays(1);
Response.Cookies["domain"].Domain = "contoso.com";

此時Cookie既可以主域,也可以用於sales.contoso.com和support.contoso.com等子域。


讀取Cookie

當瀏覽器向伺服器發出請求時,Cookie會隨著請求一起傳送到伺服器上。在你的應用程式,您可以使用HttpRequest來讀取cookie。

程式碼示例:

if(Request.Cookies["userName"] != null)
{
    HttpCookie aCookie = Request.Cookies["userName"];
    var cookiText = Server.HtmlEncode(aCookie.Value);
}

在嘗試獲取Cookie的值之前,應該確保Cookie存在;如果Cookie不存在,將得到NullReferenceException異常。還要注意,HtmlEncode方法被呼叫來對Cookie的內容進行編碼。這確保了惡意使用者沒有將可執行指令碼新增到Cookie中。

在Cookie中讀取子鍵的值也類似於設定它。下面的程式碼示例展示了獲取子鍵值的一種方法

if(Request.Cookies["userInfo"] != null)
{
    var name = 
        Server.HtmlEncode(Request.Cookies["userInfo"]["userName"]);

    var visit =
        Server.HtmlEncode(Request.Cookies["userInfo"]["lastVisit"]);
}

讀取鍵值對lastVisit的值,該值之前被設定為DateTime型別。但是Cookie將值儲存為字串,因此如果您想使用lastVisit的值作為日期,則必須將其轉換為適當的型別。

DateTime dt= DateTime.Parse(Request.Cookies["userInfo"]["lastVisit"]);

Cookie中的子鍵是NameValueCollection型別。因此,獲取子鍵的另一種方法是獲取子鍵集合,然後按名稱提取子鍵值,如下面的示例所示:

if(Request.Cookies["userInfo"] != null)
{
    System.Collections.Specialized.NameValueCollection  UserInfoCookieCollection;
    UserInfoCookieCollection = Request.Cookies["userInfo"].Values;
   var name = 
        Server.HtmlEncode(UserInfoCookieCollection["userName"]);
    var visit =
        Server.HtmlEncode(UserInfoCookieCollection["lastVisit"]);
}

Cookie的過期時間

瀏覽器負責管理Cookie,Cookie的過期時間和日期幫助瀏覽器管理其儲存的Cookie。因此,雖然您可以讀取Cookie的名稱和值,但是您不能讀取cookie的過期日期和時間。當瀏覽器向伺服器傳送Cookie資訊時,瀏覽器不包含過期資訊。(Cookie的過期屬性總是返回一個日期時間值為0。)如果您擔心cookie的過期日期,您必須重新設定它。

讀取Cookie集合

獲取所有的Cookie

程式碼示例;

System.Text.StringBuilder output = new System.Text.StringBuilder();
HttpCookie aCookie;
for(int i=0; i<Request.Cookies.Count; i++)
{
    aCookie = Request.Cookies[i];
    output.Append("Cookie name = " + Server.HtmlEncode(aCookie.Name) 
        + "<br />");
    output.Append("Cookie value = " + Server.HtmlEncode(aCookie.Value)
        + "<br /><br />");
}

上一個示例的有一個不足的地方,如果cookie有子鍵,會將子鍵作為單個name/value字串顯示。您可以讀取cookie的haskey屬性,以確定cookie是否有子鍵。如果有,您可以讀取子鍵集合以獲得單獨的子鍵名和值。
您也可以通過索引值直接從值集合中讀取子鍵值。相應的子鍵名可以在值集合的AllKeys成員中找到,它返回一個字串陣列。您還可以使用值集合的鍵值。但是,AllKeys屬性在第一次被訪問時被快取。相反,鍵屬性在每次訪問時都構建一個陣列。由於這個原因,在相同頁面請求的上下文中,AllKeys屬性要快得多。

下面的例子顯示了前面的示例的修改。它使用HasKeys屬性來測試子鍵,如果檢測到子鍵,那麼這個示例將從值集合中獲得子鍵:

for(int i=0; i<Request.Cookies.Count; i++)
{
    aCookie = Request.Cookies[i];
    output.Append("Name = " + aCookie.Name + "<br />");
    if(aCookie.HasKeys)
    {
        for(int j=0; j<aCookie.Values.Count; j++)
        {
            subkeyName = Server.HtmlEncode(aCookie.Values.AllKeys[j]);
            subkeyValue = Server.HtmlEncode(aCookie.Values[j]);
            output.Append("Subkey name = " + subkeyName + "<br />");
            output.Append("Subkey value = " + subkeyValue + 
                "<br /><br />");
        }
    }
    else
    {
        output.Append("Value = " + Server.HtmlEncode(aCookie.Value) +
            "<br /><br />");
    }
}

或者,您可以將子鍵提取為NameValueCollection物件,如下面的例子所示:

System.Text.StringBuilder output = new System.Text.StringBuilder();
HttpCookie aCookie;
string subkeyName;
string subkeyValue;

for (int i = 0; i < Request.Cookies.Count; i++)
{
    aCookie = Request.Cookies[i];
    output.Append("Name = " + aCookie.Name + "<br />");
    if (aCookie.HasKeys)
    {
        System.Collections.Specialized.NameValueCollection CookieValues = 
            aCookie.Values;
        string[] CookieValueNames = CookieValues.AllKeys;
        for (int j = 0; j < CookieValues.Count; j++)
        {
            subkeyName = Server.HtmlEncode(CookieValueNames[j]);
            subkeyValue = Server.HtmlEncode(CookieValues[j]);
            output.Append("Subkey name = " + subkeyName + "<br />");
            output.Append("Subkey value = " + subkeyValue + 
                "<br /><br />");
        }
    }
    else
    {
        output.Append("Value = " + Server.HtmlEncode(aCookie.Value) +
            "<br /><br />");
    }
}

Cookie的刪除和修改

修改Cookie

不能直接在獲取到Cookie後就修改Cookie的值,因為Cookie儲存在使用者的硬碟上(Request.Cookies["Key"].Value="some",在這裡是不起作用的),必須在Response.Cookies["Key"].Value="SomeNew"中修改才可以。其實就是伺服器中設定的Cookie新值,覆蓋了使用者瀏覽地上的Cookie的舊值。

程式碼示例:

int counter;
if (Request.Cookies["counter"] == null)
    counter = 0;
else
{
    counter = int.Parse(Request.Cookies["counter"].Value);
}
counter++;

Response.Cookies["counter"].Value = counter.ToString();
Response.Cookies["counter"].Expires = DateTime.Now.AddDays(1);

刪除Cookie

將Cookie從使用者的硬磁碟上刪除,和修改Cookie原理是一樣的。不能直接刪除Cookie,因為Cookie在使用者的硬碟上。但是,可以建立一個新Cookie,其名稱與要刪除的Cookie相同,但要將Cookie的過期時間設定為比今天更早的日期。當瀏覽器檢查Cookie的過期時間,瀏覽器將丟棄已經過時的Cookie。

程式碼示例:

HttpCookie aCookie;
string cookieName;
int limit = Request.Cookies.Count;
for (int i=0; i<limit; i++)
{
    cookieName = Request.Cookies[i].Name;
    aCookie = new HttpCookie(cookieName);
    aCookie.Expires = DateTime.Now.AddDays(-1);
    Response.Cookies.Add(aCookie);
}

子鍵的修改和刪除

修改一個單獨的子鍵與建立它是一樣的

如:

Response.Cookies["userInfo"]["lastVisit"] = DateTime.Now.ToString();
Response.Cookies["userInfo"].Expires = DateTime.Now.AddDays(1);

要刪除一個單獨的子鍵,您可以操作Cookie的值集合,該集合包含子鍵。
首先從Cookie集合中獲取一個指定的Cookie,然後呼叫該cookie值集合的remove方法,引數即為要刪除的子鍵名稱,再把該Cookie新增到集合中。

如下程式碼示例:

string subkeyName;
subkeyName = "userName";
HttpCookie aCookie = Request.Cookies["userInfo"];
aCookie.Values.Remove(subkeyName);
aCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(aCookie);

Cookies安全性

Cookie的安全性問題類似於從客戶端獲取資料。對於應用程式來說,可以把Cookie看作是另一種形式的使用者輸入。因為Cookie是儲存在使用者自己的硬碟上,所以Cookie對使用者來說是可見的,也是可以修改的。

因為Cookie對使用者是可見的所以不能在Cookie中儲存敏感資料,比如使用者名稱、密碼、信用卡號等等。

因為Cookie也是可修改的,所以對程式來說從Cookie獲取的資訊,也需要驗證和判斷。不能簡單的認為Cookie中的資料就和我們預期的資料一樣。

Cookie是在瀏覽器和伺服器之間是作為純文字傳送的,任何能夠攔截Web流量的人都可以攔截Cookie。但是可以設定Cookie只有在使用安全套接字層(SSL)的情況下才會傳輸Cookie,這樣在傳輸時會對Cookie加密。但是在使用者的硬碟上,Cookie就不受SSL保護了。


確定瀏覽器是否接受cookie

使用者可以設定瀏覽器拒絕Cookie。如果不能寫入Cookie,也不會丟擲錯誤。同樣,瀏覽器也不會向伺服器傳送關於當前Cookie設定的任何資訊。

客戶端驗證

最簡單的是使用JS來判斷

  if (navigator.cookieEnabled) {
        alert("Cookie 可用");
    }
    else {
        alert("Cookie 禁用");
    }

服務端驗證

cookie屬性並不表示是否啟用了cookie。它只表明當前瀏覽器本身是否支援cookie。

在服務端判斷Cookie是否被接受的一種方法是嘗試寫一個Cookie,然後再試著讀取該Cookie。如果不能讀取所編寫的Cookie,那麼就可以假定瀏覽器關閉了Cookie。


如有不對,請多多指教


參考:

相關文章