HTTP 是無狀態的協議
我們都知道 HTTP 是無狀態(stateless)的協議:HTTP 對於事務處理沒有記憶能力,不對請求和響應之間的通訊狀態進行儲存。 使用 HTTP 協議,每當有新的請求傳送時,就會有對應的新響應產生。協議本身並不保留之前一切的請求或響應報文的資訊。這是為了更快地處理大量事務,確保協議的可伸縮性,而特意把 HTTP 協議設計成如此簡單的。
可是,隨著 Web 的發展,早期這種無狀態的特性卻帶來了很多不方便性,比如說使用者登入新浪微博,在登入頁輸入使用者名稱、密碼之後進入首頁,但是由於 HTTP 是無狀態的,HTTP 並不知道上一次的 HTTP 請求是否通過了驗證,更無法得知當前使用者的具體資訊。
最簡單的解決方案就是在所有的請求裡面都帶上使用者名稱和密碼,這樣雖然可行,但是大大加重了伺服器的負擔(對於每個 request 都需要到資料庫驗證),而且使用者也要每進入一個頁面輸入一次密碼,毫無使用者體驗可言。
為此,引入了各種身份認證機制,這裡說一下 Cookie-Session 和 Jwt 機制。
Cookie-Session 機制
什麼是 Cookie?
Cookie 是由 HTTP 伺服器設定的,儲存在瀏覽器中的小型文字檔案,其內容為一系列的鍵值對。在 Chrome 中,通過開發者工具 -> Application -> Cookies 可檢視
這裡簡單介紹一下一些欄位意思:Expires:Cookie 的過期時間,預設過期時間為使用者關閉瀏覽器時。
HttpOnly:指示瀏覽器不要在除了 HTTP(或者 HTTPS)請求之外暴露 Cookie。通過 JavaScript 指令碼無法訪問到 Cookie,能有效防止 XSS 攻擊
Secure:設定 Cookie 的 Secure 屬性為 true
時,意味著 Cookie 通訊只限於加密傳輸,指示瀏覽器僅僅在通過安全/加密連線才能使用 Cookie。也就是說 Cookie 只有在 HTTPS 協議下才能上傳到伺服器,而 HTTP 協議下是無法上傳的。
Cookie 傳遞過程
- 瀏覽器向某個 URL 傳送請求
- 對應的伺服器收到該 HTTP 請求,生成要發給瀏覽器的 HTTP 響應
- 在響應頭中加入
Set-Cookie
欄位,值為要設定的的Cookie - 瀏覽器收到來自伺服器的 HTTP 響應
- 瀏覽器在響應頭中發現了
Set-Cookie
欄位,就會將該欄位的值儲存在記憶體或者是硬碟中。 - 當下一次向該伺服器傳送 HTTP 請求時,會將伺服器設定的 Cookie 附加在 HTTP 請求的欄位
Cookie
中。 - 伺服器收到這個 HTTP 請求之後,發現請求頭中有
Cookie
欄位,就知道了已經處理過這個使用者的請求了。 - 過期的 Cookie 會被刪除
什麼是 Session?
相對於儲存在瀏覽器中的 Cookie,Session 是儲存在伺服器端的,避免了在客戶端中儲存敏感資料。並且存取方式不同,Cookie 只能儲存 ASCII 字串,例如需要存取 Unicode 字元或者二進位制資料,需要先進行編碼。而Session中能夠存取任何型別的資料。Session 一般配合 Cookie 使用,也就是接下來要說到的 Cookie-Session 機制。
基於 Cookie-Session 身份驗證機制的過程
- 使用者輸入登入資訊
- 服務端驗證登入資訊是否正確,如果正確就在伺服器端為這個使用者建立一個 Session,並把 Session 存入資料庫
- 伺服器端會向客戶端返回帶有 sessionID 的 Cookie
- 客戶端接收到伺服器端發來的請求之後,看見響應頭中的
Set-Cookie
欄位,將 Cookie 儲存起來 - 接下來的請求中都會帶上這個 Cookie,伺服器將 sessionID 和 資料庫中的相匹配,如果有效則處理該請求
- 如果使用者登出,Session 會在客戶端和伺服器端都被銷燬
Session-Cookie 機制的缺陷
- 擴充套件性不好,當擁有多臺伺服器的情況下,如何共享 Session 會成為一個問題,也就是說,使用者第一個訪問的時候是伺服器 A,而第二個請求被轉發給了伺服器 B,那伺服器 B 無法得知其狀態。(舉例來說,A 網站和 B 網站是同一家公司的關聯服務。使用者只要在其中一個網站登入,再訪問另一個網站自動登入)
- 安全性不好,攻擊者可以利用本地 Cookie 進行欺騙和 CSRF 攻擊。
- Session 儲存在伺服器端,如果短時間內有大量使用者,會影響伺服器效能。
- 跨域問題,Cookie 屬於同源策略限制的內容之一。
Jwt 機制
JWT(JSON Web Token) 是由 RFC7519 定義的,是一個在雙方之間安全的傳達一組資訊的 JSON 物件。
JWT 組成
JWT 由三個部分組成:header、payload、signature
每個部分中間使用 .
來分隔,其中,header 和 payload 使用 Base64URL 進行編碼:
base64UrlEncode(header).base64UrlEncode(payload).signature
複製程式碼
header
header 部分是一個 JSON 物件,用來描述 JWT 的後設資料:
{
"typ": "JWT", // 表示物件是一個 JWT
"alg": "HS256" // 表示使用哪種 Hash 演算法來建立簽名,這裡是 HMAC-SHA256
}
複製程式碼
payload
payload 部分也是一個 JSON 物件,實際需要傳遞的資料被存放在這裡。我們除了使用官方提供的七個欄位之外,也可以使用自定義的私有欄位。
{
"sub": "title",
"name": "Yeoman"
}
複製程式碼
JWT 預設是不加密的,任何人都可以讀到,所以不要把祕密資訊放在這個部分。
signature
signature 是對前兩個部分的簽名,防止資料被篡改。
data = base64urlEncode( header ) + "." + base64urlEncode( payload );
signature = Hash( data, secret );
複製程式碼
使用 Base64URL 編碼的 header 和 payload 中間用 .
隔開,再使用 header 中指定的 Hash 演算法,加上金鑰對這個字串進行 Hash 得到 signature
工作流程
- 前端將自己的使用者名稱和密碼傳送到後端的介面
- 後端核對使用者名稱和密碼之後,將使用者的一些資訊作為 payload,生成 JWT
- 後端將 JWT 作為登入成功的返回結果返回給前端。前端可以將其結果儲存在 localStorage/sessionStorage 中,登出時刪除 JWT 即可。(最好不要儲存在 Cookie 中,用了 Cookie 就不能設定 HTTPonly,並且存在跨域問題)
- 每一次請求都將 JWT 放在 HTTP 請求頭中的
Authorization
位,這樣相比放在 Cookie 中可以跨域。
Authorization: Bearer <token>
複製程式碼
- 伺服器解碼 JWT,如果 token 有效,那麼處理這個請求
- 使用者登出,在客戶端刪除 token 即可,與服務端無關
JWT 特點
- JWT 預設是不加密的
- JWT 的目的是用來驗證來源可靠性,並不是保護資料和防止未經授權的訪問。(可以類比成一張電影票,只能驗證電影票是否是真的,電影票也有一些基本資訊,但是他人也可以使用你的電影票,如果可能的話)一旦暴露,任何人都可以獲得許可權。為了減少盜用,JWT 的有效期應該設定得比較短,對於一些比較重要的許可權,使用時應該再次對使用者進行認證。
- 最大的缺點是 token 過期處理問題,由於伺服器不儲存 Session 狀態,因此無法在使用過程中廢止或者更改許可權。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非伺服器部署額外的邏輯。
複習
這裡再次複習一下相關知識:
同源策略限制的內容
- Cookie、LocalStorage、SessionStorage、IndexedDB 等儲存性內容
- DOM 節點
- Ajax 傳送請求後,結果被瀏覽器攔截
Cookie 和 Session 的區別
- 存取方式不同:Cookie 只能儲存 ASCII 字串,例如需要存取 Unicode 字元或者二進位制資料,需要先進行編碼。而Session中能夠存取任何型別的資料
- 隱私策略不同:Cookie 儲存在瀏覽器中,Session 儲存在伺服器上。
- 伺服器壓力不同:Session 是保管在伺服器上的,每個使用者都會產生一個 Session 。假如併發訪問的使用者十分多,會產生大量的 Session ,耗費大量的記憶體。