從狀態說起
[HTTP 無狀態]
我們知道,HTTP是無狀態的,也就是說,HTTP請求方和回應方間無法維護狀態,都是一次性的,它不知道前后的請求都發生了什么
但有的場景下,我們需要維護狀態,最常見的,一個用戶登錄微博,發布,關注,評論,都是應該在登錄后的用戶狀態下的
[標記]
在學校或公司,入學入職那一天起,會錄入你的身份、賬戶資訊,然后給你發個卡,今后在園區內,你的門禁、打卡、消費都只需要刷這張卡
[前端存盤]
這就涉及一發,一存,一帶,發好辦,登錄介面直接回傳給前端,存盤就需要前端想辦法了,前提是,你要把卡帶在身上
前端的存盤方式有很多
- 掛載到全域變數上,但這是個[體驗卡],一次重繪頁面就沒了
- 可以存到cookie,localStorage里,這屬于[會員卡],無論怎么重繪,只要瀏覽器沒清掉或者過期,就一直拿著這個狀態
基石:cookie
可是前端好麻煩啊,又要自己存,又要想辦法帶出去,有沒有不用操心的?
有,cookie
cookie也是前端存盤的一種,但相比于localStorage等其他方式,借助HTTP頭,瀏覽器能力,cookie可以做到前端無感知
一般程序是這樣的:
- 在提供標記的介面,通過 HTTP 回傳頭的 Set-Cookie 欄位,直接「種」到瀏覽器上
- 瀏覽器發起請求時,會自動把 cookie 通過 HTTP 請求頭的 Cookie 欄位,帶給介面
[配置:Domain / Path]
你不能拿清華的校園卡進北大
cookie 是要限制:通過Domain(域) / Path(路徑)兩級(空間范圍)
Domain屬性指定瀏覽器發出 HTTP 請求時,哪些域名要附帶這個 Cookie,如果沒有指定該屬性,瀏覽器會默認將其設為當前 URL 的一級域名,比如 www.example.com 會設為 example.com,而且以后如果訪問example.com的任何子域名,HTTP 請求也會帶上這個 Cookie,如果服務器在Set-Cookie欄位指定的域名,不屬于當前域名,瀏覽器會拒絕這個 Cookie,
Path屬性指定瀏覽器發出 HTTP 請求時,哪些路徑要附帶這個 Cookie,只要瀏覽器發現,Path屬性是 HTTP 請求路徑的開頭一部分,就會在頭資訊里面帶上這個 Cookie,比如,PATH屬性是/,那么請求/docs路徑也會包含該 Cookie,當然,前提是域名必須一致
[配置:Expires / Max-Age]
你畢業了卡就不好使了
cookie 還可以限制 通過Expires,Max-Age中的一種 (時間范圍)
Expires屬性指定一個具體的到期時間,到了指定時間以后,瀏覽器就不再保留這個 Cookie,它的值是 UTC 格式,如果不設定該屬性,或者設為null,Cookie 只在當前會話(session)有效,瀏覽器視窗一旦關閉,當前 Session 結束,該 Cookie 就會被洗掉,另外,瀏覽器根據本地時間,決定 Cookie 是否過期,由于本地時間是不精確的,所以沒有辦法保證 Cookie 一定會在服務器指定的時間過期,
Max-Age屬性指定從現在開始 Cookie 存在的秒數,比如60 * 60 * 24 * 365(即一年),過了這個時間以后,瀏覽器就不再保留這個 Cookie,
如果同時指定了Expires和Max-Age,那么Max-Age的值將優先生效,
如果Set-Cookie欄位沒有指定Expires或Max-Age屬性,那么這個 Cookie 就是 Session Cookie,即它只在本次對話存在,一旦用戶關閉瀏覽器,瀏覽器就不會再保留這個 Cookie
[配置:Secure / HttpOnly]
有的學校規定,不帶卡套不讓刷(什么奇葩學校,假設);有的學校不讓自己給卡貼貼紙,
Secure屬性指定瀏覽器只有在加密協議 HTTPS 下,才能將這個 Cookie 發送到服務器,另一方面,如果當前協議是 HTTP,瀏覽器會自動忽略服務器發來的Secure屬性,該屬性只是一個開關,不需要指定值,如果通信是 HTTPS 協議,該開關自動打開
HttpOnly屬性指定該 Cookie 無法通過 JavaScript 腳本拿到,主要是Document.cookie屬性、XMLHttpRequest物件和 Request API 都拿不到該屬性,這樣就防止了該 Cookie 被腳本讀到,只有瀏覽器發出 HTTP 請求時,才會帶上該 Cookie
[HTTP 頭對cookie的讀寫]
HTTP 回傳的一個 Set-Cookie 頭用于向瀏覽器寫入「一條(且只能是一條)」cookie,格式為 cookie 鍵值 + 配置鍵值
Set-Cookie: username=jimu; domain=jimu.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
那我想一次多 set 幾個 cookie 怎么辦?多給幾個 Set-Cookie 頭(一次 HTTP 請求中允許重復)
[前端對cookie的讀寫]
前端可以自己創建 cookie,如果服務端創建的 cookie 沒加HttpOnly,那恭喜你也可以修改他給的 cookie,
呼叫document.cookie可以創建、修改 cookie,和 HTTP 一樣,一次document.cookie能且只能操作一個 cookie
document.cookie = 'username=jimu; domain=jimu.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly';
呼叫document.cookie也可以讀到 cookie,也和 HTTP 一樣,能讀到所有的非HttpOnly cookie
console.log(document.cookie);// username=jimu; height=180; weight=80
那有了存盤工具,接下來怎么做呢?
應用方案:服務端session
現在回想下,你刷卡的時候發生了什么?
其實你的卡上只存了一個 id(可能是你的學號),刷的時候物業系統去查你的資訊、賬戶,再決定「這個門你能不能進」「這個雞腿去哪個賬戶扣錢」
這種操作,在前后端鑒權系統中,叫 session
典型的 session 登陸/驗證流程:

- 瀏覽器登錄發送賬號密碼,服務端查用戶庫,校驗用戶
- 服務端把用戶登錄狀態存為Session,生成一個sessionId
- 通過登錄介面回傳,把sessionId set到cookie上
- 此后瀏覽器再請求業務介面,sessionId 隨cookie帶上
- 服務端查sessionId校驗session
- 成功后正常做業務處理,回傳結果
[Session 的存盤方式]
服務端只是給 cookie 一個 sessionId,而 session 的具體內容(可能包含用戶資訊、session 狀態等),要自己存一下,存盤的方式有幾種:
Redis(推薦):記憶體型資料庫,redis中文官方網站,以 key-value 的形式存,正合 sessionId-sessionData 的場景;且訪問快,
記憶體:直接放到變數里,一旦服務重啟就沒了
資料庫:普通資料庫,性能不高
[Session 的過期和銷毀]
很簡單,只要把存盤的 session 資料銷毀就可以
應用方案:token
我又想到學校,在沒有校園卡技術以前,我們都靠「學生證」,門衛小哥直接對照我和學生證上的臉,確認學生證有效期、年級等資訊,就可以放行了
回過頭來想想,一個登錄場景,也不必往 session 存太多東西,那為什么不直接打包到 cookie 中呢?這樣服務端不用存了,每次只要核驗 cookie 帶的「證件」有效性就可以了,也可以攜帶一些輕量的資訊
這種方式通常被叫做 token

token的流程是這樣的:
- 用戶登錄,服務端校驗賬號密碼,獲取用戶資訊
- 把用戶資訊,token配置編碼成token,通過cookie set到瀏覽器
- 此后用戶請求業務介面,通過cookie攜帶token
- 介面校驗token有效性,進行正常業務介面處理
session 和 token
狹義上,我們通常認為 session 是「種在 cookie 上、資料存在服務端」的認證方案,token 是「客戶端存哪都行、資料存在 token 里」的認證方案,對 session 和 token 的對比本質上是「客戶端存 cookie / 存別地兒」、「服務端存資料 / 不存資料」的對比,
單點登錄
前面我們已經知道了,在同域下的客戶端/服務端認證系統中,通過客戶端攜帶憑證,維持一段時間內的登錄狀態,
但當我們業務線越來越多,就會有更多業務系統分散到不同域名下,就需要「一次登錄,全線通用」的能力,叫做「單點登錄」
虛假”的單點登錄(主域名相同)
簡單的,如果業務系統都在同一主域名下,比如wenku.baidu.com tieba.baidu.com,就好辦了,可以直接把 cookie domain 設定為主域名 baidu.com,百度也就是這么干的
“真實”的單點登錄(主域名不同)
比如滴滴這么潮的公司,同時擁有didichuxing.com xiaojukeji.com didiglobal.com等域名,種 cookie 是完全繞不開的,
這要能實作「一次登錄,全線通用」,才是真正的單點登錄,
這種場景下,我們需要獨立的認證服務,通常被稱為 SSO

- 用戶進入A系統,沒有登錄憑證(ticket),A系統給他跳到SSO
- SSO沒登錄過,也就沒有sso系統下沒有憑證(注意這個和前面 A ticket是兩回事),輸入賬號密碼登錄
- SSO賬號密碼驗證成功,通過介面回傳做兩件事,一是種下sso系統下憑證(記錄用戶登錄狀態);二是下發一個ticket
- 客戶端拿到ticket,保存起來,帶著請求系統A介面
- 系統A檢驗ticket,成功后正常處理業務請求
- 此時用戶第一次進入系統B,沒有登錄憑證(ticket),B系統給他跳到SSO
- SSO登錄過,系統下有憑證,不用再次登錄,只需要下發ticket
- 客戶端拿到ticket,保存起來,帶著請求系統B介面
總結
- HTTP是無狀態的,為了維持前后請求,需要前端存盤標記
- cookie 是一種完善的標記方式,通過 HTTP 頭或 js 操作,有對應的安全策略,是大多數狀態管理方案的基石
- session 是一種狀態管理方案,前端通過 cookie 存盤 id,后端存盤資料,但后端要處理分布式問題
- token 是另一種狀態管理方案,相比于 session 不需要后端存盤,資料全部存在前端
- token 的編碼技術,通常基于 base64,或增加加密演算法防篡改,jwt 是一種成熟的編碼方案
- session 和 token 的對比就是「用不用cookie」和「后端存不存」的對比
- 單點登錄要求不同域下的系統「一次登錄,全線通用」,通常由獨立的 SSO 系統記錄登錄狀態、下發 ticket,各業務系統配合存盤和認證 ticket
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/307224.html
標籤:java
上一篇:cgb2108-day07
