一文帶你徹底搞懂Cookie、Session和Token

在學(xué)習(xí)Cookie、Session和Token之前,我們先了解下HTTP的無狀態(tài)協(xié)議。
1、HTTP的無狀態(tài)協(xié)議
HTTP無狀態(tài)協(xié)議是指該協(xié)議對事件的處理過程沒有記憶能力,當(dāng)后續(xù)的步驟需要上一步的信息時,則需要重傳,即需要攜帶上一次的信息。
因此,對于存在依賴性的訪問請求,則下一次的傳遞需要攜帶上一次傳遞的信息,依次不斷的疊加,會導(dǎo)致傳輸?shù)男畔⒘繒絹碓酱?,服?wù)器響應(yīng)較慢。

在90年代,瀏覽器剛出現(xiàn)的時候,它的作用也僅僅是瀏覽一些文本而已,彼此之間不存在依賴關(guān)系(在后面的學(xué)習(xí)中,我們把這種依賴關(guān)系理解為會話)。因此,服務(wù)器不需要做任何的記錄,瀏覽器請求什么,服務(wù)器就返回什么,彼此之間清清楚楚,不存在任何的愛恨情仇,雙方的關(guān)系非常的融洽。
隨著技術(shù)的發(fā)展,特別是當(dāng)客戶端與服務(wù)器進(jìn)行動態(tài)交互的Web應(yīng)用程序出現(xiàn)之后,HTTP的無狀態(tài)特征嚴(yán)重影響了這類Web應(yīng)用程序,Web應(yīng)用的交互是雙向,需要承前啟后的,總不能每次都失憶。
就像夏洛特?zé)乐?,夏洛和大爺?shù)膶υ挘?/p>
夏洛:大爺,樓上322住的是馬冬梅家嗎? 大爺:馬冬什么? 夏洛:馬冬梅。 大爺:什么冬梅啊? 夏洛:馬冬梅啊。 大爺:馬什么梅??? 夏洛:……行,大爺,您先涼快吧。
如簡單的購物車程序也要知道用戶到底在之前選擇了什么商品,總不能所有用戶都使用一個購物車吧,所以需要把每個用戶都區(qū)分開,服務(wù)器需要記錄每個用戶的會話,又因為HTTP是無狀態(tài)的,那么就要想辦法為每個用戶保持一個會話。
因此市面上出現(xiàn)了兩種保持HTTP連接狀態(tài)的技術(shù):Cookie和Session。
Cookie是客戶端保持HTTP會話狀態(tài)的技術(shù),而Session是服務(wù)端保持HTTP會話狀態(tài)的技術(shù)。通常情況下,這兩種技術(shù)是結(jié)合使用的。
下面,我們將分別學(xué)習(xí)Cookie、Session,并針對它們的不足,學(xué)習(xí)Token的原理與使用。
2、Cookie
在學(xué)習(xí)Cookie之前,我們先考慮以下問題:
(1)什么是Cookie,Cookie的作用是什么?
(2)Cookie的工作機制是什么?
(3)Cookie的基本屬性有哪些?
針對上面的問題,我們一一來做解答。
2.1 Cookie的原理及工作機制
按照官方的定義:
Cookie是保存在客戶端瀏覽器中的文本文件(key-value形式),這個文件與訪問的特定的Web頁面(文檔)關(guān)聯(lián)在一起,并且保存在本地的客戶端中,Cookie 最根本的用途是幫助 Web 站點保存有關(guān)訪問者的信息。
舉個例子,當(dāng)客戶端瀏覽器訪問服務(wù)端時,服務(wù)端會記錄每個用戶的訪問信息并以Cookie文件的形式保存在客戶端,當(dāng)用戶再次訪問服務(wù)端的特定頁面時,服務(wù)端會首先檢查客戶端攜帶的Cookie中的用戶身份信息,從而保持了會話的進(jìn)行。
什么意思呢,我們舉個生活中的例子:
當(dāng)我們?nèi)ャy行辦理儲蓄業(yè)務(wù)時,柜員第一次給我們辦了張銀行卡,里面存放了身份證、密碼、手機等個人信息。當(dāng)我們下次再來這個銀行時,銀行機器能識別這種卡,從而能夠直接辦理業(yè)務(wù)。
那么問題來了,Cookie到底是如何起作用的呢?
事實上,當(dāng)用戶每次訪問服務(wù)器時,Web應(yīng)用程序都可以讀取Cookie包含的信息,當(dāng)用戶再次訪問該頁面時,瀏覽器就會在本地硬盤上查找與該URL相關(guān)的Cookie,如果該 Cookie 存在(Cookie在不過期的情況下),瀏覽器就將它添加到request header的Cookie字段中,與http請求一起發(fā)送到該站點。
我們以登錄為例:
實現(xiàn)登陸我們就必須需要cookie, 使用 cookie 來保存用戶的信息。
在請求某個域的時候,http 會自動將這個域的 Cookie 放到請求頭當(dāng)中。所以我們只需要在登陸用戶成功后,將必要的信息通過設(shè)置 Set-Cookie 這個響應(yīng)頭返回給客戶端。
瀏覽器會自動將該頭的信息存儲到當(dāng)前域名下的 Cookie 中,當(dāng)下次用戶請求的時候,http 協(xié)議會自動將該 cookie 帶上。我們就可以在每次的請求的請求頭當(dāng)中拿到該 Cookie, 然后去判斷用戶是否登陸,進(jìn)而根據(jù)用戶是否登陸進(jìn)行相應(yīng)的處理。
NOTE:
Cookie被添加到
request header中是「瀏覽器的行為」,存儲在cookie的數(shù)據(jù)「每次」都會被瀏覽器「自動」放在http請求中。Cookie過多的信息會增加網(wǎng)絡(luò)流量,因此,我們必須考慮什么樣的數(shù)據(jù)才能放入到Cookie中,我們用的最多的是身份驗證信息。
因此,當(dāng)用戶第一次訪問并登陸一個網(wǎng)站的時候,Cookie的設(shè)置以及發(fā)送會經(jīng)歷以下4個步驟:
(1)客戶端發(fā)送一個請求數(shù)據(jù)
(2)服務(wù)器發(fā)送一個HttpResponse響應(yīng)到客戶端,該響應(yīng)包含Set-Cookie頭部
(3)客戶端保存Cookie,之后向服務(wù)器發(fā)送請求時,HttpRequest請求中會包含一個Cookie的頭部
(4)服務(wù)器返回響應(yīng)數(shù)據(jù)

2.2 Cookie的組成
以登錄為例,一般來說我們不會在用戶登陸后將用戶的用戶名和密碼設(shè)置到 Cookie 中,從而直接在請求頭拿到用戶名和密碼,然后去判斷用戶是否登陸,這些信息過于敏感,暴露出來十分危險。
通常情況下,正確的做法應(yīng)該是不管用戶是否登錄,都應(yīng)該通過 Cookie 給用戶返回一個標(biāo)志(通常稱為session_id或者token 等,在下面的部分具體展開),并需要設(shè)置其過期時間、httpOnly、path等。每次用戶請求時,都根據(jù)這個標(biāo)志在服務(wù)端去取根據(jù)這個標(biāo)志存在服務(wù)端的用戶的信息。
因此,Cookie一般由以下幾部分構(gòu)成
(1)Name/Value:
該屬性是設(shè)置Cookie的名稱及相對應(yīng)的值,該值通常是保留在Cookie中的用戶信息。對于認(rèn)證Cookie,Value值包括Web服務(wù)器所提供的訪問令牌(繼續(xù)往下看,下面內(nèi)容會學(xué)習(xí)令牌)。
(2)Expires屬性:
該屬性是設(shè)置Cookie的生存周期。
在默認(rèn)情況下,Cookie是臨時存在的。
當(dāng)一個瀏覽器窗口打開時,可以設(shè)置Cookie,只要該瀏覽器窗口沒有關(guān)閉,Cookie就一直有效,而一旦瀏覽器窗口關(guān)閉后,Cookie也就隨之消失。
如果想要cookie在瀏覽器窗口之后還能繼續(xù)使用,就需要為Cookie設(shè)置一個生存期。所謂生存期也就是Cookie的終止日期,在這個終止日期到達(dá)之前,瀏覽器都可以讀取該Cookie。一旦終止日期到達(dá)之后,該cookie將會從cookie文件中刪除。
(3)Path屬性:
定義了Web站點上可以訪問該Cookie的目錄。比如,設(shè)置為"/"表示允許當(dāng)前域名下的所有路徑都可以使用該Cookie。
(4)Domain屬性:
指定了可以訪問該 Cookie 的 Web 站點或域,默認(rèn)為當(dāng)前域。
如當(dāng)前域是www.simon.item,那么它的子域www.simon.item.count共享父域的Cookie,對于不同的域或者平行域則無法共享該Cookie。
(5)Secure屬性:
secure是 cookie 的安全標(biāo)志,指定是否使用HTTPS安全協(xié)議發(fā)送Cookie。
(6)HTTPOnly 屬性 :
用于防止客戶端腳本通過document.cookie屬性訪問Cookie,有助于保護(hù)Cookie不被跨站腳本攻擊竊取或篡改。
綜上所述,服務(wù)器通過發(fā)送一個名為 Set-Cookie(Cookie是一個對象,需要自己new出來) 的HTTP頭來創(chuàng)建一個cookie,并作為 Response Headers 的一部分。每個Set-Cookie 表示一個 Cookie(如果有多個Cookie,需寫多個Set-Cookie),每個屬性也是以名/值對的形式(除了secure),屬性間以分號加空格隔開。格式如下:
Set-Cookie: name1=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
Set-Cookie: name2=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
Set-Cookie: name3=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
......
除了在服務(wù)器端設(shè)置,還可以在客戶端設(shè)置Cookie
document.cookie = "test1=myCookie1;"
document.cookie = "test2=myCookie2; domain=.google.com.hk; path=/webhp"
document.cookie = "test3=myCookie3; domain=.google.com.hk; expires=Sat, 08 AUG 2021 16:00:00 GMT; secure"
document.cookie = "test4=myCookie4; domain=.google.com.hk; max-age=10800;"
NOTE:只有name/value可以被發(fā)送至服務(wù)端,其余的參數(shù)僅僅是服務(wù)端給客戶端的指示或者客戶端自身的約束。
由于Cookie的出現(xiàn)可以解決HTTP的無狀態(tài),維持會話的正常進(jìn)行,我們使用Cookie的應(yīng)用場景通常有以下幾種:
(1)購物車(網(wǎng)購)
(2)自動登錄(登錄賬號時的自動登錄)
(3)精準(zhǔn)廣告
平常瀏覽網(wǎng)頁時有時會推出商品剛好是你最近瀏覽過,買過的類似東西,這些是通過cookie記錄的。
雖然Cookie有很多優(yōu)點,其缺點也很明顯。
(1)每個域的Cookie總數(shù)是有限的,不同瀏覽器之間各有不同
如Firefox限制每個域最多50個cookie,IE限制50個,Chrome對于每個域的Cookie數(shù)量沒有規(guī)定
(2)Cookie大小限制
大多數(shù)瀏覽器限制Cookie的大小為4KB
(3)安全性
Cookie文件中可能含有涉密信息,可能會導(dǎo)致信息泄露。
由于Cookie存儲信息的大小不僅有限制,而且還存在信息安全問題,因此,必須想辦法把一些具體的信息存儲到服務(wù)端上。因此,Session的出現(xiàn)可以解決這個問題。
3、Session
Session在計算機中被稱為會話控制。
Session對象可以存儲特定用戶會話所需的屬性及配置信息,它通過給不同的用戶發(fā)送session_id并放在Cookie中,然后具體的數(shù)據(jù)則是保存在session中。
如果用戶已經(jīng)登錄,則服務(wù)器會在Cookie中保存一個session_id,下次再次請求的時候,會把該session_id攜帶上來,服務(wù)器根據(jù)session_id在session庫中獲取用戶的session數(shù)據(jù)。就能知道該用戶到底是誰,以及之前保存的一些狀態(tài)信息,從而保持會話連接的狀態(tài)。
圖解如下:

Session流程對應(yīng)的后端代碼如下:
package xdp.gacl.session;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setCharacterEncoding("UTF=8");
response.setContentType("text/html;charset=UTF-8");
//使用request對象的getSession()獲取session,如果session不存在則創(chuàng)建一個
HttpSession session = request.getSession();
//將數(shù)據(jù)存儲到session中
session.setAttribute("data", "小郎同學(xué)");
//獲取session的Id
String sessionId = session.getId();
//判斷session是不是新創(chuàng)建的
if (session.isNew()) {
response.getWriter().print("session創(chuàng)建成功,session的id是:"+sessionId);
}else {
response.getWriter().print("服務(wù)器已經(jīng)存在該session了,session的id是:"+sessionId);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
隨著訪問服務(wù)器的用戶數(shù)量的增多,服務(wù)器上保存的Session也日益增多,這對服務(wù)器來說是個巨大的開銷,對于單個服務(wù)器的Web應(yīng)用匯總,大量的Session會占用比較多的內(nèi)存。
不僅如此,在分布式系統(tǒng)中,由于負(fù)載均衡對請求轉(zhuǎn)發(fā),這樣就有可能導(dǎo)致同一個用戶的請求分發(fā)到不同的服務(wù)器上,會出現(xiàn)不能獲取不到Session的情況。
Session不一致原理圖

解決Session不一致的情況通常有三種方法。
1、反向代理hash一致性
這類方法是使用Nginx的負(fù)載均衡算法其中的hash_ip算法將ip固定到某一臺服務(wù)器上,這樣就不會出現(xiàn)session共享問題,因為同一個ip訪問下,永遠(yuǎn)是同一個服務(wù)器。

但是這樣的做法也是不保險的,當(dāng)綁定的機器掛了,請求還是會被分發(fā)到別的機器上去。
因此,可以對多個服務(wù)器間進(jìn)行Session復(fù)制,這樣就可以保障每個服務(wù)器上都包好全部請求的session
2、Session復(fù)制

多個服務(wù)器間進(jìn)行Session復(fù)制非常占用內(nèi)網(wǎng)的帶寬,每個服務(wù)器都有相同的Session不僅導(dǎo)致服務(wù)器的空間利用降低,而且受內(nèi)存的限制,無法水平擴(kuò)展。
既然每個服務(wù)器都包含相同的Session,我們可以把Session統(tǒng)一存儲管理。
3、共享Session服務(wù)器
該方法是把Session集中存儲一個服務(wù)器上,Session服務(wù)器是將有狀態(tài)的Session信息與無狀態(tài)的應(yīng)用服務(wù)器相分離,所有的請求都來訪問這個session服務(wù)器。

這種方法雖然不再需要復(fù)制,但是卻增加了單點失敗的可能性,如果負(fù)責(zé)Session的機器掛了,那么保存所有用戶信息的Session將會丟失。有沒有一種辦法使得服務(wù)端存儲這些session呢?
在這個基礎(chǔ)上,Token就出現(xiàn)了。
4、Token
通常意義上的token是把session中的內(nèi)容都放到token中(可以明文形式或者用對稱加密算法加密(加密密鑰放在服務(wù)器端),
然后再把這些內(nèi)容做hash簽名(密鑰在服務(wù)器),把內(nèi)容和其簽名拼接成一個字符串(這個就是token) 發(fā)送給客戶端。
客戶端后續(xù)請求都帶上這個token,服務(wù)器首先校驗token是否被篡改(根據(jù)hash簽名校驗),如果沒被篡改而且token
也沒過期,就把token中的用戶信息(可能還有權(quán)限信息等等)拿出來,直接使用,不需要像session一樣去查詢具體用戶信息。
通常情況下,基于Token的身份驗證的過程如下:
(1)用戶通過用戶名和密碼發(fā)送請求。
(2)程序驗證。
(3)程序返回一個簽名的token 給客戶端。
(4)客戶端儲存token,并且每次用于每次發(fā)送請求。
(5)服務(wù)端驗證token并返回數(shù)據(jù)。

Token具有以下的優(yōu)勢:
(1)無狀態(tài)、可擴(kuò)展
在客戶端存儲的token是無狀態(tài)的,并且能夠被擴(kuò)展。基于這種無狀態(tài)和不存儲Session信息,負(fù)載負(fù)載均衡器能夠?qū)⒂脩粜畔囊粋€服務(wù)傳到其他服務(wù)器上。
如果我們將已驗證的用戶的信息保存在Session中,則每次請求都需要用戶向已驗證的服務(wù)器發(fā)送驗證信息(稱為Session親和性)。用戶量大時,可能會造成一些擁堵。
但是不要著急。使用tokens之后這些問題都迎刃而解,因為服務(wù)端使用token可以使用秘鑰找到用戶的信息。
(2)安全性
請求中發(fā)送token而不再是發(fā)送cookie能夠防止CSRF(跨站請求偽造)。即使在客戶端使用cookie存儲token,cookie也僅僅是一個存儲機制而不是用于認(rèn)證。不將信息存儲在Session中,讓我們少了對session操作。
token是有時效的,一段時間之后用戶需要重新驗證。我們也不一定需要等到token自動失效,token有撤回的操作,通過token revocataion可以使一個特定的token或是一組有相同認(rèn)證的token無效。
可擴(kuò)展性
使用token時,可以提供可選的權(quán)限給第三方應(yīng)用程序。當(dāng)用戶想讓另一個應(yīng)用程序訪問它們的數(shù)據(jù),我們可以通過建立自己的API,得出特殊權(quán)限的tokens。
如使用微信登錄微博
多平臺跨域
只要用戶有一個通過了驗證的token,數(shù)據(jù)和資源就能夠在任何域上被請求到。
總結(jié)
下面,我們對Cookie、Session和Token做以下總結(jié):
HTTP請求是無狀態(tài)的,就是說第一次和服務(wù)器連接并登陸成功后,第二次請求服務(wù)器仍然不知道當(dāng)前請求的用戶。Cookie出現(xiàn)就是解決了這個問題,第一次登陸后服務(wù)器返回一些數(shù)據(jù)(cookie)給瀏覽器,然后瀏覽器保存在本地,當(dāng)用戶第二次返回請求的時候,就會把上次請求存儲的cookie數(shù)據(jù)自動攜帶給服務(wù)器。
如果關(guān)閉瀏覽器Cookie失效(Cookie就是保存在內(nèi)存中)
如果關(guān)閉瀏覽器Cookie不失效(Cookie保存在磁盤中)
Session和Cookie的作用有點類似,都是為了存儲用戶相關(guān)的信息。
不同的是,Cookie是存儲在本地瀏覽器,而Session存儲在服務(wù)器。存儲在服務(wù)器的數(shù)據(jù)會更加的安全,不容易被竊取。但存儲在服務(wù)器也有一定的弊端,就是會占用服務(wù)器的資源。
存儲在服務(wù)端
通過cookie存儲一個session_id,然后具體的數(shù)據(jù)則是保存在session中。 如果用戶已經(jīng)登錄,則服務(wù)器會在cookie中保存一個session_id,下次再次請求的時候,會把該session_id攜帶上來,服務(wù)器根據(jù)session_id在session庫中獲取用戶的session數(shù)據(jù)。就能知道該用戶到底是誰,以及之前保存的一些狀態(tài)信息。
1、Cookie與Session的區(qū)別
cookie數(shù)據(jù)存放在客戶的瀏覽器上,session數(shù)據(jù)放在服務(wù)器上。
cookie不是很安全,別人可以分析存放在本地的cookie并進(jìn)行cookie欺騙考慮到安全應(yīng)當(dāng)使用session。
session會在一定時間內(nèi)保存在服務(wù)器上。當(dāng)訪問增多,會比較占用你服務(wù)器的性能考慮到減輕服務(wù)器性能方面,應(yīng)當(dāng)使用cookie。
單個cookie保存的數(shù)據(jù)不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie。因此使用cookie只能存儲一些小量的數(shù)據(jù)。
所以開發(fā)人員的通常做法是:
將登陸信息等重要信息存放為Session 其他信息如果需要保留,可以放在Cookie中
2、Token和Session的區(qū)別
共同點:
都是保存了用戶身份信息,都有過期時間。
不同點:
session翻譯為會話,token翻譯為令牌。
session是
空間換時間,token是時間換空間。session和session_id:服務(wù)器會保存一份,可能保存到緩存/數(shù)據(jù)庫/文件。
token:服務(wù)器不需要記錄任何東西,每次都是一個
無狀態(tài)的請求,每次都是通過解密來驗證是否合法。token 只是一個 key,不存放實際的數(shù)據(jù),與這個 token 相關(guān)的數(shù)據(jù)還是存放在服務(wù)器上,例如 Session,Redis 等分布式緩存里,用 token 去請求對應(yīng)的數(shù)據(jù)。session_id:一般是隨機字符串,要到
服務(wù)器檢索id的有效性。出現(xiàn)請求:服務(wù)器重啟內(nèi)存中的session沒了,數(shù)據(jù)庫服務(wù)器掛了。token 和 cookie 本質(zhì)上沒啥區(qū)別,只不過 token 只是一個字符串,訪問的時候可以放在 url 的參數(shù),header 里等,不像 cookie 那么重量級,而且移動端訪問的時候 token 更方便,僅此而已。
巨人的肩膀
往期推薦
