Http權威指南筆記(九)——客戶端識別和cookie機制

按照《HTTP權威指南》的章節,在這之前應該還有“Web機器人”和“HTTP-NG”兩個章節,但是這兩個章節實際使用當中比較少,這裏就沒有做介紹,感興趣的朋友可以自己去看看。本節我們開始介紹HTTP識別、認證和安全部分的客戶端識別和cookie機制。

HTTP屬於無狀態協議,一般情況服務器是沒法得知分別發送請求的客戶端用戶的。但是我們大部分Web站點都需要或者希望能夠區分用戶,根據用戶的不同興趣愛好等提供更好的個性化服務。所以本節我們就來看下客戶端識別相關知識。

1 HTTP首部

HTTP中提供了幾種可以用於承載用戶信息的首部,如下表所示:

首部名稱 首部類型 描  述
From 請求 用戶的 E-mail 地址
User-Agent 請求 用戶的瀏覽器軟件
Referer 請求 用戶是從這個頁面上依照鏈接跳轉過來的
Authorization 請求 用戶名和密碼(稍後討論)
Client-IP 擴展(請求) 客戶端的 IP 地址(稍後討論)
X-Forwarded-For 擴展(請求) 客戶端的 IP 地址(稍後討論)
Cookie 擴展(請求) 服務器產生的 ID 標籤(稍後討論)

這裏我們先介紹下前面三種,後面四種相對比較複雜,我們會分小節單獨介紹。

  • From:該首部可以用於指定用戶的Email地址,但是由於暴露了Email地址,而且有些服務器或者代理商又喜歡收集用戶的Email地址用作其他用途,所以該首部相對較少使用。
  • User-Agent:是用於指定用戶所使用客戶端(如瀏覽器)的相關信息,比如我們常用的Chrome瀏覽器的該頭部信息如下:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
  • Referer:該首部提供了用戶來源頁面的 URL。Referer 首部自身並不能完全標識用戶,但它確實說明了用戶之前訪問過哪個頁面。

2 客戶端IP地址

一般來說HTTP的客戶端是不會再請求中提供IP地址的(某些代理可能會提供Client-IP首部),但是服務器端可以通過響應途徑找到TCP連接的另一端的IP地址的。
雖然服務器端能獲得客戶端的IP地址,但是將IP地址作爲用戶的標識是不可行或者說不完善的。主要原因如下:

  1. 現在的計算機大部分都是多用戶系統,同一個IP地址可以對應多個用戶。
  2. 而且現在由於IP地址比較緊缺,很多網絡服務商會通過網絡地址轉換器(NetWork Address Translation,NAT)提供服務,這些NAT設備會隱藏客戶端的IP,所以客戶端共享一個對外的IP。
  3. 如果中間有經過代理,服務器獲獲取到的就是代理的IP,而不是客戶端的IP地址。

3 用戶登錄

Web 服務器可以要求用戶通過用戶名和密碼進行認證(登錄)來顯式地詢問用戶是誰。
爲了使 Web 站點的登錄更加簡便,HTTP 中包含了一種內建機制,可以用 WWW-Authenticate 首部和 Authorization 首部向 Web 站點傳送用戶的相關信息。一旦登錄,瀏覽器就可以不斷地在每條發往這個站點的請求中發送這個登錄信息了,服務器就可以通過登錄信息進行客戶端識別了。具體認證方式後面會有專門的章節進行介紹,這裏就不詳述了。

4 胖URL

有些 Web 站點會爲每個用戶生成特定版本的 URL 來追蹤用戶的身份。通常,會對真正的 URL 進行擴展,在 URL 路徑開始或結束的地方添加一些狀態信息。用戶瀏覽站點時,Web 服務器會動態生成一些超鏈,繼續維護 URL 中的狀態信息。改動後包含了用戶狀態信息的 URL 被稱爲胖 URL(fat URL)。後面服務器就可以根據請求中的URL攜帶的=額外信息進行客戶端識別了。但是這個也存在一些問題:

  • 無法共享URL:因爲URL裏面攜帶有個人的識別信息,所以不能將URL進行共享,共享就可能會泄露個人信息
  • 緩存問題:由於所有的URL都添加了個人識別信息,這個時候即使請求的同一個資源,但是緩存的時候也會爲每個用戶進行進行單獨緩存,會非常浪費資源。
  • 增加服務器負荷:由於每個用戶需要單獨重新生成特定的URL,也就意味着所有的HTML等資源中的超鏈接都需要爲每個用戶進行單獨修改

5 cookie

cookie是目前所有識別用戶信息方式中最好的一種,所以也是最爲流行的一種,也是本小節的重點。

5.1 cookie的類型

cookie大體上可以分爲兩類,一種是在一次會話有效的臨時cookie,當用戶退出瀏覽器結束本次會好的時候,就會失效。另一種是持久cookie,這種cookie一般會被保存在硬盤等存儲介質上,只要cookie沒有過期,就一直有效。即使用戶退出瀏覽器,下次打開訪問的時候,如果cookie還在有效期,就能使用。
區別兩種cookie的方式,就是隻要使用了Discard參數或者沒有設置Expires或者Max-age參數,就屬於臨時cookie,其他這屬於持久cookie。

5.2 cookie的作用過程

cookie的作用原理就是:Web站點會給客戶端貼一個標識,後續每當客戶端訪問這個Web站點的時候就帶上這個標識,Web站點就能通過這個標識知道是哪個客戶端在訪問了。過程如下圖所示:
cookie原理
如上圖所示,當客戶端第一次訪問該服務器的時候,Web服務器會的響應內容中會包含一個Set-cookie的響應頭,裏面包含一些cookie信息,客戶端收到響應後就會記下(保存)該cookie的信息,後續每次訪問該Web服務器就會攜帶上指定的cookie信息。至於cookie的保存方式,這個就客戶端自行決定了,只要客戶端自己能找到和識別就行。

5.3 不同的站點的cookie區分

一個客戶端不可能只訪問一個Web站點,可能會訪問很多個Web站點,而每個Web站點都會自己的cookie,所以客戶端保存的cookie也是有很多的。客戶端在發送cookie的時候就會有篩選過程。那客戶端是怎麼知道在訪問不同的資源的時候發送哪個cookie呢?一般就是通過cookie的domain和path屬性進行控制。客戶端在發送cookie的時候,會對domain和path進行判斷,選取匹配的cookie屬性值進行發送。這裏對domain和path就不展開說明,每個屬性後面小節會專門介紹cookie各個版本的屬性。

5.4 cookie的屬性介紹

現在使用的 cookie 規範有兩個不同的版本:cookies 版本 0(有時被稱爲 Netscape cookies) 和 cookies 版本 1(RFC 2965)。cookies 版本 1 是對 cookies 版本 0 的擴展。所以這裏對cookie屬性的介紹也分爲兩個版本進行介紹。

5.4.1 cookies版本0的屬性介紹

  1. 這裏先介紹服務器使用的Set-Cookie首部
    Set-Cookie 首部有一個強制性的 cookie 名和 cookie 值。後面跟着可選的 cookie 屬性,中間由分號分隔。具體每個屬性介紹如下表所示:
Set-Cookie屬性 描述及實例
NAME=VALUE 強制的。NAME 和 VALUE 都是字符序列,除非包含在雙引號內,否則不包括分號、逗號、等號和空格。Web 服務器可以創建任意的 NAME=VALUE 關聯,在後繼對站點的訪問中會將其送回給 Web 服務器:Set-Cookie: customer=Mary
Expires 可選的。這個屬性會指定一個日期字符串,用來定義cookie 的實際生存期。一旦到了過期日期,就不再存儲或發佈這個 cookie 了。日期的格式爲:Weekday, DD-Mon-YY HH:MM:SS GMT
唯一合法的時區爲GMT,各日期元素之間的分隔符一定要是長劃線。如果沒有指定Expires, cookie 就會在用戶會話結束時過期。如:
Set-Cookie: foo=bar; expires=Wednesday, 09-Nov-99 23:12:40 GMT
Domain 可選的。瀏覽器只向指定域中的服務器主機名發送cookie。這樣服務器就將 cookie 限制在了特定的域中。acme.com 域就與 anvil.acme.comshipping.crate.acme.com 相匹配,但與 www.cnn.com 就不匹配了。只有指定域中的主機才能爲一個域設置cookie,這些域中至少要有兩個或三個句號,以防止出現.com、.edu 和 va.us 等形式的域。這裏列出了一組固定的特定高層域,落在這個範圍中的域只需要兩個句號。所有其他域都至少需要三個句號。特定的高層域包括:.com、.edu、.net、.org、.gov、.mil、.int、.biz、.info、.name、.museum、.coop、.aero 和.pro
如果沒有指定域,就默認爲產生Set-Cookie 響應的服務器的主機名:Set-Cookie: SHIPPING=FEDEX; domain=“joes-hardware.com
Path 可選的。通過這個屬性可以爲服務器上特定的文檔分配cookie。如果 Path 屬性是一個 URL 路徑前綴,就可以附加一個cookie。路徑 /foo 與 /foobar 和 /foo/bar.html 相匹配。路徑“/”與域名中所有內容都匹配。如果沒有指定路徑,就將其設置爲產生 Set-Cookie 響應的 URL 的路徑:Set-Cookie: lastorder=00183; path=/orders
Secure 可選的。如果包含了這一屬性,就只有在 HTTP 使用 SSL 安全連接時纔會發送 cookie:Set-Cookie: private_id=519; secure
  1. 客戶端使用的Cookie首部
    客戶端這一端相對比較簡單,一般根據需要將合適的Cookie屬性發送給服務器即可。在版本0的客戶端在發送請求時,會將所有有關需要的cookie屬性組合一個
    Cookie首部中,如:
    Cookie: session-id=002-1145265-8016838; session-id-time=1007884800

5.4.2 cookie版本1的屬性介紹

和版本0相比,該版本的cookie引入了標準的Set-Cookie2和Cookie2兩個首部。同版本0相比主要區別如下:

  • 可以爲每個cookie添加解釋性文章,對其進行描述
  • 允許客戶端在退出時候,不考慮過期時間,將cookie強制銷燬
  • 使用相對秒數(Max-Age),而不是絕對時間來設置cookie的過期時間
  • 除了domain和path外,還可以通過端口號區分cookie
  • 在Cookie首部中,如果是使用關鍵字,在其前面加上“$”符號的前綴表示是關鍵字,而不是普通的cookie值。

下面接具體介紹每個數限制的,同樣的我們還是先看Set-Cookie2的屬性。

  1. Set-Cookie2的屬性
Set-Cookie2屬性 描述及實例
NAME=VALUE 強制的。Web 服務器可以創建任意的 NAME=VALUE 關聯,可以在後繼對站點的訪問中將其發回給 Web 服務器。“$ ”是保留字符,所以名字一定不能以它開頭
Version 強制的。這個屬性的值是一個整數,對應於 cookie 規範的版本。RFC 2965 爲版本1:Set-Cookie2: Part=“Rocket_Launcher_0001”; Version=“1”
Comment 可選。這個屬性說明了服務器準備如何使用這個cookie。用戶可以通過檢查此策略來確定是否允許使用帶有這個 cookie 的會話。這個值必須採用 UTF-8 編碼
CommentURL 可選。這個屬性提供了一個 URL 指針,指向詳細描述了 cookie 目的及策略的文檔。用戶可以通過查看此策略來判定是否允許使用帶有這個 cookie會話。
Discard 可選。如果提供了這個屬性,就會在客戶端程序終止時,指示客戶端放棄這個 cookie
Domain 可選。瀏覽器只向指定域中的服務器主機名發送 cookie。這樣服務器就可以將 cookie 限制在特定域中了。acme.com 域與主機名 anvil.acme.comshipping.crate.acme.com 相匹配,但不匹配於www.cnn.com。域名匹配的規則基本上與 Netscape cookie 一樣,但有幾條附加的規則。細節請參見 RFC 2965
Max-Age 可選。這個屬性的值是一個整數,用於設置以秒爲單位的 cookie 生存期。客戶端應該根據 HTTP/1.1 的使用期計算規則來計算 cookie 的使用期。cookie 的使用期比 Max-Age 大時,客戶端就應該將這個 cookie 丟棄。值爲零說明應該立即將那個 cookie 丟棄
Path 可選。通過這個屬性可以爲服務器上的特定文檔指定 cookie。如果 Path 屬性是一個 URL 路徑的前綴,就可以附加一個 cookie。路徑 /foo 匹配於 /foobar 和 /foo/bar.html。路徑“/”匹配於域中所有內容。如果沒有指定路徑,就將其設置爲生成 Set-Cookie 響應的URL 的路徑
Port 可選。這個屬性可以單獨作爲關鍵字使用,也可以包含一個由逗號分隔的、可以應用 cookie 的端口列表。如果有端口列表,就只能向端口與列表中的端口相匹配的服務器提供 cookie。如果單獨提供關鍵字 Port 而沒有值,就只能向當前響應服務器的端口號提供 cookie:
Set-Cookie2: foo=“bar”; Version=“1”;
Port="80,81,8080"
Set-Cookie2: foo=“bar”; Version=“1”; Port
Secure 可選。如果包含這個屬性,就只有在 HTTP 使用 SSL 安全連接時才能發送 cookie
  1. 版本1的Cookie首部
    對於Cookie首部來說,版本1和版本0基本沒有差別,也是一樣根據相關屬性篩選出來Cookie後,一般組合在一起進行發送。

5.5 關於版本0和版本1的使用

一般來說,對於能夠支持Cookie2的客戶端,要同時做好兼容Cookie的準備,保證服務器返回的是Set-Cookie首部,也能正確處理。如果客戶端同時受到了Set-Cookie2和Set-Cookie,那麼應該選用Set-Cookie2,忽略Set-Cookie首部。
對於服務端來說,如果客戶端明確告知了其支持版本1,那服務器在返回響應的時候,也要優先選擇返回Set-Cookie2的首部。

5.6 Cookie作用與會話跟蹤的過程

Cookie可以作爲客戶端識別和會話跟蹤技術。那他們具體實現過程如下圖所示:
Cookie工作工程
從上圖中可以看到,一開始客戶端發起的是一個不帶有Cookie首部的普通請求,服務器再收到該請求後,會返回重定向響應,同時攜帶Set-Cookie首部。客戶端再收到該響應後,會攜帶上Cookie首部,然後重新對服務器指定的URL發起訪問,最終直到服務器返回所請求的正確資源。

5.7 cookie與緩存

如果我們的請求過程中有cookie的參與,那麼我們在處理緩存的時候就需要特別消息。因爲很多cookie可能都和用戶的隱私相關,如果我們不加處理的緩存了所有數據,很有可能造成數據混亂設置用戶的隱私數據的暴露。一般我們在對有cookie的數據進行緩存的時候,採取如下幾個原則進行處理:

  • 服務器如果明確知道該文檔資源是不能被緩存的,那麼它在響應的時候,應該明確標識出來(Cache-Control:no-cache="Set-Cookie"),告訴代理不能緩存該文檔。如果能緩存,也可以明確告訴客戶端最好(Cache-Contrl:public
  • 如果服務器沒有明確說明該資源不能緩存,此時緩存的時候,要特別小心Set-Cookie的處理,如果此時我們僅僅不對Set-Cookie首部進行緩存,但是內容我們緩存了,這種情況下次客戶端在發起請求的時候,如果使用了緩存資源,這個時候客戶端收到的響應就沒有Set-Cookie首部,所以也會引起一些問題。當然,最好的控制還是在原始服務器,原始服務器可以要求每次使用緩存的時候都進行驗證(Cache-Control: must-revalidate, max-age=0),這樣就能獲取到Set-Cookie首部了。如果服務器什麼都沒有提供,那麼保守的做法就是不要緩存帶有Set-Cookie的文檔資源,一些圖片資源卻可以緩存。
  • 最後一條就是對服務器要求,也就是我們上面第一條和第二條中說到,對於有Set-Cookie首部的內容,最好的緩存控制是在服務器端,所以我們的服務器要儘量做到對緩存控制的精細,要告知代理緩存具體應該怎麼去處理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章