登錄是每個網站中都經常用到的一個功能,在頁面上我們輸入賬號密碼,敲一下回車鍵,就登錄了,但這背後的登錄原理你是否清楚呢?今天我們就來介紹幾種常用的登錄方式。
- Cookie + Session 登錄
- Token 登錄
- SSO 單點登錄
- OAuth 第三方登錄
Cookie + Session 登錄
HTTP 是一種無狀態的協議,客户端每次發送請求時,首先要和服務器端建立一個連接,在請求完成後又會斷開這個連接。這種方式可以節省傳輸時佔用的連接資源,但同時也存在一個問題:每次請求都是獨立的,服務器端無法判斷本次請求和上一次請求是否來自同一個用户,進而也就無法判斷用户的登錄狀態。
為了解決 HTTP 無狀態的問題,Lou Montulli 在 1994 年的時候,推出了 Cookie。
Cookie 是服務器端發送給客户端的一段特殊信息,這些信息以文本的方式存放在客户端,客户端每次向服務器端發送請求時都會帶上這些特殊信息。
當訪問一個頁面的時候,服務器在下行http報文中,命令瀏覽器存儲一個字符串;瀏覽器再訪問同一個域的時候,將把這個字符串攜帶到上行http請求中。
第一次訪問一個服務器,不可能攜帶cookie。必須是服務器得到這次請求,在下行響應報頭中,攜帶cookie信息,此後每一次瀏覽器往這個服務器發出的請求,都會攜帶這個cookie。
有了 Cookie 之後,服務器端就能夠獲取到客户端傳遞過來的信息了,如果需要對信息進行驗證,還需要通過 Session。
客户端請求服務端,服務端會為這次請求開闢一塊內存空間,這個便是 Session 對象。
有了 Cookie 和 Session 之後,我們就可以進行登錄認證了。
1. Cookie + Session 實現流程
session在計算機網絡應用中被稱為“會話控制”。客户端瀏覽器訪問網站的時候,服務器會向客户瀏覽器發送一個每個用户特有的會話編號sessionID,讓他進入到cookie裏,服務器同時也把sessionID和對應的用户信息、用户操作記錄在服務器上,這些記錄就是session。客户端瀏覽器再次訪問時,會發送cookie給服務器,其中就包含sessionID。服務器從cookie裏找到sessionID,再根據sessionID找到以前記錄的用户信息就可以知道他之前操控些、訪問過哪裏。
Cookie + Session 的登錄方式是最經典的一種登錄方式,現在仍然有大量的企業在使用。
用户首次登錄時:
- 用户訪問
a.com/pageA,並輸入密碼登錄。 - 服務器驗證密碼無誤後,會創建 SessionId,並將它保存起來。
- 服務器端響應這個 HTTP 請求,並通過 Set-Cookie 頭信息,將 SessionId 寫入 Cookie 中。
- 瀏覽器會根據Set-Cookie中的信息,自動將SessionId存儲至cookie中。
服務器端的 SessionId 可能存放在很多地方,例如:內存、文件、數據庫等。
第一次登錄完成之後,後續的訪問就可以直接使用 Cookie 進行身份驗證了:
- 用户訪問
a.com/pageB頁面時,會自動帶上第一次登錄時寫入的 Cookie。 - 服務器端比對 Cookie 中的 SessionId 和保存在服務器端的 SessionId 是否一致。
- 如果一致,則身份驗證成功。
2. Cookie + Session 存在的問題
雖然我們使用 Cookie + Session 的方式完成了登錄驗證,但仍然存在一些問題:
- 由於服務器端需要對接大量的客户端,也就需要存放大量的 SessionId,這樣會導致服務器壓力過大。
- 如果服務器端是一個集羣,為了同步登錄態,需要將 SessionId 同步到每一台機器上,無形中增加了服務器端維護成本。
- 由於 SessionId 存放在 Cookie 中,所以無法避免 CSRF 攻擊。
Token 登錄認證
為了解決 Session + Cookie 機制暴露出的諸多問題,我們可以使用 Token 的登錄方式。
Token是服務端生成的一串字符串,以作為客户端請求的一個令牌。當第一次登錄後,服務器會生成一個 Token 並返回給客户端,客户端後續訪問時,只需帶上這個 Token 即可完成身份認證。
1. Token 機制實現流程
用户首次登錄時:
- 用户輸入賬號密碼,並點擊登錄。
- 服務器端驗證賬號密碼無誤,創建 Token。
- 服務器端將 Token 返回給客户端,由客户端自由保存。
後續頁面訪問時:
- 用户訪問
a.com/pageB時,帶上第一次登錄時獲取的 Token。 - 服務器端驗證 Token ,有效則身份驗證成功。
2. Token 機制的特點
根據上面的案例,我們可以分析出 Token 的優缺點:
- 服務器端不需要存放 Token,所以不會對服務器端造成壓力,即使是服務器集羣,也不需要增加維護成本。
- Token 可以存放在前端任何地方,可以不用保存在 Cookie 中,提升了頁面的安全性。
- Token 下發之後,只要在生效時間之內,就一直有效,如果服務器端想收回此 Token 的權限,並不容易。
3. Token 的生成方式
最常見的 Token 生成方式是使用 JWT(Json Web Token),它是一種簡潔的,自包含的方法用於通信雙方之間以 JSON 對象的形式安全的傳遞信息。
上文中我們説到,使用 Token 後,服務器端並不會存儲 Token,那怎麼判斷客户端發過來的 Token 是合法有效的呢?
答案其實就在 Token 字符串中,其實 Token 並不是一串雜亂無章的字符串,而是通過多種算法拼接組合而成的字符串,我們來具體分析一下。
JWT 算法主要分為 3 個部分:header(頭信息),playload(消息體),signature(簽名)。
header 部分指定了該 JWT 使用的簽名算法:
header = '{"alg":"HS256","typ":"JWT"}' // `HS256` 表示使用了 HMAC-SHA256 來生成簽名。
playload 部分表明了 JWT 的意圖:
payload = '{"loggedInAs":"admin","iat":1422779638}' //iat 表示令牌生成的時間
signature 部分為 JWT 的簽名,主要為了讓 JWT 不能被隨意篡改,簽名的方法分為兩個步驟:
- 輸入
base64url編碼的 header 部分、.、base64url編碼的 playload 部分,輸出 unsignedToken。 - 輸入服務器端私鑰、unsignedToken,輸出 signature 簽名。
const base64Header = encodeBase64(header)
const base64Payload = encodeBase64(payload)
const unsignedToken = `${base64Header}.${base64Payload}`
const key = '服務器私鑰'
signature = HMAC(key, unsignedToken)
最後的 Token 計算如下:
最後的 Token 計算如下:
const base64Header = encodeBase64(header)
const base64Payload = encodeBase64(payload)
const base64Signature = encodeBase64(signature)
token = `${base64Header}.${base64Payload}.${base64Signature}`
服務器在判斷 Token 時:
const [base64Header, base64Payload, base64Signature] = token.split('.')
const signature1 = decodeBase64(base64Signature)
const unsignedToken = `${base64Header}.${base64Payload}`
const signature2 = HMAC('服務器私鑰', unsignedToken)
if(signature1 === signature2) {
return '簽名驗證成功,token 沒有被篡改'
}
const payload = decodeBase64(base64Payload)
if(new Date() - payload.iat < 'token 有效期'){
return 'token 有效'
}
有了 Token 之後,登錄方式已經變得非常高效,接下來我們介紹另外兩種登錄方式。
SSO 單點登錄
單點登錄指的是在公司內部搭建一個公共的認證中心,公司下的所有產品的登錄都可以在認證中心裏完成,一個產品在認證中心登錄後,再去訪問另一個產品,可以不用再次登錄,即可獲取登錄狀態。
1. SSO 機制實現流程
用户首次訪問時,需要在認證中心登錄:
- 用户訪問網站
a.com下的 pageA 頁面。 - 由於沒有登錄,則會重定向到認證中心,並帶上回調地址
www.sso.com?return_uri=a.com/pageA,以便登錄後直接進入對應頁面。 - 用户在認證中心輸入賬號密碼,提交登錄。
- 認證中心驗證賬號密碼有效,然後重定向
a.com?ticket=123帶上授權碼 ticket,並將認證中心sso.com的登錄態寫入 Cookie。 - 在
a.com服務器中,拿着 ticket 向認證中心確認,授權碼 ticket 真實有效。 - 驗證成功後,服務器將登錄信息寫入 Cookie(此時客户端有 2 個 Cookie 分別存有
a.com和sso.com的登錄態)。
認證中心登錄完成之後,繼續訪問 a.com 下的其他頁面:
這個時候,由於 a.com 存在已登錄的 Cookie 信息,所以服務器端直接認證成功。
如果認證中心登錄完成之後,訪問 b.com 下的頁面:
這個時候,由於認證中心存在之前登錄過的 Cookie,所以也不用再次輸入賬號密碼,直接返回第 4 步,下發 ticket 給 b.com 即可。
2. SSO 單點登錄退出
目前我們已經完成了單點登錄,在同一套認證中心的管理下,多個產品可以共享登錄態。現在我們需要考慮退出了,即:在一個產品中退出了登錄,怎麼讓其他的產品也都退出登錄?
原理其實不難,可以回過頭來看第 5 步,每一個產品在向認證中心驗證 ticket 時,其實可以順帶將自己的退出登錄 api 發送到認證中心。
當某個產品 c.com 退出登錄時:
- 清空
c.com中的登錄態 Cookie。 - 請求認證中心
sso.com中的退出 api。 - 認證中心遍歷下發過 ticket 的所有產品,並調用對應的退出 api,完成退出。
OAuth 第三方登錄
在上文中,我們使用單點登錄完成了多產品的登錄態共享,但都是建立在一套統一的認證中心下,對於一些小型企業,未免太麻煩,有沒有一種登錄能夠做到開箱即用?
其實是有的,很多大廠都會提供自己的第三方登錄服務,我們一起來分析一下。
1. OAuth 機制實現流程
這裏以微信開放平台的接入流程為例:
- 首先,
a.com的運營者需要在微信開放平台註冊賬號,並向微信申請使用微信登錄功能。 - 申請成功後,得到申請的 appid、appsecret。
- 用户在
a.com上選擇使用微信登錄。 - 這時會跳轉微信的 OAuth 授權登錄,並帶上
a.com的回調地址。 - 用户輸入微信賬號和密碼,登錄成功後,需要選擇具體的授權範圍,如:授權用户的頭像、暱稱等。
- 授權之後,微信會根據拉起
a.com?code=123,這時帶上了一個臨時票據 code。 - 獲取 code 之後,
a.com會拿着 code 、appid、appsecret,向微信服務器申請 token,驗證成功後,微信會下發一個 token。 - 有了 token 之後,
a.com就可以憑藉 token 拿到對應的微信用户頭像,用户暱稱等信息了。 a.com提示用户登錄成功,並將登錄狀態寫入 Cooke,以作為後續訪問的憑證。
總結
本文介紹了 4 種常見的登錄方式,原理應該大家都清楚了,總結一下這 4 種方案的使用場景:
- Cookie + Session 歷史悠久,適合於簡單的後端架構,需開發人員自己處理好安全問題。
- Token 方案對後端壓力小,適合大型分佈式的後端架構,但已分發出去的 token ,如果想收回權限,就不是很方便了。
- SSO 單點登錄,適用於中大型企業,想要統一內部所有產品的登錄方式。
- OAuth 第三方登錄,簡單易用,對用户和開發者都友好,但第三方平台很多,需要選擇合適自己的第三方登錄平台。