前言
還記得之前在面試的時候,有一位面試官就問了,關于前端鑒權這塊,Token、Cookie、Session、JWT、單點登錄
是什么?有什么作用?你一般是怎么做的?以及你是怎么存儲的呢?那你又是怎么保證它
的安全的呢?
一頓連問下來,我是焦頭又爛額,欲言而又止.......
其實鑒權的方法有很多,下面我總結了常用的10種鑒權方法
,那么哪一種是最適合你的系統呢?哪一種又最安全呢?
那就讓我們從下文慢慢探索尋找答案吧 ~
通過這篇文章你將學到什么?
在介紹鑒權方法之前,我們先要了解的是:什么是認證、授權、鑒權、權限控制
以及他們之間的關系,有了他們做鋪墊,那么我們才能做到從始至終的了解透徹 ~
什么是認證?
認證(Identification)
是指根據聲明者所特有的識別信息,確認聲明者的身份。
白話文的意思就是:你需要用身份證證明你自己是你自己
。
比如我們常見的認證技術:
-
身份證
-
用戶名和密碼
-
用戶手機:手機短信、手機二維碼掃描、手勢密碼
-
用戶的電子郵箱
-
用戶的生物學特征:指紋、語音、眼睛虹膜
-
用戶的大數據識別
-
等等
授權(Authorization)
:在信息安全領域是指資源所有者
委派執行者
,賦予執行者
指定范圍的資源操作權限,以便對資源的相關操作。
在現實生活領域例如:銀行卡(由銀行派發)、門禁卡(由物業管理處派發)、鑰匙(由房東派發),這些都是現實生活中授權的實現方式。
在互聯網領域例如:web 服務器的 session 機制、web 瀏覽器的 cookie 機制、頒發授權令牌(token)等都是一個授權的機制。
什么是鑒權?
鑒權(Authentication)
在信息安全領域是指對于一個聲明者所聲明的身份權利,對其所聲明的真實性進行鑒別確認的過程。
若從授權出發,則會更加容易理解鑒權。授權和鑒權是兩個上下游相匹配的關系,先授權,后鑒權。
在現實生活領域:門禁卡需要通過門禁卡識別器,銀行卡需要通過銀行卡識別器;
在互聯網領域:校驗 session/cookie/token 的合法性和有效性
鑒權
是一個承上啟下的一個環節,上游它接受授權的輸出,校驗其真實性后,然后獲取權限(permission),這個將會為下一步的權限控制做好準備。
什么是權限控制?
權限控制(Access/Permission Control)
將可執行的操作定義為權限列表,然后判斷操作是否允許/禁止
對于權限控制,可以分為兩部分進行理解:一個是權限,另一個是控制。權限是抽象的邏輯概念,而控制是具體的實現方式。
在現實生活領域中:以門禁卡的權限實現為例,一個門禁卡,擁有開公司所有的門的權限;一個門禁卡,擁有管理員角色的權限,因而可以開公司所有的門。
在互聯網領域:通過 web 后端服務,來控制接口訪問,允許或拒絕訪問請求。
認證、授權、鑒權和權限控制的關系?
看到這里,我們應該明白了認證
、授權
、鑒權
和權限控制
這四個環節是一個前后依次發生
、上下游
的關系;
需要說明的是,這四個環節在有些時候會同時發生。例如在下面的幾個場景:
-
使用門禁卡開門:認證、授權、鑒權、權限控制四個環節一氣呵成,在瞬間同時發生
-
用戶的網站登錄:用戶在使用用戶名和密碼進行登錄時,認證和授權兩個環節一同完成,而鑒權和權限控制則發生在后續的請求訪問中,比如在選購物品或支付時。
這里提個小問題,供大家思考:認證和鑒權之間的關系?歡迎大家在評論區討論
既然我們已經了解了他們之間的關系,那么我們應該好好講講關于前端鑒權有哪些?以及他們之間存在的差異點又在哪里呢?
1. HTTP 基本鑒權
在 HTTP 中,基本認證方案(Basic Access Authentication)
是允許客戶端(通常指的就是網頁瀏覽器)在請求時,通過用戶提供用戶名和密碼的方式,實現對用戶身份的驗證。
因為幾乎所有的線上網站都不會走該認證方案,所以該方案大家了解即可1.1 認證流程圖1.2 認證步驟解析
-
客戶端(如瀏覽器):向服務器請求一個
受限的列表數據或資源
,例如字段如下GET /list/ HTTP/1.1
Host: www.baidu.com
Authorization: Basic aHR0cHdhdGNoOmY= -
服務器:客戶端你好,這個資源在安全區 baidu.com里,是受限資源,需要基本認證;
并且向客戶端返回 401 狀態碼(Unauthorized 未被授權的)以及附帶提供了一個認證域
www-Authenticate: Basic realm=”baidu.com”
要求進行身份驗證;其中
Basic
就是驗證的模式,而realm="baidu.com"
說明客戶端需要輸入這個安全域的用戶名和密碼,而不是其他域的HTTP/1.1 401 Unauthorized
www-Authenticate: Basic realm= "baidu.com" -
客戶端:服務器,我已經攜帶了用戶名和密碼給你了,你看一下;(注:如客戶端是瀏覽器,那么此時會自動彈出一個彈窗,讓用戶輸入用戶名和密碼);
輸入完用戶名和密碼后,則客戶端將用戶名及密碼以 Base64 加密方式發送給服務器
傳送的格式如下 (其中 Basic 內容為:用戶名:密碼 的 ase64 形式):
GET /list/ HTTP/1.1
Authorization: Basic Ksid2FuZzp3YW5n== -
服務器:客戶端你好,我已經校驗了
Authorization
字段你的用戶名和密碼,是正確的,這是你要的資源。HTTP/1.1 200 OK
...
簡單,基本所有流行的瀏覽器都支持
1.4 缺點
-
不安全:
-
由于是基于 HTTP 傳輸,所以它在網絡上幾乎是裸奔的,雖然它使用了 Base64 來編碼,但這個編碼很容易就可以解碼出來。
-
即使認證內容無法被解碼為原始的用戶名和密碼也是不安全的,惡意用戶可以再獲取了認證內容后使用其不斷的享服務器發起請求,這就是所謂的重放攻擊。
無法主動注銷:
-
由于 HTTP 協議沒有提供機制清除瀏覽器中的 Basic 認證信息,除非標簽頁或瀏覽器關閉、或用戶清除歷史記錄。
內部網絡,或者對安全要求不是很高的網絡。
2. Session-Cookie 鑒權
Session-Cookie
認證是利用服務端的Session(會話)和瀏覽器(客戶端)的 Cookie 來實現的前后端通信認證模式。
在理解這句話之前我們先簡單了解下什么是 Cookie
以及什么是 Session
?
2.1 什么是 Cookie
眾所周知,HTTP 是無狀態的協議
(對于事務處理沒有記憶能力,每次客戶端和服務端會話完成時,服務端不會保存任何會話信息);
所以為了讓服務器區分不同的客戶端,就必須主動的去維護一個狀態,這個狀態用于告知服務端前后兩個請求是否來自同一瀏覽器。而這個狀態可以通過Cookie
去實現。
特點:
-
Cookie 存儲在客戶端,可隨意篡改,不安全
-
有大小限制,最大為 4kb
-
有數量限制,一般一個瀏覽器對于一個網站只能存不超過 20 個 Cookie,瀏覽器一般只允許存放 300個 Cookie
-
Android 和 IOS 對 Cookie 支持性不好
-
Cookie 是不可跨域的,但是一級域名和二級域名是允許共享使用的(靠的是 domain)
Session 的抽象概念是會話,是無狀態協議通信過程中,為了實現中斷/繼續操作,將用戶和服務器之間的交互進行的一種抽象;
具體來說,是服務器生成的一種 Session 結構,可以通過多種方式保存,如內存、數據庫、文件等,大型網站一般有專門的 Session 服務器集群來保存用戶會話;
原理流程:
-
客戶端:用戶向服務器首次發送請求;
-
服務器:接收到數據并自動為該用戶創建特定的 Session / Session ID,來標識用戶并跟蹤用戶當前的會話過程;
-
客戶端:瀏覽器收到響應獲取會話信息,并且會在下一次請求時帶上 Session / Session ID;
-
服務器:服務器提取后會與本地保存的 Session ID進行對比找到該特定用戶的會話,進而獲取會話狀態;
-
至此客戶端與服務器的通信變成有狀態的通信;
特點:
-
Session 保存在服務器上;
-
通過服務器自帶的加密協議進行;
與 Cookie 的差異:
-
安全性:Cookie 由于保存在客戶端,可隨意篡改,Session 則不同存儲在服務器端,無法偽造,所以 Session 的安全性更高;
-
存取值的類型不同:Cookie 只支持字符串數據,Session 可以存任意數據類型;
-
有效期不同:Cookie 可設置為長時間保持,Session 一般失效時間較短;
-
存儲大小不同:Cookie 保存的數據不能超過 4K;
看到這里可能就有同學想到了,Session-Cookie是不是就是把Session存儲在了客戶端的Cookie中呢? Bingo,的確是這樣的,我們接著往下看2.3 Session-Cookie 的認證流程圖2.4 Session-Cookie 認證步驟解析
-
客戶端:向服務器發送登錄信息用戶名/密碼來請求登錄校驗;
-
服務器:驗證登錄的信息,驗證通過后自動創建 Session(將 Session 保存在內存中,也可以保存在 redis 中),然后給這個 Session 生成一個唯一的標識字符串會話身份憑證
session_id
(通常稱為sid
),并在響應頭Set-Cookie
中設置這個唯一標識符;注:可以使用簽名對sid進行加密處理,服務端會根據對應的secret密鑰進行解密 (非必須步驟)
-
客戶端:收到服務器的響應后會解析響應頭,并自動將
sid
保存在本地 Cookie 中,瀏覽器在下次 HTTP 請求時請求頭會自動附帶上該域名下的 Cookie 信息; -
服務器:接收客戶端請求時會去解析請求頭 Cookie 中的
sid
,然后根據這個sid
去找服務端保存的該客戶端的sid
,然后判斷該請求是否合法;
-
Cookie 簡單易用
-
Session 數據存儲在服務端,相較于 JWT 方便進行管理,也就是當用戶登錄和主動注銷,只需要添加刪除對應的 Session 就可以了,方便管理
-
只需要后端操作即可,前端可以無感等進行操作;
-
依賴 Cookie,一旦用戶在瀏覽器端禁用 Cookie,那么就 GG 思密達了;
-
非常不安全,Cookie 將數據暴露在瀏覽器中,增加了數據被盜的風險(容易被 CSRF 等攻擊);
-
Session 存儲在服務端,增大了服務端的開銷,用戶量大的時候會大大降低服務器性能;
-
對移動端的支持性不友好;
-
一般中大型的網站都適用(除了 App 移動端);
-
由于一般的 Session 需集中存儲在內存服務器上(如 Redis),這樣就會增加服務器的預算,所以預算不夠請謹慎選擇;
-
使用 express: express-session [1]
-
使用 koa: koa-session [2]
現在我們已經得知,Session-Cookie
的一些缺點,以及 Session 的維護給服務端造成很大困擾,我們必須找地方存放它,又要考慮分布式的問題,甚至要單獨為了它啟用一套 Redis 集群。那有沒有更好的辦法?
那Token
就應運而生了
3.1 什么是 Token(令牌)
Token
是一個令牌,客戶端訪問服務器時,驗證通過后服務端會為其簽發一張令牌,之后,客戶端就可以攜帶令牌訪問服務器,服務端只需要驗證令牌的有效性即可。
一句話概括;訪問資源接口(API)時所需要的資源憑證
一般 Token 的組成:
uid(用戶唯一的身份標識) +time(當前時間的時間戳) +sign(簽名,Token 的前幾位以哈希算法壓縮成的一定長度的十六進制字符串)
Token 的認證流程圖:
Token 認證步驟解析:
-
客戶端:輸入用戶名和密碼請求登錄校驗;
-
服務器:收到請求,去驗證用戶名與密碼;驗證成功后,服務端會簽發一個 Token 并把這個 Token 發送給客戶端;
-
客戶端:收到 Token 以后需要把它存儲起來,web 端一般會放在 localStorage 或 Cookie 中,移動端原生 APP 一般存儲在本地緩存中;
-
客戶端發送請求:向服務端請求 API 資源的時候,將 Token 通過 HTTP 請求頭 Authorization 字段或者其它方式發送給服務端;
-
服務器:收到請求,然后去驗證客戶端請求里面帶著的 Token ,如果驗證成功,就向客戶端返回請求的數據,否則拒絕返還(401);
Token 的優點:
-
服務端無狀態化、可擴展性好:Token 機制在服務端不需要存儲會話(Session)信息,因為 Token 自身包含了其所標識用戶的相關信息,這有利于在多個服務間共享用戶狀態
-
支持 APP 移動端設備;
-
安全性好:有效避免 CSRF 攻擊(因為不需要 Cookie)
-
支持跨程序調用:因為 Cookie 是不允許跨域訪問的,而 Token 則不存在這個問題
Token 的缺點:
-
配合:需要前后端配合處理;
-
占帶寬:正常情況下比
sid
更大,消耗更多流量,擠占更多寬帶 -
性能問題:雖說驗證 Token 時不用再去訪問數據庫或遠程服務進行權限校驗,但是需要對 Token 加解密等操作,所以會更耗性能;
-
有效期短:為了避免 Token 被盜用,一般 Token 的有效期會設置的較短,所以就有了
Refresh Token
;
業務接口用來鑒權的 Token,我們稱之為Access Token
。
為了安全,我們的Access Token
有效期一般設置較短,以避免被盜用。但過短的有效期會造成Access Token
經常過期,過期后怎么辦呢?
一種辦法是:刷新 Access Token
,讓用戶重新登錄獲取新 Token,會很麻煩;
另外一種辦法是:再來一個 Token,一個專門生成 Access Token 的 Token,我們稱為Refresh Token
;
-
Access Token:用來訪問業務接口,由于有效期足夠短,盜用風險小,也可以使請求方式更寬松靈活;
-
Refresh Token:用來獲取 Access Token,有效期可以長一些,通過獨立服務和嚴格的請求方式增加安全性;由于不常驗證,也可以如前面的 Session 一樣處理;
Refresh Token 的認證流程圖:
Refresh Token 認證步驟解析:
-
客戶端:輸入用戶名和密碼請求登錄校驗;
-
服務端:收到請求,驗證用戶名與密碼;驗證成功后,服務端會簽發一個
Access Token
和Refresh Token
并返回給客戶端; -
客戶端:把
Access Token
和Refresh Token
存儲在本地; -
客戶端發送請求:請求數據時,攜帶
Access Token
傳輸給服務端; -
服務端:
-
驗證 Access Token 有效:正常返回數據
-
驗證 Access Token 過期:拒絕請求
客戶端( Access Token 已過期):則重新傳輸 Refresh Token 給服務端;
服務端( Access Token 已過期):驗證 Refresh Token ,驗證成功后返回新的 Access Token 給客戶端;
客戶端:重新攜帶新的 Access Token 請求接口;
3.3 Token 和 Session-Cookie 的區別
Session-Cookie
和Token
有很多類似的地方,但是Token
更像是Session-Cookie
的升級改良版。
-
存儲地不同:Session 一般是存儲在服務端;Token 是無狀態的,一般由前端存儲;
-
安全性不同:Session 和 Token 并不矛盾,作為身份認證 Token 安全性比 Session 好,因為每一個請求都有簽名還能防止監聽以及重放攻擊;
-
支持性不同:Session-Cookie 認證需要靠瀏覽器的 Cookie 機制實現,如果遇到原生 NativeAPP 時這種機制就不起作用了,或是瀏覽器的 Cookie 存儲功能被禁用,也是無法使用該認證機制實現鑒權的;而 Token 驗證機制豐富了客戶端類型。
如果你的用戶數據可能需要和第三方共享,或者允許第三方調用 API 接口,用 Token 。如果永遠只是自己的網站,自己的 App,用什么就無所謂了。4. JWT(JSON Web Token)鑒權
通過第三節,我們知道了Token
的使用方式以及組成,我們不難發現,服務端驗證客戶端發送過來的 Token 時,還需要查詢數據庫獲取用戶基本信息,然后驗證 Token 是否有效;
這樣每次請求驗證都要查詢數據庫,增加了查庫帶來的延遲等性能消耗;
那么這時候業界常用的JWT
就應運而生了!!!
4.1 什么是 JWT
JWT
是Auth0
提出的通過對 JSON 進行加密簽名
來實現授權驗證的方案;
就是登錄成功后將相關用戶信息組成 JSON 對象,然后對這個對象進行某種方式的加密
,返回給客戶端;客戶端在下次請求時帶上這個 Token;服務端再收到請求時校驗 token 合法性
,其實也就是在校驗請求的合法性。
4.2 JWT 的組成
JWT 由三部分組成:Header 頭部
、Payload 負載
和Signature 簽名
它是一個很長的字符串,中間用點(.
)分隔成三個部分。列如 :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MdiyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header 頭部:
在 Header 中通常包含了兩部分:
-
typ:代表 Token 的類型,這里使用的是 JWT 類型;
-
alg:使用的 Hash 算法,例如 Hmac SHA256 或 RSA.
{
"alg": "HS256",
"typ": "JWT"
}
Payload 負載:
它包含一些聲明 Claim (實體的描述,通常是一個 User 信息,還包括一些其他的元數據) ,用來存放實際需要傳遞的數據,JWT 規定了7個官方字段:
-
iss (issuer):簽發人
-
exp (expiration time):過期時間
-
sub (subject):主題
-
aud (audience):受眾
-
nbf (Not Before):生效時間
-
iat (Issued At):簽發時間
-
jti (JWT ID):編號
除了官方字段,你還可以在這個部分定義私有字段,下面就是一個例子。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature 簽名
Signature 部分是對前兩部分的簽名,防止數據篡改。
首先,需要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
4.3JWT 的使用方式
客戶端收到服務器返回的 JWT,可以儲存在 Cookie 里面,也可以儲存在 localStorage。
此后,客戶端每次與服務器通信,都要帶上這個 JWT。你可以把它放在 Cookie 里面自動發送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請求的頭信息Authorization
字段里面。
Authorization: Bearer
4.4 JWT 的認證流程圖
其實 JWT 的認證流程與 Token 的認證流程差不多,只是不需要再單獨去查詢數據庫查找用戶用戶;簡要概括如下:
4.5 JWT 的優點
-
不需要在服務端保存會話信息(RESTful API 的原則之一就是無狀態),所以易于應用的擴展,即信息不保存在服務端,不會存在 Session 擴展不方便的情況;
-
JWT 中的 Payload 負載可以存儲常用信息,用于信息交換,有效地使用 JWT,可以降低服務端查詢數據庫的次數
-
加密問題:JWT 默認是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次。
-
到期問題:由于服務器不保存 Session 狀態,因此無法在使用過程中廢止某個 Token,或者更改 Token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。
-
使用 express:express-jwt[3]
-
使用 koa:koa-jwt[4]
前面我們已經知道了,在同域下的客戶端/服務端認證系統中,通過客戶端攜帶憑證,可以維持一段時間內的登錄狀態。
但隨著企業的發展,一個大型系統里可能包含 n 多子系統,用戶在操作不同的系統時,需要多次登錄,很麻煩,那么單點登錄(SSO)
就可以很好的解決這個問題的,在多個應用系統中,只需要登錄一次,就可以訪問其他相互信任的應用系統。
-
例如登錄天貓,淘寶也會自動登錄;
-
登錄百度貼吧,百度網盤也會自動登錄;
當百度網站存在兩個相同主域名下的貼吧子系統tieba.baidu.com
和網盤子系統pan.baidu.com
時,以下為他們實現 SSO 的步驟:
-
客戶端:用戶訪問某個子系統時(例如
tieba.baidu.com
),如果沒有登錄,則跳轉至 SSO 認證中心提供的登錄頁面進行登錄; -
服務端:登錄認證后,服務端把登錄用戶的信息存儲于 Session 中,并且附加在響應頭的
Set-Cookie
字段中,設置 Cookie 的 Domain 為.baidu.com
; -
客戶端:再次發送請求時,攜帶主域名 Domain 下的 Cookie 給服務器,此時服務端就可以通過該 Cookie 來驗證登錄狀態了;
其實我們不難發現,這就是我們上面講的Session-Cookie 認證登錄方式;但如果是不同域呢?畢竟不同域之間 Cookie 是不共享的,那怎么辦?5.2 跨域下的 SSO(主域名不同)
在我們常見的購物網站天貓 (tmall.com) 和 淘寶 (taobao.com) 中,我們只需要登錄其中某一個系統,另外一個系統打開后就會默認登錄,那么這是怎么做的呢?
那么就有了CAS(Central Authentication Service)中央授權服務
,那么我們先主要說下CAS
的流程;
單點登錄下的 CAS 認證流程圖:
單點登錄下的 CAS 認證步驟詳解:
-
客戶端:開始訪問系統 A;
-
系統 A:發現用戶未登錄,重定向至 CAS 認證服務(sso.com),同時 URL 地址參數攜帶登錄成功后回跳到系統 A 的頁面鏈接(https://sso.com/login?redirectURL=https%3A%2F%2Fwww.taobao.com%EF%BC%89%EF%BC%9B)
-
CAS 認證服務:發現請求 Cookie 中沒有攜帶登錄的票據憑證(TGC),所以 CAS 認證服務判定用戶處于
未登錄
狀態,重定向用戶頁面至 CAS 的登錄界面,用戶在 CAS 的登錄頁面上進行登錄操作。 -
客戶端:輸入用戶名密碼進行 CAS 系統認證;
-
CAS 認證服務:校驗用戶信息,并且
生成 TGC
放入自己的 Session 中,同時以 Set-Cookie 形式寫入 Domain 為sso.com
的域下 ;同時生成一個授權令牌 ST (Service Ticket)
,然后重定向至系統 A 的地址,重定向的地址中包含生成的 ST(重定向地址:https://www.taobao.com?token=ST-345678%EF%BC%89) -
系統 A:拿著 ST 向 CAS 認證服務發送請求,CAS 認證服務驗證票據 (ST) 的有效性。驗證成功后,系統 A 知道用戶已經在 CAS 登錄了(其中的 ST 可以保存到 Cookie 或者本地中),系統 A 服務器使用該票據 (ST) 創建與用戶的會話,稱為局部會話,返回受保護資源;
到這里客戶端就可以跟系統 A 愉快的交往啦 ~
7.客戶端:開始訪問系統 B;
8.系統 B:發現用戶未登錄,重定向至 SSO 認證服務,并將自己的地址作為參數傳遞,并附上在 sso.com 域下的 cookie 值是第五步生成的 TGC;
9. CAS 認證服務:CAS 認證服務中心發現用戶已登錄,跳轉回系統 B 的地址,并附上票據 (ST) ;
10.系統 B:拿到票據 (ST),去 CAS 認證服務驗證票據 (ST) 的有效性。驗證成功后,客戶端也可以跟系統 B 交往了 ~
(PS:腳踏兩只船,感覺有點渣呀 ~)
單點登錄下需要注意的點:
-
如圖中流程所示,我們發現
CAS 認證服務
在簽發的授權令牌 ST
后,直接重定向,這樣其實是比較容易容易被竊取,那么我們需要在系統 A 或者系統 B 在向 CAS 驗證成功 (如圖中的第 14 步和第 11 步) 后,再生成另一個新的驗證 Token 返回給客戶端保存; -
CAS 一般提供四個接口:
-
-
/login
:登錄接口,用于登錄到中央授權服務 -
/logout
:登出接口,用于從中央授權服務中登出 -
/validate
:用于驗證用戶是否登錄中央授權服務 -
/serviceValidate
:用于讓各個 Service 驗證用戶是否登錄中央授權服務
-
-
CAS 生成的票據:
-
-
TGT(Ticket Grangting Ticket):TGT 是 CAS 為用戶簽發的
登錄票據
,擁有了 TGT,用戶就可以證明自己在 CAS 成功登錄過。 -
TGC:Ticket Granting Cookie:CAS Server 生成TGT放入自己的 Session 中,而 TGC 就是這個 Session 的唯一標識(SessionId),以 Cookie 形式放到瀏覽器端,是 CAS Server 用來明確用戶身份的憑證。
-
ST(Service Ticket):ST 是 CAS 為用戶簽發的訪問某個 Service 的票據。
-
在我們實際瀏覽網站的時候,當我們登錄的時候除了輸入當前網站的賬號密碼外,我們還發現可以通過第三方的 QQ 或者 微信登錄,那么這又是如何做到了呢,這就要談到OAuth了。
OAuth 協議又有 1.0 和 2.0 兩個版本,2.0 版整個授權驗證流程更簡單更安全,也是目前最主要的用戶身份驗證和授權方式。6.1 什么是 OAuth 2.0?
OAuth是一個開放標準,允許用戶授權第三方網站 (CSDN、思否等) 訪問他們存儲在另外的服務提供者上的信息,而不需要將用戶名和密碼提供給第三方網站;
常見的提供 OAuth 認證服務的廠商:支付寶、QQ、微信、微博
簡單說,OAuth 就是一種授權機制。數據的所有者告訴系統,同意授權第三方應用進入系統,獲取這些數據。系統從而產生一個短期的進入令牌(Token),用來代替密碼,供第三方應用使用。
令牌與密碼的差異:
令牌(Token)
與密碼(Password)
的作用是一樣的,都可以進入系統,但是有三點差異。
-
令牌是短期的,到期會自動失效:用戶自己無法修改。密碼一般長期有效,用戶不修改,就不會發生變化。
-
令牌可以被數據所有者撤銷,會立即失效。
-
令牌有權限范圍(scope):對于網絡服務來說,只讀令牌就比讀寫令牌更安全。密碼一般是完整權限。
OAuth 2.0 對于如何頒發令牌的細節,規定得非常詳細。具體來說,一共分成四種授權模式(Authorization Grant),適用于不同的互聯網場景。
無論哪個模式都擁有三個必要角色:客戶端
、授權服務器
、資源服務器
,有的還有用戶(資源擁有者)
,下面簡單介紹下四種授權模式。
6.2 授權碼模式
授權碼(Authorization Code Grant)
方式,指的是第三方應用先申請一個授權碼,然后再用該碼獲取令牌。
這種方式是最常用的流程,安全性也最高,它適用于那些有后端服務的 Web 應用。授權碼通過前端傳送,令牌則是儲存在后端,而且所有與資源服務器的通信都在后端完成。這樣的前后端分離,可以避免令牌泄漏。
一句話概括:客戶端換取授權碼,客戶端使用授權碼換token,客戶端使用token訪問資源
授權碼模式的步驟詳解
-
客戶端:
打開網站 A,點擊登錄按鈕,請求 A 服務,A 服務重定向 (重定向地址如下) 至授權服務器 (如QQ、微信授權服務)。
https://qq.com/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read上面 URL 中,
response_type
參數表示要求返回授權碼(code
),client_id
參數讓 B 知道是誰在請求,redirect_uri
參數是 B 接受或拒絕請求后的跳轉網址,scope
參數表示要求的授權范圍(這里是只讀) -
授權服務器:
授權服務網站
會要求用戶登錄,然后詢問是否同意給予 A 網站授權。用戶表示同意,這時授權服務網站
就會跳回redirect_uri
參數指定的網址。跳轉時,會傳回一個授權碼,就像下面這樣。https://a.com/callback?code=AUTHORIZATION_CODE
上面 URL 中,
code
參數就是授權碼。 -
網站 A 服務器:
拿到授權碼以后,就可以向
授權服務器 (qq.com)
請求令牌,請求地址如下:https://qq.com/oauth/token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL上面 URL 中,
client_id
參數和client_secret
參數用來讓授權服務器 確認 A 的身份(client_secret
參數是保密的,因此只能在后端發請求),grant_type
參數的值是AUTHORIZATION_CODE
,表示采用的授權方式是授權碼,code
參數是上一步拿到的授權碼,redirect_uri
參數是令牌頒發后的回調網址。 -
授權服務器:
收到請求以后,驗證通過,就會頒發令牌。具體做法是向
redirect_uri
指定的網址,發送一段 JSON 數據。{
"access_token":"ACCESS_TOKEN",
"token_type":"bearer",
"expires_in":2592000,
"refresh_token":"REFRESH_TOKEN",
"scope":"read",
"uid":100101,
"info":{...}
}上面 JSON 數據中,
access_token
字段就是令牌,A 網站在后端拿到了,然后返回給客戶端即可。
有些 Web 應用是純前端應用,沒有后端。這時就不能用上面的方式了,必須將令牌儲存在前端。OAuth2.0 就規定了第二種方式,允許直接向前端頒發令牌。這種方式沒有授權碼這個中間步驟,所以稱為(授權碼)"隱藏式"(implicit)。
一句話概括:客戶端讓用戶登錄授權服務器換token,客戶端使用token訪問資源
。
隱藏式模式的步驟詳解
-
客戶端:
打開網站 A,A 網站提供一個鏈接,要求用戶跳轉到
授權服務器
,授權用戶數據給 A 網站使用。如下鏈接:https://qq.com/oauth/authorize?
response_type=token&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read上面 URL 中,
response_type
參數為token
,表示要求直接返回令牌。 -
授權服務器:
用戶跳轉到授權服務器,登錄后同意給予 A 網站授權。這時,授權服務器就會跳回
redirect_uri
參數指定的跳轉網址,并且把令牌作為 URL 參數,傳給 A 網站。https://a.com/callback#token=ACCESS_TOKEN
上面 URL 中,
token
參數就是令牌,A 網站因此直接在前端拿到令牌。
注意: 令牌的位置是 URL 錨點(fragment),而不是查詢字符串(querystring),這是因為 OAuth 2.0 允許跳轉網址是 HTTP 協議,因此存在"中間人攻擊"的風險,而瀏覽器跳轉時,錨點不會發到服務器,就減少了泄漏令牌的風險。 這種方式把令牌直接傳給前端,是很不安全的。因此,只能用于一些安全要求不高的場景,并且令牌的有效期必須非常短,通常就是會話期間(session)有效,瀏覽器關掉,令牌就失效了。6.4 用戶名密碼式模式(Password Credentials Grant)
如果你高度信任某個應用,OAuth 2.0 也允許用戶把用戶名和密碼,直接告訴該應用。該應用就使用你的密碼,申請令牌,這種方式稱為"密碼式"(password)。
一句話概括:用戶在客戶端提交賬號密碼換token,客戶端使用token訪問資源。
密碼式模式的步驟詳解
-
客戶端:
A 網站要求用戶提供
授權服務器(qq.com)
的用戶名和密碼。拿到以后,A 就直接向授權服務器
請求令牌。https://oauth.b.com/token?
grant_type=password&
username=USERNAME&
password=PASSWORD&
client_id=CLIENT_ID上面 URL 中,
grant_type
參數是授權方式,這里的password
表示"密碼式",username
和password
是授權服務器
的用戶名和密碼。 -
授權服務器:
授權服務器
驗證身份通過后,直接給出令牌。注意,這時不需要跳轉,而是把令牌放在 JSON 數據里面,作為 HTTP 回應,A 網站因此拿到令牌。
這種方式需要用戶給出自己的用戶名/密碼,顯然風險很大,因此只適用于其他授權方式都無法采用的情況,而且必須是用戶高度信任的應用。6.5 客戶端模式(Client Credentials Grant)
客戶端模式指客戶端以自己的名義,而不是以用戶的名義,向授權服務器
進行認證。
主要適用于沒有前端的命令行應用。
一句話概括:客戶端使用自己的標識換token,客戶端使用token訪問資源
。
客戶端模式的步驟詳解
-
客戶端:
客戶端向
授權服務器
進行身份認證,并要求一個訪問令牌。請求鏈接地址:https://oauth.b.com/token?
grant_type=client_credentials&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET上面 URL 中,
grant_type
參數等于client_credentials
表示采用憑證式,client_id
和client_secret
用來讓授權服務器
確認 A 的身份。 -
授權服務器:
授權服務器
驗證通過以后,直接返回令牌。
注意:這種方式給出的令牌,是針對第三方應用的,而不是針對用戶的,即有可能多個用戶共享同一個令牌。6.6 授權模式選型 按授權需要的多端情況:
模式 需要前端 需要后端 需要用戶響應 需要客戶端密鑰 授權碼模式 Authorization Code ? ? ? ? 隱式授權模式 Implicit Grant ? ? ? ? 密碼授權模式 Password Grant ? ? ? ? 客戶端授權模式 Client Credentials ? ? ? ?
按照客戶端類型與訪問令牌所有者分類:
上述主要比較淺顯的講解了 OAuth2.0 的基本邏輯,如若想詳細深入的了解,可查看官方文檔OAuth[5]或RFC 6749[6];亦可查看OAuth 2.0 概念及授權流程梳理[7]做對比7. 聯合登錄和信任登錄 7.1 什么是聯合登陸
聯合登錄
指同時包含多種憑證校驗的登錄服務,同時,也可以理解為使用第三方憑證進行校驗的登錄服務。
通俗點講:對于兩個網站 A 和 B,在登錄 A 網站的時候用 B 網站的帳號密碼,就是聯合登錄,或者登錄 B 網站的時候使用 A 網站的帳號密碼,也是聯合登錄。
這樣的概念其實與上面所講的 OAuth2.0 的用戶名密碼式模式
認證方式類似。
最經典的莫過于 APP 內嵌 H5 的使用場景,當用戶從 APP 進入內嵌的 H5 時,我們希望 APP 內已登錄的用戶能夠訪問到 H5 內受限的資源,而未登錄的用戶則需要登錄后訪問。
這里思路主要有兩種,一種是原生跳轉內嵌 H5 頁面時,將登錄態 Token 附加在 URL 參數上,另一種則是內嵌 H5 主動通過與原生客戶端制定的協議獲取應用內的登錄狀態。
7.2 什么是信任登錄
信任登錄
是指所有不需要用戶主動參與的登錄,例如建立在私有設備與用戶之間的綁定關系,憑證就是私有設備的信息,此時不需要用戶再提供額外的憑證。信任登錄又指用第三方比較成熟的用戶庫來校驗憑證,并登錄當前訪問的網站。
通俗點講:在 A 網站有登錄狀態的時候,可以直接跳轉到 B 網站而不用登錄,就是信任登錄
。
目前比較常見的第三方信任登錄帳號如:QQ 號淘寶帳號、支付寶帳號、微博帳號等。
我們不難發現 OAtuth 2.0 其實就是信任登錄的縮影,因為正是有了 OAuth,我們的信任登錄才得以實現。
8. 唯一登錄
— 假設現在產品經理提一個需求:我想要實現用戶只能在一個設備上登錄,禁止用戶重復登錄;
— 身為優秀的程序員的我們當然是滿足他啦 !!
8.1 什么是唯一登錄
唯一登錄,指的是禁止多人同時登錄同一賬號,后者的登錄行為,會導致前者掉線。
通俗點講就是:A 賬號在 A 電腦上登錄后,A 賬號此時又用 B 電腦再次登錄,則 A 電腦請求頁面時,提示“重新登錄”的信息,并跳轉到登錄頁面
8.2 唯一登錄流程圖
8.3 唯一登錄步驟詳解
用戶在客戶端 A 操作:
-
輸入賬號請求登錄接口;
-
后端生成對應 Token 并且返回給客戶端 A,并且在服務端保存一個登錄狀態;
-
客戶端A 保存 Token,并且每次請求都在 header 頭中攜帶對應的 Token;
用戶在客戶端 B 操作:
突然用戶在客戶端 B 上開始登錄操作,我們會發現,步驟和在客戶端A上面的操作幾乎是一致的;
只是后端在生成新的 Token 時,要先驗證登錄狀態,然后再生成對應新的 Token;
9. 掃碼登錄 9.1 什么是掃碼登錄
掃碼登錄通常見于移動端 APP 中,很多 PC 端的網站都提供了掃碼登錄的功能,無需在網頁上輸入任何賬號和密碼,只需要讓移動端 APP (如微信、淘寶、QQ等等) 中已登錄用戶主動掃描二維碼
,再確認登錄,以使 PC 端的同款應用得以快速登錄的方式就是掃碼登錄
。
9.2 什么是二維碼
二維碼
又稱二維條碼,常見的二維碼為 QR Code,QR 全稱 Quick Response,是一個近幾年來移動設備上超流行的一種編碼方式,它比傳統的Bar Code條形碼能存更多的信息,也能表示更多的數據類型。
通過上面所述,我們不難發現,掃碼登錄需要三端 (PC端
、手機端
、服務端
) 來進行配合才能達到登錄成功的效果;
9.3 掃碼登錄的認證流程圖
9.4 掃碼登錄的步驟詳解 (待掃碼階段、待確認階段、已確認階段)
待掃碼階段:
-
PC端:
打開某個網站 (如taobao.com) 或者某個 APP (如微信) 的掃碼登錄入口;就會攜帶 PC 端的設備信息向服務端發送一個獲取二維碼的請求;
-
服務端:
服務器收到請求后,隨機生成一個 UUID 作為二維碼 ID,并將 UUID 與
PC 端的設備信息
關聯起來存儲在 Redis 服務器中,然后返回給 PC 端;同時設置一個過期時間,在過期后,用戶登錄二維碼需要進行刷新重新獲取。 -
PC 端:
收到二維碼 ID 之后,將二維碼 ID 以
二維碼的形式
展示,等待移動端掃碼。并且此時的 PC 端開始輪詢查詢二維碼狀態,直到登錄成功。如果移動端未掃描,那么一段時間后二維碼會自動失效。
已掃碼待確認階段:
-
手機端:
打開手機端對應已登錄的 APP (微信或淘寶等),開始掃描識別 PC 端展示的二維碼;
移動端掃描二維碼后,會自動獲取到二維碼 ID,并將移動端登錄的信息憑證(Token)和二維碼 ID 作為參數發送給服務端,此時手機必須是已登錄(使用掃描登錄的前提是移動端的應用為已登錄狀態,這樣才可以共享登錄態)。
-
服務端:
收到手機端發來的請求后,會將
Token 與二維碼 ID
關聯,為什么需要關聯呢?因為,當我們在使用微信時,移動端退出時,PC 端也應該隨之退出登錄,這個關聯就起到這個作用。然后會生成一個臨時 Token,這個 Token 會返回給移動端,一次性 Token 用作確認時的憑證。
已確認階段:
-
手機端:
收到確認信息后,點擊確認按鈕,移動端攜帶上一步中獲取的
臨時 Token
發送給服務端校驗; -
服務端:
服務端校驗完成后,會更新二維碼狀態,并且給 PC 端生成一個
正式的 Token
,后續 PC 端就是持有這個 Token 訪問服務端。 -
PC端:
輪詢到二維碼狀態為已登錄狀態,并且會獲取到了生成的 Token,完成登錄,后續訪問都基于 Token 完成。
大家都知道,最傳統的登錄方式就是使用賬號加密碼登錄,簡單粗暴,一般也不會出現什么問題;
缺點:
-
但這種方式要求用戶要記住自己的賬號和密碼,也就是有一個記憶成本。用戶為了降低記憶成本,很可能會在不同平臺使用同一套賬號密碼。從安全角度考慮,一旦某個平臺的賬號密碼泄露了,會連累到該用戶使用的其他平臺。
-
另外,由于賬號和個人身份無關,意味著同一個用戶可以注冊多個不同的賬號,也就是可能會有惡意注冊的情況發生。
直到手機卡的強制實名制才得以解決!
10.2 手機號驗證碼登錄
隨著無線互聯的發展以及手機卡實名制的推廣,手機號儼然已成為特別的身份證明,與賬號密碼相比,手機號可以更好地驗證用戶的身份,防止惡意注冊。
但是手機號注冊還是需要一系列繁瑣的操作:輸入手機號、等待短信驗證碼、輸入驗證碼、點擊登錄。整個流程少說二十秒,而且如果收不到短信,也就登錄補了,這類問題有可能導致潛在的用戶流失。
從安全角度考慮,還存在驗證碼泄漏的風險。如果有人知道了你的手機號,并且竊取到了驗證碼,那他也能登錄你的賬號了。
所以就有了一鍵登錄操作!
10.3 什么是一鍵登錄
我們想一下,為什么我們需要驗證碼?驗證碼的作用就是確定這個手機號是你的,那除了使用短信,是否還有別的方式對手機號進行認證?
于是,就有了咱們的主角一鍵登錄。
短信驗證碼的作用就是證明當前操作頁面的用戶與輸入手機號的用戶為相同的人,那么實際上只要我們能夠獲取到當前手機使用的手機卡號,直接使用這個號碼進行登錄,不需要額外的操作,這就是一鍵登錄。
一鍵登錄能不能做,取決于運營商是否開放相關服務;隨著運營商開放了相關的服務,我們現在已經能夠接入運營商提供的 SDK 并付費使用相關的服務。
一鍵登錄流程圖:
一鍵登錄步驟詳解:
-
SDK 初始化:調用 SDK 方法,傳入平臺配置的 AppKey 和 AppSecret
-
喚起授權頁:調用 SDK 喚起授權接口,SDK 會先向運營商發起獲取手機號掩碼的請求,請求成功后跳到授權頁。授權頁會顯示手機號掩碼以及運營商協議給用戶確認。
-
同意授權并登錄:用戶同意相關協議,點擊授權頁面的登錄按鈕,SDK 會請求本次取號的 Token,請求成功后將 Token 返回給客戶端
-
取號:將獲取到的 Token 發送到自己的服務器,由服務端攜帶 Token 調用運營商一鍵登錄的接口,調用成功就返回手機號碼。服務端用手機號進行登錄或注冊操作,返回操作結果給客戶端,完成一鍵登錄。
三大運營商開放平臺:
-
移動 - 互聯網能力開放平臺[8]
-
電信 - 天翼賬號開放平臺[9]
-
聯通 - WO+ 開放平臺[10]
由于國內三大運營商各自有獨立的 SDK,所以會導致兼容方面的工作會特別繁瑣。如果要采用一鍵登錄的方案,不妨采用第三方提供了號碼認證服務,下列幾家供應商都擁有手機號碼認證能力:
-
-
阿里 - 號碼認證服務 [11]
-
創藍 - 閃驗 [12]
-
極光 - 極光認證 [13]
-
mob - 秒驗 [14]
-
注意:
在認證過程中,需要用戶打開蜂窩網絡,如果手機設備沒有插入 SIM 卡、或者關閉蜂窩網絡的情況下,是無法完成認證的。所以就算接入一鍵登錄,還是要兼容傳統的登錄方式,允許用戶在失敗的情況下,仍能正常完成登錄流程。
總結
在學習了解上面的 10 種鑒權方法后,我們簡單概括一下
-
HTTP 基本認證
適用于內部網絡,或者對安全要求不是很高的網絡; -
Session-Cookie
適用于一般中大型的網站(移動端 APP 除外); -
Token
和JWT
都適用于市面上大部分的企業型網站,JWT 效能會優于 Token; -
單點登錄
適用于子系統較多的大型企業網站; -
OAuth 2.0
適用于需要快速注冊用戶型的網站; -
掃碼登錄
適用于已完成部署了三端的企業; -
一鍵登錄
適用于原生 APP;
[1] express-session: https://github.com/expressjs/session
[2] koa-session: https://github.com/koajs/session
[3] express-jwt: https://github.com/auth0/express-jwt
[4] koa-jwt: https://github.com/koajs/jwt
[5] OAuth:https://en.wikipedia.org/wiki/OAuth
[6] RFC 6749:http://www.rfcreader.com/#rfc6749
[7] OAuth 2.0 概念及授權流程梳理:https://www.cnblogs.com/hellxz/p/oauth2_process.html
[8] 移動 - 互聯網能力開放平臺:http://dev.10086.cn/
[9] 電信 - 天翼賬號開放平臺: https://id.189.cn/
[10] 聯通 - WO+ 開放平臺:http://open.wo.com.cn/
[11] 阿里 - 號碼認證服務:https://help.aliyun.com/product/75010.html
[12] 創藍 - 閃驗:http://shanyan.253.com/
[13] 極光 - 極光認證:https://www.jiguang.cn/identify
[14] mob - 秒驗:https://www.mob.com/mobService/secverify