Cookie


前面的話

  cookie是一種早期的客戶端存儲機制,起初是針對服務器端腳本設計使用的,只適合存儲少量文本數據。從最底層來看,作為HTTP協議的一種擴展實現它。cookie數據會自動在Web瀏覽器和Web服務器之間傳輸,因此服務端腳本就可以讀、寫存儲在客戶端的cookie的值。任何以cookie形式存儲的數據,不論服務器端是否需要,每一次HTTP請求都會把這些數據傳輸到服務器端。cookie目前仍然被客戶端程序員大量使用的一個重要原因是:所有新舊瀏覽器都支持它。但是,隨着WebStorage的普及,cookie終將會回歸到最初的形態:作為一種被服務端腳本使用的客戶端存儲機制。本文將詳細介紹Cookie

 

概述

  “cookie”這個名字沒有太多的含義,但是在計算機歷史上其實很早就用到它了。“cookie”和“magic cookie”用於代表少量數據,特別是指類似密碼這種用於識別身份或者許可訪問的保密數據。在javascript中,cookie用於保存狀態以及能夠為Web瀏覽器提供一種身份識別機制。但是,javascript中使用cookie不會采用任何加密機制,因此它們是不安全的。但是,通過https來傳輸cookie數據是安全的,不過這和cookie本身無關,而和https協議相關

  HTTP Cookie,通常直接叫做cookie,最初是在客戶端用於存儲會話信息的。該標准要求服務器對任意HTTP請求發送Set-Cookie HTTP頭作為響應的一部分,其中包含會話信息。例如,這種服務器響應的頭可能如下

HTTP/1.1 200 OK
Content-type: Text/html
Set-Cookie: name=value
Other-header: other-header-value

  這個HTTP響應設置以name為名稱、以value為值的一個cookie,名稱和值在傳送時都必須是URL編碼的。瀏覽器會存儲這樣的會話信息,並在這之后,通過為每個請求添加Cookie HTTP頭將信息發送回服務器,如下所示:

GET /index.html HTTP/1.1
Cookie: name=value
Other-header: other-header-value

  發送回服務器的額外信息可以用於唯一驗證客戶來自於發送的哪個請求

 

標識

  瀏覽器默認打開Cookie功能。window.navigator.cookieEnabled屬性返回一個布爾值,表示瀏覽器是否打開Cookie功能

console.log(window.navigator.cookieEnabled);//true

  可以通過瀏覽器的一些設置將cookie功能關閉

console.log(window.navigator.cookieEnabled);//false

  此時,客戶端本地將不再存儲任何cookie

 

限制

  cookie在性質上是綁定在特定的域名下的。當設定了一個cookie后,再給創建它的域名發送請求時,都會包含這個cookie。這個限制確保了儲存在cookie中的信息只能讓批准的接受者訪問,而無法被其他域訪問

  [注意]不同的瀏覽器存入的cookie位置不一樣,不能通用

  由於cookie是存在客戶端計算機上的,還加入了一些限制確保cookie不會被惡意使用,同時不會占據太多磁盤空間。每個域的cookie總數是有限的,不過瀏覽器之間各有不同。如下所示

  IE6-瀏覽器限制每個域名最多20個cookie

  IE7+瀏覽器限制每個域名最多50個。IE7最初是支持每個域名最大20個cookie,之后被微軟的一個補丁所更新

  Firefox限制每個域最多50個cookie

  Opera限制每個域最多30個cookie

  Safari和Chrome對於每個域的cookie數量限制沒有硬性規定

  當超過單個域名限制之后還要再設置cookie,瀏覽器就會清除以前設置的cookie。IE和Opera會刪除最近最少使用過的(LRU, LeastRecentlyUsed)cookie,騰出空間給新設置的cookie。Firefox看上去好像是隨機決定要清除哪個cookie,所以考慮cookie限制非常重要,以免出現不可預期的后果

  瀏覽器中對於cookie的尺寸也有限制。大多數瀏覽器都有大約4096B(加減1)的長度限制。為了最佳的瀏覽器兼容性,最好將整個cookie長度限制在4095B(含4095)以內。尺寸限制影響到一個域下所有的cookie,而並非每個cookie單獨限制

  如果嘗試創建超過最大尺寸限制的cookie,那么該cookie會被悄無聲息地丟掉。注意,雖然一個字符串常占用一字節,但是多字節情況則有不同

【同源】

  兩個網址只要域名相同和端口相同,就可以共享Cookie。注意,這里不要求協議相同

  也就是說,http://example.com設置的Cookie,可以被https://example.com讀取

 

組成

  cookie由瀏覽器保存的以下7塊信息構成

Set-Cookie: name=value[; expires=date][; max-age=secondes][; domain=domain][; path=path][; secure]

  1、名稱:唯一確定cookie的名稱。cookie名稱是不區分大小寫的,所以myCookie和MyCookie被認為是同一個cookie。然而,實踐中最好將cookie名稱看作是區分大小寫的,因為某些服務器會這樣處理cookie。cookie的名稱必須是經過URL編碼的

  2、值:儲存在cookie中的字符串值。值必須被URL編碼

  3、域:cookie對於哪個域是有效的。所有向該域發送的請求中都會包含這個cookie信息。這個值可以包含子域(subdomain,如www.wrox.com),也可以不包含它(如.wrox.com,則對於wrox.com的所有子域都有效)。如果沒有明確設定,那么這個域會被認作來自設置cookie的那個域

  4、路徑:對於指定域中的路徑,必須是絕對路徑(比如/、/books),如果未指定,默認為請求該Cookie的網頁路徑。例如,可以指定cookie只有從"http://www.wrox.com/books/"中才能訪問,那么http://www.wrox.com的頁面就不會發送cookie信息,即使請求都是來自同一個域的

  [注意]這里的匹配不是絕對匹配,而是從根路徑開始,只要path屬性匹配發送路徑的一部分,就可以發送。比如,path屬性等於/blog,則發送路徑是/blog或者/blogroll,Cookie都會發送。path屬性生效的前提是domain屬性匹配

  5、失效時間(expires):表示cookie何時應該被刪除的時間戳(也就是,何時應該停止向服務器發送這個cookie)。默認情況下,瀏覽器會話結束時即將所有cookie刪除;不過也可以自己設置刪除時間。這個值是個GMT格式的日期(Wdy,DD-Mon-YYYY HH:MM:SS GMT),用於指定應該刪除cookie的准確時間。因此,cookie可在瀏覽器關閉后依然保存在用戶的機器上。如果設置的失效日期是個以前的時間,則cookie會被立刻刪除

document.cookie = "a = 2; expires = " + (new Date( +new Date() + 4000*60*60*24 )).toUTCString();

  [注意1]必須使用toUTCString()或者toGMTString(),如果使用toString()會因為時區問題,導致時間設置錯誤

  [注意2]瀏覽器根據本地時間,決定Cookie是否過期,由於本地時間是不精確的,所以沒有辦法保證Cookie一定會在服務器指定的時間過期

  6、有效期(max-age):表示cookie有效期為多久,單位為秒(s)

document.cookie = "b = 3; max-age=60";

  7、安全標志:指定后,cookie只有在使用SSL連接的時候才發送到服務器。例如,cookie信息只能發送給"https:www.wrox.com",而"http:www.wrox.com"的請求則不能發送cookie

  每一段信息都作為Set-Cookie頭的一部分,使用分號加空格分隔每一段,如下所示

HTTP/1.1 200 0K
Content-type: text/html
Set-Cookie: name=value; expires=Mon, 22-Jan-17 07:10:24 GMT; domains=.wrox.com
Other-header: other-header-value

  該頭信息指定了一個叫做name的cookie,它會在格林威治時間2017年1月22日7:10:24失效,同時對於www.wrox.com和wrox.com的任何子域(如p2p.wrox.com)都有效

  secure標志是cookie中唯一一個非名值對兒的部分,直接包含一個secure單詞。如下所示

HTTP/1.1 200 0K
Content-type: text/html
Set-Cookie: name=value; domain=.wrox.com; path=/; secure
Other-header: other-header-value

  這里,創建了一個對於所有wrox.com的子域和域名下(由path參數指定的)所有頁面都有效的cookie。因為設置了secure標志,這個cookie只能通過SSL連接才能傳輸

  [注意]域、路徑、失效時間、有效期和安全標志都是服務器給瀏覽器的指示,以指定何時應該發送cookie。這些參數並不會作為發送到服務器的cookie信息的一部分,只有名值對兒才會被發送

 

讀取

  通過document.cookie屬性可以獲取cookie的值,其返回值是一個字符串,該字符串都是由一系列名值對兒組成,不同名/值對之間通過“分號和空格”分開,其內容包含了所有作用在當前文檔的cookie。但是,它並不包含其他設置的cookie屬性

document.cookie = "name=match; domain=127.0.0.1; path=/test";
console.log(document.cookie);//'age=32; name=match'

  但是為了更好地査看cookie的值,一般會采用split()方法將cookie值中的名/值對都分離出來

  把cookie的值從cookie屬性分離出來之后,必須要采用相應的解碼方式(取決於之前存儲cookie值時采用的編碼方式),把值還原出來。比如,先采用decodeURIComponent()方法把cookie值解碼出來,之后再利用JSON.parse()方法轉化成json對象

  下面定義了一個getCookie()函數,該函數將document.cookie屬性的值解析出來

function getCookie(key){
    var arr1 = document.cookie.split("; ");
    for(var i = 0; i < arr1.length; i++){
        var arr2 = arr1[i].split("=");
        if(arr2[0] == key){
            return decodeURIComponent(arr2[1]);
        }
    }
}

console.log(getCookie('name'));//'match'
console.log(getCookie('age'));//'32'

 

設置

  當用於設置值的時候,document.cookie屬性可以設置為一個新的cookie字符串。這個cookie字符串會被解釋並添加到現有的cookie集合中。設置document.cookie並不會覆蓋cookie,除非設置的cookie的名稱已經存在。設置cookie的格式如下,和Set-Cookie頭中使用的格式一樣

name=value;expires=expiration_time;path=domain_path; domain=domain_name;secure 

  這些參數中,只有cookie的名字和值是必需的。這段代碼創建了一個叫name的cookie,值為Nicholas。當客戶端每次向服務器端發送請求的時候,都會發送這個cookie; 當瀏覽器關閉的時候,它就會被刪除

document.cookie = "name=Nicholas";

  以簡單的名/值對形式存儲的cookie數據有效期只在當前Web瀏覽器的會話內,一旦用戶關閉瀏覽器,cookie數據就丟失了。如果想要延長cookie的有效期,就需要設置max-age屬性來指定cookie的有效期(單位是秒)。按照如下的字符串形式設置cookie屬性即可:

name=value;max-age=seconds

  由於cookie的名/值中的值是不允許包含分號、逗號和空白符,因此,在存儲前一般可以采用encodeURIComponent()對值進行編碼。相應的,讀取cookie值的時候需要采用decodeURIComponent()函數解碼

  [注意]與這兩個函數相當於的PHP的編解碼函數是urlencode()和urldecode()

document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas");

  要給被創建的cookie指定額外的信息,只要將參數追加到該字符串,和Set-Cookie頭中的格式一樣,如下所示

document.cookie = encodeURIComponent("name")+"="+encodeURIComponent("Nicholas") + ";domain=.wrox.com;path=/";

  下面的函數用來設置一個cookie的值,同時提供一個可選的max-age屬性

function setCookie(key,value,d){
    if(d === undefined){
        document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(value);
    }else{
        document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(value) + ";max-age=" + (d*60*60*24);        
    }
}
setCookie('name','小火柴');
console.log(getCookie('name'));//'小火柴'

【改變】

  要改變cookie的值,需要使用相同的名字、路徑和域,但是新的值重新設置cookie的值。同樣地,設置新max-age屬性就可以改變原來的cookie的有效期

function setCookie(key,value,d){
    if(d === undefined){
        document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(value);
    }else{
        document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(value) + ";max-age=" + (d*60*60*24);        
    }

}
setCookie('name','小火柴');
console.log(getCookie('name'));//'小火柴'
setCookie('name','火柴');
console.log(getCookie('name'));//'火柴'

【刪除】

  要刪除一個cookie,需要使用相同的名字、路徑和域,然后指定一個任意(非空)的值,並且將max-age屬性指定為0,再次設置cookie

function setCookie(key,value,d){
    if(d === undefined){
        document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(value);
    }else{
        document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(value) + ";max-age=" + (d*60*60*24);        
    }

}
setCookie('name','小火柴');
console.log(getCookie('name'));//'小火柴'
setCookie('name','小火柴',0);
console.log(getCookie('name'));//undefined

 

讀寫差異

  document.cookie屬性一次可以讀出全部Cookie,但是只能寫入一個Cookie,與服務器與瀏覽器之間的Cookie通信格式有關。瀏覽器向服務器發送Cookie的時候,是一行將所有Cookie全部發送

GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: cookie_name1=cookie_value1; cookie_name2=cookie_value2
Accept: */*

  上面的頭信息中,Cookie字段是瀏覽器向服務器發送的Cookie

  服務器告訴瀏覽器需要儲存Cookie的時候,則是分行指定

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: cookie_name1=cookie_value1
Set-Cookie: cookie_name2=cookie_value2; expires=Sun, 16 Jul 3567 06:23:41 GMT

  上面的頭信息中,Set-Cookie字段是服務器寫入瀏覽器的Cookie,一行一個

 

子cookie

  為了繞開瀏覽器的單域名下的cookie數限制,一些開發人員使用了一種稱為子cookie(subcookie)的概念。子cookie是存放在單個cookie中的更小段的數據。也就是使用cookie值來存儲多個名稱值對兒。子cookie最常見的格式如下所示

name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5

  子cookie一般也以査詢字符串的格式進行格式化。然后這些值可以使用單個cookie進行存儲和訪問,而非對每個名稱——值對兒使用不同的cookie存儲。最后網站或者Web應用程序可以無需達到單域名cookie上限也可以存儲更加結構化的數據

  為了更好地操作子cookie,必須建立一系列新方法。子cookie的解析和序列化會因子cookie的期望用途而略有不同並更加復雜些

 

PHP

  雖然在客戶端可以使用javascript讀寫Cookie,但更常用的是在服務器端來讀寫Cookie,然后再將Cookie返回,並保存到客戶端

【設置】

  PHP使用setcookie()函數來設置Cookie

bool setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, string $domain = "" [, bool $secure = false [, bool $httponly = false ]]]]]] )

  可以看出前6個參數,與document.cookie中的參數相同,只是新增了一個httponly參數,稍后介紹

//向客戶端發送一個Cookie,將變量username設置為'小火柴',保存時間為一天
setcookie('username','小火柴',time()+60*60*24);

function getCookie(key){
    var arr1 = document.cookie.split("; ");
    for(var i = 0; i < arr1.length; i++){
        var arr2 = arr1[i].split("=");
        if(arr2[0] == key){
            return decodeURIComponent(arr2[1]);
        }
    }
}
console.log(getCookie('username'));//'小火柴'

  也可以利用多維數組的形式,將多個內容值存儲在相同Cookie名稱標識符下

setcookie("user[username]", "小火柴");            //$_COOKIE["user"]["username"] 
setcookie("user[password]", md5("123456"));   //$_COOKIE["user"]["password"] 
//遍歷$_COOKIE["user"]數組
foreach($_COOKIE["user"] as $key => $value){ 
  //輸出Cookie數組中二維的鍵值對 
  echo $key.":".$value."\n";
}

【HTTP專有】

  設置Cookie的時候,如果服務器加上了HttpOnly屬性,則這個Cookie無法被javascript讀取(即document.cookie不會返回這個Cookie的值),只能從服務器端讀取。進行AJAX操作時,XMLHttpRequest對象也無法包括這個Cookie。這主要是為了防止XSS攻擊盜取Cookie

  [注意]cookie依然保存在客戶端中,只是無法被document.cookie讀取

setcookie('username','小火柴',time()+60*60*24,'','','',true);

function getCookie(key){
    var arr1 = document.cookie.split("; ");
    for(var i = 0; i < arr1.length; i++){
        var arr2 = arr1[i].split("=");
        if(arr2[0] == key){
            return decodeURIComponent(arr2[1]);
        }
    }
}
console.log(getCookie('username'));//undefined

【刪除】

  設置Cookie在當前時間過期,因此系統會自動刪除識別該名稱的Cookie

setCookie("username", "" , time()-1);  

【讀取】

  在PHP中讀取Cookie信息很簡單,使用超全局數組$_COOKIE['名稱']即可獲取cookie中的內容

//輸出Cookie中保存的所有用戶信息 
print_r($_COOKIE); 
//輸出Cookie中'username'的值 
print_r($_COOKIE['username']); 

 

登錄

  大部分頁面都有登錄模塊,這是為了維護系統安全,確保只有通過身份驗證的用戶才能訪問該系統,采用Cookie保存用戶登錄信息,在每個PHP腳本中,都能跟蹤登錄的用戶

  下面是一個使用原生js+php+mysql制作的一個簡易的登錄框

【數據庫】

  數據表格式如下

CREATE TABLE user(
    id int not null auto_increment,
    username varchar(50) not null default '',
    password char(32) not null default '',
    primary key(id)
);

  插入數據用戶名為admin,密碼為123456

【PHP】

  查詢數據庫,如果用戶存在,則返回登錄成功,否則返回登錄失敗

<?php
    header("Content-Type:text/html;charset=utf-8");
    try {
        //創建對象
        $pdo = new PDO("mysql:host=localhost;dbname=cookie1", "root", "*****");
        $stmt = $pdo -> prepare("select id, username, password from user where username=? and password=?");
        $stmt -> execute(array($_POST['username'],$_POST['password']));
        //如果查出數據,說明這個用戶是存在的
        if($stmt->rowCount() > 0) {
            //表示用戶存在
            echo '登錄成功';
            $time = time()+24*60*60;
            setCookie('username',urlencode($_POST['username']),$time);
            setCookie('password',urlencode($_POST['password']),$time);
            //設置一個登錄判斷的標記isLogin
            setCookie("isLogin", 1, $time);
        } else {
            //表示用戶不存在 
            echo '登錄失敗';
        }
    }catch(PDOException $e) {
        echo "數據庫連接失敗:".$e->getMessage();
        exit;
    }
?>

【前端】

  將要發送的數據通過md5加密后通過ajax發送給服務器

<div id="box">
    <label for="username">用戶名:</label><input id="username" name="username">
    <label for="password">密碼:</label><input id="password" name="password">
    <input type="button" id="btn" value="登錄">
</div>  
<div id="result"></div>
<script src="md5.js"></script>
<script>
btn.onclick = function(){
    //創建xhr對象
    var xhr = new XMLHttpRequest();
    //異步接受響應
    xhr.onreadystatechange = function(){
        if(xhr.readyState == 4){
            if(xhr.status == 200){
                result.innerHTML = xhr.responseText;            
            }
        }
    }
    //發送請求
    xhr.open('post','cookie1.php',true);
    xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
    xhr.send('username=' + hex_md5(username.value)+ '&password=' + hex_md5(password.value));    
} 
</script>

  下列示例中,只有用戶名為'admin',密碼為'123456'時,才能成功

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM