全方位解析!會(huì)話、Cookie、令牌與JWT的工作原理與實(shí)際應(yīng)用
在現(xiàn)代互聯(lián)網(wǎng)應(yīng)用中,安全性和用戶體驗(yàn)是兩個(gè)至關(guān)重要的因素。隨著移動(dòng)設(shè)備的普及和分布式系統(tǒng)的興起,傳統(tǒng)的 Session 認(rèn)證方式逐漸顯露出其局限性。Token 認(rèn)證作為一種新興的身份驗(yàn)證機(jī)制,因其無狀態(tài)的特性和良好的擴(kuò)展性而受到廣泛關(guān)注。Token 的使用不僅簡(jiǎn)化了用戶的登錄流程,還有效減輕了服務(wù)器的負(fù)擔(dān)。在本文中,我們將深入探討 Token 的定義、組成和優(yōu)勢(shì),特別是在電商平臺(tái)和第三方服務(wù)集成中的應(yīng)用。我們還將介紹 JSON Web Token(JWT),作為 Token 的一種特定形式,JWT 在實(shí)現(xiàn)無縫用戶體驗(yàn)和增強(qiáng)安全性方面具有顯著優(yōu)勢(shì)。
HTTP協(xié)議是一個(gè)“無狀態(tài)協(xié)議”,即每當(dāng)服務(wù)器收到客戶端的請(qǐng)求時(shí),這都是一個(gè)全新的請(qǐng)求,服務(wù)器并不知道客戶端的歷史請(qǐng)求記錄。Session和Cookie的主要目的就是彌補(bǔ)HTTP的無狀態(tài)特性。
什么是Session?
當(dāng)客戶端請(qǐng)求服務(wù)器時(shí),服務(wù)器會(huì)為該請(qǐng)求打開一個(gè)“內(nèi)存空間”。這個(gè)內(nèi)存空間存儲(chǔ)Session對(duì)象,存儲(chǔ)結(jié)構(gòu)是ConcurrentHashMap。Session彌補(bǔ)了HTTP的無狀態(tài)特性。服務(wù)器可以通過Session存儲(chǔ)客戶端在同一會(huì)話期間的一些操作記錄。
如何判斷是否是同一會(huì)話?
當(dāng)服務(wù)器第一次接收到請(qǐng)求時(shí),會(huì)打開一個(gè)Session空間(創(chuàng)建一個(gè)Session對(duì)象),同時(shí)生成一個(gè)sessionId,并通過響應(yīng)頭中的Set-Cookie: JSESSIONID=XXX命令向客戶端發(fā)送響應(yīng),請(qǐng)求設(shè)置Cookie。
客戶端收到響應(yīng)后,會(huì)在本地設(shè)置一個(gè)JSESSIONID=XXX的Cookie信息。這個(gè)Cookie的過期時(shí)間是瀏覽器會(huì)話結(jié)束時(shí)。
下次當(dāng)客戶端向同一網(wǎng)站發(fā)送請(qǐng)求時(shí),請(qǐng)求頭將攜帶這個(gè)Cookie信息(包括sessionId)。然后,通過讀取請(qǐng)求頭中的Cookie信息,服務(wù)器獲得名為JSESSIONID的值,并獲取該請(qǐng)求的sessionId。
Session的缺點(diǎn)
然而,Session機(jī)制有一個(gè)缺點(diǎn)。如果你的服務(wù)器進(jìn)行了負(fù)載均衡,并在第一次請(qǐng)求時(shí)將Session存儲(chǔ)在服務(wù)器A上。假設(shè)在一段時(shí)間內(nèi),服務(wù)器A的流量激增,請(qǐng)求將被轉(zhuǎn)發(fā)到服務(wù)器B進(jìn)行訪問。但是服務(wù)器B并不存儲(chǔ)服務(wù)器A的Session,這將導(dǎo)致Session失效。
圖片
什么是Cookie?
在介紹Session時(shí),你應(yīng)該注意到Cookie已經(jīng)被提到。Session是基于Cookie實(shí)現(xiàn)的。Session存儲(chǔ)在服務(wù)器端,而sessionId則存儲(chǔ)在客戶端的Cookie中。
HTTP協(xié)議中的Cookies包括Web Cookie和瀏覽器 Cookie。它是服務(wù)器發(fā)送到Web瀏覽器的小塊數(shù)據(jù)。服務(wù)器發(fā)送給瀏覽器的Cookie將被瀏覽器存儲(chǔ),并在下次請(qǐng)求時(shí)與其他請(qǐng)求一起發(fā)送回服務(wù)器。通常用于確定兩個(gè)請(qǐng)求是否來自同一瀏覽器,例如用戶保持登錄狀態(tài)時(shí)。
Cookies主要用于以下三個(gè)目的:
1. 會(huì)話管理
- 與服務(wù)器協(xié)作,通過存儲(chǔ)sessionid來識(shí)別用戶會(huì)話。
2. 存儲(chǔ)用戶信息
- 登錄狀態(tài):記住用戶是否已登錄。下次訪問時(shí)無需再次登錄。
- 偏好設(shè)置:如語言、主題等。下次訪問時(shí)自動(dòng)應(yīng)用。
3. 跟蹤用戶行為
- 瀏覽歷史:記錄訪問的頁面,以便于推薦和導(dǎo)航。
- 分析行為:了解用戶習(xí)慣,以優(yōu)化和精準(zhǔn)營(yíng)銷。
創(chuàng)建Cookies
當(dāng)服務(wù)器接收到來自客戶端的HTTP請(qǐng)求時(shí),可以發(fā)送帶有Set-Cookie頭的響應(yīng)。Cookies通常由瀏覽器存儲(chǔ),然后隨HTTP頭一起發(fā)送到服務(wù)器。
Set-Cookie和Cookie頭
Set-Cookie HTTP響應(yīng)頭將Cookies從服務(wù)器發(fā)送到用戶代理。下面是發(fā)送Cookie的示例。
圖片
這個(gè)頭告訴客戶端存儲(chǔ)Cookies。
現(xiàn)在,每次向服務(wù)器發(fā)起新請(qǐng)求時(shí),瀏覽器都會(huì)通過Cookie頭將所有先前存儲(chǔ)的Cookies發(fā)送回服務(wù)器。
圖片
有兩種類型的Cookies。一種是Session Cookies,另一種是Persistent Cookies。如果一個(gè)cookie不包含過期日期,它被視為會(huì)話cookie。Session cookies存儲(chǔ)在內(nèi)存中,從不寫入磁盤。當(dāng)瀏覽器關(guān)閉時(shí),cookie將永久丟失。如果一個(gè)cookie包含“過期時(shí)間”,則被視為持久性cookie。在指定的過期日期,cookie將從磁盤中刪除。
還有“Secure和HttpOnly標(biāo)志”。讓我們逐一介紹它們。
Session Cookies
上面的示例創(chuàng)建了一個(gè)會(huì)話cookie。會(huì)話cookie的一個(gè)特征是,當(dāng)客戶端關(guān)閉時(shí),該cookie將被刪除,因?yàn)闆]有指定Expires或Max-Age指令。
但是,網(wǎng)頁瀏覽器可能會(huì)使用會(huì)話恢復(fù),這將使大多數(shù)會(huì)話cookie保持在永久狀態(tài),就好像瀏覽器從未關(guān)閉過。
持久性Cookies
持久性cookies在客戶端關(guān)閉時(shí)不會(huì)過期。相反,它們?cè)凇疤囟ㄈ掌冢‥xpires)”或“特定時(shí)間段(Max-Age)”后過期。例如:
Set-Cookie: id=a3fWa; Expires=Sat, 21 Sep 2024 11:28:00 GMT;
Secure和HttpOnly標(biāo)志
Secure cookies需要通過HTTPS協(xié)議以加密方式發(fā)送到服務(wù)器。即使它們是安全的,敏感信息也不應(yīng)存儲(chǔ)在cookies中,因?yàn)樗鼈儽举|(zhì)上是不安全的,這個(gè)標(biāo)志并不能提供真正的保護(hù)。
HttpOnly的功能:
- 會(huì)話cookie中缺少HttpOnly屬性可能導(dǎo)致攻擊者通過程序(JS腳本、Applet等)獲取用戶的cookie信息,從而導(dǎo)致用戶cookie信息泄露,增加跨站腳本攻擊的威脅。
- HttpOnly是微軟對(duì)cookies的擴(kuò)展。這個(gè)值指定cookies是否可以通過客戶端腳本訪問。
- 如果cookies中沒有將HttpOnly屬性設(shè)置為true,可能導(dǎo)致cookie被竊取。被竊取的cookies可能包含識(shí)別網(wǎng)站用戶的敏感信息,例如ASP.NET會(huì)話ID或Forms身份驗(yàn)證票據(jù)。攻擊者可以重放被竊取的cookies偽裝成用戶,獲取敏感信息并進(jìn)行跨站腳本攻擊。
Cookies的范圍
Domain和Path標(biāo)識(shí)符定義了cookies的范圍:即cookies應(yīng)發(fā)送到哪些URL。
Domain標(biāo)識(shí)符指定可以接受cookies的主機(jī)。如果未指定,當(dāng)前主機(jī)(不包括子域)為默認(rèn)值。如果指定了Domain,通常會(huì)包括子域。
例如,如果設(shè)置Domain=mozilla.org,則cookies也包括子域(如developer.mozilla.org)。
例如,如果設(shè)置Path=/test,則以下地址都將匹配:
- /test
- /test/user/
- /test/user/login
為什么在已有Session的情況下還需要Token?
在現(xiàn)代Web開發(fā)中,雖然Session在一定程度上可以實(shí)現(xiàn)用戶認(rèn)證和狀態(tài)管理,但它也有一些局限性。
Session的局限性
假設(shè)你在經(jīng)營(yíng)一個(gè)大型在線購(gòu)物商城。當(dāng)用戶登錄你的商城時(shí),服務(wù)器會(huì)創(chuàng)建一個(gè)Session來記錄用戶的登錄狀態(tài)。這個(gè)Session就像在商城服務(wù)臺(tái)為用戶準(zhǔn)備的專屬卡,記錄用戶的身份信息。
然而,當(dāng)你的商城業(yè)務(wù)越來越繁忙,許多用戶同時(shí)在線購(gòu)物時(shí),服務(wù)器需要為每個(gè)用戶保存這個(gè)Session信息,這將占用大量的服務(wù)器內(nèi)存資源。此外,如果你的商城使用多個(gè)服務(wù)器共享流量(例如通過負(fù)載均衡器),那么需要復(fù)雜的機(jī)制來確保當(dāng)用戶在不同服務(wù)器之間切換時(shí),他們的Session信息能夠正確傳輸和識(shí)別。否則,用戶可能會(huì)突然被登出或無法正常購(gòu)物。
另外,假設(shè)用戶在手機(jī)上購(gòu)物時(shí),突然有緊急事務(wù)需要外出。這時(shí),如果用戶再次使用另一設(shè)備(如平板)訪問你的商城,由于Session通常綁定在特定設(shè)備上,用戶可能需要重新登錄,這將給用戶帶來不便。
Token 的優(yōu)勢(shì)
現(xiàn)在,讓我們介紹 Token。Token 就像一個(gè)神奇的通行證。當(dāng)用戶成功登錄后,服務(wù)器會(huì)生成一個(gè)包含用戶身份信息的 Token,并將其返回給用戶。用戶可以將這個(gè) Token 保存在自己的設(shè)備上(例如瀏覽器的本地存儲(chǔ)中)。
當(dāng)用戶在商場(chǎng)瀏覽產(chǎn)品、加入購(gòu)物車或結(jié)賬時(shí),只需在每次請(qǐng)求中攜帶這個(gè) Token。服務(wù)器收到請(qǐng)求后,可以通過驗(yàn)證 Token 的有效性來判斷用戶的身份和權(quán)限,而不必查找和管理復(fù)雜的 Session 信息。
例如,用戶在手機(jī)上登錄商場(chǎng)并獲得 Token。當(dāng)用戶外出并再次使用平板電腦訪問商場(chǎng)時(shí),只需在平板瀏覽器中提供這個(gè) Token,服務(wù)器就能立即識(shí)別用戶的身份,而用戶無需再次登錄。此外,無論商場(chǎng)中有多少用戶同時(shí)在線,服務(wù)器都無需為每個(gè)用戶保存大量的 Session 信息,只需驗(yàn)證每次請(qǐng)求的 Token,大大減輕了服務(wù)器的負(fù)擔(dān)。
另外,Token 可以與第三方服務(wù)輕松集成。例如,如果商場(chǎng)想與外部支付服務(wù)合作,只需將 Token 傳遞給支付服務(wù)。支付服務(wù)可以通過驗(yàn)證 Token 確定用戶的身份,而無需建立自己的 Session 管理機(jī)制。
現(xiàn)在讓我們?cè)敿?xì)介紹 Token。
什么是 Token?
訪問 Token
訪問 Token 是訪問資源接口(API)時(shí)所需的憑證。
Token 的組成并不是固定的。一個(gè)簡(jiǎn)單的 Token 組成包括:
- uid(用戶的唯一身份標(biāo)識(shí)符);
- time(當(dāng)前時(shí)間的時(shí)間戳);
- sign(簽名,是從 Token 的前幾個(gè)數(shù)字中通過哈希算法壓縮而成的十六進(jìn)制字符串)。
import java.security.MessageDigest;
import java.util.Base64;
import java.util.Date;
public class TokenGenerator {
public static String generateToken(int uid) {
long time = new Date().getTime();
String tokenContent = uid + "-" + time;
// 為 Token 內(nèi)容生成簽名。
String sign = generateSign(tokenContent);
return uid + "-" + time + "-" + sign;
}
// 使用 SHA-256 哈希算法為給定內(nèi)容生成簽名。
private static String generateSign(String content) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(content.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
// 將每個(gè)字節(jié)轉(zhuǎn)換為兩位十六進(jìn)制字符串。
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.substring(0, 8);
} catch (Exception e) {
return null;
}
}
public static void main(String[] args) {
int uid = 1234;
String token = generateToken(uid);
System.out.println("生成的 Token: " + token);
}
}
輸出:
生成的 Token: 1234-1729530432169-3638dd14
它具有以下特點(diǎn):
- 服務(wù)器是無狀態(tài)的,具有良好的可擴(kuò)展性。
- 支持移動(dòng)設(shè)備。
- 安全性足夠高。
- 支持跨程序調(diào)用。
Token 驗(yàn)證過程:
圖片
從上述過程可以知道,Token 需要在每個(gè)后續(xù)請(qǐng)求中攜帶,因此 Token 需要放置在 HTTP Header 中?;?Token 的用戶驗(yàn)證是一種服務(wù)器端無狀態(tài)的驗(yàn)證方法,服務(wù)器無需存儲(chǔ) Token 數(shù)據(jù)。解析 Token 的計(jì)算時(shí)間換取了 Session 的存儲(chǔ)空間,從而減輕了服務(wù)器的壓力,減少了頻繁的數(shù)據(jù)庫(kù)查詢操作。
此外,Token 完全由應(yīng)用程序自己管理,因此可以避免同源策略的限制。
Refresh Token 是另一種專用于刷新訪問 Token 的 Token。如果沒有 Refresh Token,訪問 Token 也可以被刷新,但每次刷新時(shí)用戶需要輸入登錄用戶名和密碼,這會(huì)非常麻煩。有了 Refresh Token,這個(gè)麻煩就可以減少。客戶端直接使用 Refresh Token 更新訪問 Token,而用戶無需進(jìn)行額外操作。
訪問 Token 的有效期通常較短。當(dāng)訪問 Token 由于過期變得無效時(shí),可以使用 Refresh Token 獲取新的 Token。如果 Refresh Token 也過期,用戶只能重新登錄。
此外,Refresh Token 和過期時(shí)間存儲(chǔ)在服務(wù)器的數(shù)據(jù)庫(kù)中,僅在申請(qǐng)新訪問 Token 時(shí)進(jìn)行驗(yàn)證。這不會(huì)影響業(yè)務(wù)接口的響應(yīng)時(shí)間,也不需要像 Session 一樣一直保存在內(nèi)存中以處理大量請(qǐng)求。
JSON Web Token
Token 是一個(gè)更廣泛的概念,而 JSON Web Token(JWT)是一種具有特定結(jié)構(gòu)和特性的 Token。JWT 在某些場(chǎng)景中具有優(yōu)勢(shì),例如需要自包含的認(rèn)證信息、跨平臺(tái)使用,以及對(duì)可擴(kuò)展性有較高要求的場(chǎng)景。我們將在后續(xù)文章中提供對(duì) JWT 的具體介紹。
結(jié)論
通過對(duì) Token 的深入分析,我們可以看到,它在現(xiàn)代 web 應(yīng)用中的重要性不可忽視。Token 不僅為用戶提供了便捷的訪問方式,減少了重復(fù)登錄的麻煩,還通過無狀態(tài)的驗(yàn)證機(jī)制大幅減輕了服務(wù)器的壓力。這種設(shè)計(jì)使得系統(tǒng)能夠更好地應(yīng)對(duì)高并發(fā)的請(qǐng)求,確保良好的用戶體驗(yàn)。
此外,Token 的靈活性使其能夠與第三方服務(wù)輕松集成,為開發(fā)者提供了更大的自由度。尤其是在需要與外部支付服務(wù)或其他 API 進(jìn)行交互的場(chǎng)景中,Token 的使用簡(jiǎn)化了身份驗(yàn)證的復(fù)雜性,提升了整體效率。
最后,隨著對(duì)數(shù)據(jù)安全和隱私保護(hù)要求的提高,Token 認(rèn)證特別是 JWT 的自包含特性,將在未來的開發(fā)中扮演更為重要的角色。我們期待在后續(xù)的文章中,進(jìn)一步探討 JWT 的具體實(shí)現(xiàn)及其在各種場(chǎng)景下的應(yīng)用潛力。通過掌握 Token 的工作原理和應(yīng)用策略,開發(fā)者將能夠設(shè)計(jì)出更加安全、高效的系統(tǒng),滿足不斷變化的市場(chǎng)需求。