淺談一下前後端鑑權方式 ^.^
雖然本人現在從事前端開發,但是之前一直是 PHP 全棧,所以對前後端鑑權機制也有一定的瞭解,就找些資料簡單記錄一下吧。(瞎掰扯~)
常見鑒權機制
HTTP 是無狀態的協議(對於事務處理沒有記憶能力,每次客户端和服務端會話完成時,服務端不會保存任何會話信息。):每個請求都是完全獨立的,服務端無法確認當前訪問者的身份信息,無法分辨上一次的請求發送者和這一次的發送者是不是同一個人。所以服務器與瀏覽器為了進行會話跟蹤(知道是誰在訪問我),就必須主動的去維護一個狀態,這個狀態用於告知服務端前後兩個請求是否來自同一瀏覽器,由此產生了很多種鑑權方式。
- HTTP Basic Authentication
- Session-Cookie
- Token
- OAuth
另外我們要注意區分Authentication與Authrization,一個是認證一個是授權。Authentication是為了驗證你是不是本人,而Authrization是為了驗證你有沒有做某件事情的權限。我們分別舉三個例子來説明三種情況讓大家對認證和授權的關係有更好的理解。
-
只認證不授權
- 只是登錄應用,並不進行其他操作,這時候不需要授權只進行認證。
-
既認證又授權
- 我們使用第三方應用登錄的時候,既輸入了第三方應用的賬號密碼來認證,又授權了本應用讀取第三方登錄應用已經註冊了的個人信息數據等。
-
不認證只授權
- 我們點開小程序時,需要獲取個人信息,這種時候相當於只授權數據給小程序,並未進行認證,畢竟在應用內部使用小程序,很少有需要再登錄認證這種操作。
各鑑權機制流程與原理
一旦涉及認證授權,必須要考慮的一個問題就是狀態管理。所謂的狀態管理就是説我們在進行登錄之後的一段時間裏,不希望每次訪問它都需要重新登錄。所以開發者必須要考慮怎麼樣保持用户的登錄狀態以及設置失效時間。而這個過程需要前後端通力合作來完成。
- 下面就來簡單談一下幾種常見的認證和授權方式的流程與原理,本人瞎掰扯,歡迎大佬指點。
HTTP Basic Authentication
這種授權方式是瀏覽器遵守 HTTP 協議實現的基本授權方式,HTTP 協議進行通信的過程中定義了基本認證允許 HTTP 服務器對客户端進行用户身份證的方法。
基本流程
- 發送請求:客户端向服務器請求數據,請求的內容可能是一個網頁或者是一個 ajax 異步請求,此時假設客户端尚未被驗證(服務器驗證並判斷是否返回 401),則客户端提供如下請求至服務器。
Get /index.html HTTP/1.0
Host: www.google.com
- 服務器返回 401:服務器向客户端發送驗證請求代碼 401,
WWW-Authenticate: Basic realm="google.com"這句話是關鍵,如果沒有客户端不會彈出用户名和密碼輸入界面,服務器返回的數據大抵如下。
HTTP/1.0 401 Unauthorised
Server: SokEvo/1.0
WWW-Authenticate: Basic realm="google.com"
Content-Type: text/html
Content-Length: xxx
-
客户端彈出窗口:當符合 http1.0 或 1.1 規範的客户端收到 401 返回值時,將自動彈出一個登錄窗口,要求用户輸入用户名和密碼。
- 這個時候請求時屬於
pending狀態,當用户輸入用户名密碼的時候客户端會再次發送請求頭帶Authorization的請求。
- 這個時候請求時屬於
- 用户輸入用户名和密碼:輸入密碼後,點擊提交會將用户名及密碼以
Base64加密方式加密,並將密文放入前一條請求信息中,則客户端發送的第一條請求信息則變成如下內容。
Get /index.html HTTP/1.0
Host: www.google.com
Authorization: Basic xxxxxxx(base64 密文)
// 加密過程是瀏覽器默認的行為,不需要我們人為加密,我們只需要輸入用户名密碼即可。
- 服務端解密:服務器收到上述請求信息後,將
Authorization字段後的用户信息取出並解密,將解密後的用户名及密碼與用户數據庫進行比較驗證,如用户名及密碼正確,服務器則根據請求,將所請求資源發送給客户端。
優點:簡單便捷,兼容性好。
缺點:未使用 TLS/SSL 的情況下信息容易泄露,不安全;無法註銷,只能關閉瀏覽器或標籤頁。
Session-Cookie
這種授權方式是利用服務端的 Session 和瀏覽器(客户端)的 Cookie 來實現的前後端通信認證模式。HTTP 協議是一個無狀態的協議,服務器不會知道到底是哪一台瀏覽器訪問了它,因此需要一個標識用來讓服務器區分不同的瀏覽器。cookie 就是這個管理服務器與客户端之間狀態的標識。
cookie 的原理是,瀏覽器第一次向服務器發送請求時,服務器在 response 頭部設置 Set-Cookie 字段,瀏覽器收到響應就會設置 cookie 並存儲,在下一次該瀏覽器向服務器發送請求時,就會在 request 頭部自動帶上 Cookie 字段,服務器端收到該 cookie 用以區分不同的瀏覽器。
當然,這個 cookie 與某個用户的對應關係應該在第一次訪問時就存在服務器端,這時就需要 session 了。另外 cookie 記得設置過期時間,如果不設置過期時間關閉瀏覽器就會消失,設置過期時間的話會保存在本地磁盤上。服務端也記得配置 seesion,尤其是分佈式服務器在鑑權機制上需要考慮 cookie 共享與 seesion 共享等問題。
session 是會話的意思,瀏覽器第一次訪問服務端,服務端就會創建一次會話,在會話中保存標識該瀏覽器的信息。它與 cookie 的區別就是 session 是緩存在服務端的,cookie 則是緩存在客户端,他們都由服務端生成,是為了彌補 HTTP 協議無狀態的缺陷。每當請求到達服務端時會先校驗請求中的用户標識是否存在於 session 中,如果有則表示已經認證成功,否則表示認證失敗。
基本流程
- 服務器在接受客户端首次訪問時在服務器端創建 seesion,然後保存 seesion(我們可以將 seesion 保存在內存中,也可以保存在 redis 中,推薦使用後者。),然後給這個 session 生成一個唯一的標識字符串
sessionId(sid)。 - 通過秘鑰(自定義)對 sid 進行簽名處理,避免客户端修改 sid。(非必需步驟)生成 sid 後把 sid 和用户信息映射起來保存在服務器,最後在響應頭中種下 (set-cookie) 這個唯一標識字符串。
- 瀏覽器中收到請求響應的時候會解析響應頭,然後將 sid 保存在本地 cookie 中,瀏覽器在下次 http 請求的請求頭中會帶上該域名下的 cookie 信息。
- 服務器在之後接受客户端請求時會去解析請求頭 cookie 中的 sid,然後根據這個 sid 去找服務器端保存的該客户端的 session 判斷該請求是否合法。
- 在後續請求中,服務器會一直根據 sid 認證,如果驗證通過,則繼續處理。一旦用户登出,服務端和客户端同時銷燬該會話。
優點:簡單便捷,瀏覽器會自動帶上;不需要每次都從數據庫取數據比對(如果 sid 不存服務器的話);可以方便管理用户註銷與登錄(刪除/添加 session)。
缺點:脱離瀏覽器沒法用,比如移動端、PC端等;session 存儲在服務端,增大了服務器的開銷;由於 sid 存在服務端,若被人取到 sid 容易受到跨站請求偽造(CSRF)的攻擊,我們可以設置 HttpOnly(腳本無法讀取保存在本地的 sid,可以防止 XSS 注入後獲取 cookie 中的 sid,從而偽造攻擊。)、Secure 設置為 true(使用 HTTPS)來提高安全性;在分佈式服務器上,需要共享 session 等配置,會限制負載均衡和集羣水平拓展的能力。
-
session 也依賴於 cookie 機制,除了比cookie安全點外,cookie 認證的其他缺點 session 幾乎也都有。但也有一點區別,且 session cookie 的鑑權方式比單獨使用 cookie 鑑權更多一點。
- session 比 cookie 安全,session 是存儲在服務器端的,cookie 是存儲在客户端的。
- cookie 只支持存字符串數據,想要設置其他類型的數據,需要將其轉換成字符串,session 可以存任意數據類型。
- cookie 可設置為長時間保持,比如我們經常使用的默認登錄功能,session 一般失效時間較短,客户端關閉(默認情況下)或者 session 超時都會失效。
- 單個 cookie 保存的數據不能超過 4K,session 可存儲數據遠高於 cookie,但是當訪問量過多,會佔用過多的服務器資源。
- cookie 認證的基本流程
Token
Token 授權
token 又叫令牌,本質上就是一串無意義的字符串,一般放在請求頭裏,請求頭 key 一般是 Authorization,當然也可以和服務端約定好自定義成其他的,只要服務端能夠從請求頭中拿到 token 就好了。
token 認證的出現最大的特點就是讓登錄認證不再依賴於 cookie 機制了,將 token 放在了請求頭裏,那些由於 cookie 機制導致的弊端自然就沒有了。
- 客户端使用用户名跟密碼請求登錄
- 服務端收到請求,去驗證用户名與密碼。
- 驗證成功後,服務端會根據自定義規則簽發一個 Token,再把這個 Token 發送給客户端。
- 客户端收到 Token 以後可以把它存儲起來,比如放在 Cookie 裏或者 LocalStorage 裏。
- 客户端每次向服務端請求資源的時候需要帶着服務端簽發的 Token,放在請求頭
Authorization中。 - 服務端收到請求,然後去驗證客户端請求裏面帶着的 Token,如果驗證成功,就向客户端返回請求的數據(從數據庫查詢簽發的 Token,並查詢用户數據。) ,如果不成功返回 401 錯誤碼,鑑權失敗。
優點:token 認證不侷限於 cookie 且不受同源策略的影響,可以指定放在請求頭某個字段中,可以給應用程序使用;不使用 cookie,攻擊者無法猜到使用的 token 在哪,而且用户的 token 存在本地,只有在提交請求時才會放在請求頭某個字段中供服務器讀取(類似於獲取 Referer 這種,腳本無法讀取。),這些就可以一定程度上規避 CSRF 攻擊。
缺點:加密解密消耗使得 token 認證比 Session-Cookie 更消耗性能;token 比 sessionId 大,更佔帶寬;token 需要去數據庫中查詢用户信息,增大數據庫壓力。
JWT (JSON Web Token)
由於每次請求都要用 token 去數據庫中查詢用户信息,數據庫的壓力太大了。如果 token 攜帶了用户信息,不就不需要每次請求都訪問數據庫查了嘛,可以直接從 token 中直接解析出用户信息以及用户登錄狀態進行校驗,這就是 JWT。給瀏覽器返回的 token 是一串帶着用户信息的加密字符串。
JWT 全稱是 Json Web Token。其實就是特殊的 token,理解起來就是攜帶着用户信息的 token。所以 JWT 認證和 token 認證本質上是一樣的。只不過 token 認證的用户信息是從數據庫裏查的。而 JWT 認證的用户信息是直接從 token 解析出來的。
JWT 組成
Header
-
Header 部分是一個 JSON 對象,描述 JWT 的元數據如圖所示。
- 上面代碼中,alg 屬性表示簽名的算法(algorithm),默認是 HMAC SHA256(寫成 HS256)。typ 屬性表示這個令牌(token)的類型(type),JWT 令牌統一寫為 JWT。
- 最後,將上面的 JSON 對象使用 Base64URL 算法(詳見後文)轉成字符串。
Payload
- Payload 部分也是一個 JSON 對象,用來存放實際需要傳遞的數據。JWT 規定了 7 個官方字段,供選用。
iss (issuer):簽發人
exp (expiration time):過期時間
sub (subject):主題
aud (audience):受眾
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號
除了官方字段,你還可以在這個部分定義私有字段,比如用户名、用户暱稱、權限、部門等等。
注意,JWT 默認是不加密的,任何人都可以讀到,所以不要把秘密信息放在這個部分。
這個 JSON 對象也要使用 Base64URL 算法轉成字符串。
Signature
-
Signature 部分是對前兩部分的簽名,防止數據篡改。
- 首先,需要指定一個密鑰(secret)。這個密鑰(自定義)只有服務器才知道,不能泄露給用户。然後,使用 Header 裏面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)- 算出簽名以後,把
Header、Payload、Signature三個部分拼成一個字符串,每個部分之間用.分隔,就可以返回給用户。
Base64URL 算法
前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。
JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 http://api.example.com/?token=xxx)。Base64 有三個字符 +、/、=,在 URL 裏面有特殊含義,所以要被替換掉:= 被省略、+ 替換成 -,/ 替換成 _。這就是 Base64URL 算法。
基本流程
瞭解以上內容後我們簡單説一下流程
- 基本流程與 token 一致,只是簽發的 token 內容不同。
-
前面我們提到了 JWT 是保存了用户信息的,所以我們需要做的事情主要是以下幾點。
- 客户端使用用户名跟密碼請求登錄
- 服務端收到請求,去驗證用户名與密碼。
- 驗證成功後,服務端會根據自定義密鑰與用户信息簽發一個 Token,再把這個 Token 發送給客户端。
- 客户端收到 Token 以後可以把它存儲起來,比如放在 Cookie 裏或者 LocalStorage 裏。
-
客户端每次向服務端請求資源的時候需要帶着服務端簽發的 Token,放在請求頭
Authorization中,當然你也可以放到 cookie 中,但是這樣不能跨域。Authorization: Bearer <token>
- 服務器收到後根據 Header 中的加密算法與自定義的密鑰,對 Payload 內容進行加密,然後生成結果與 Signature 一致的話,則認證通過,否則表示認證失敗。
補充
- JWT 默認是不加密,但也是可以加密的。生成原始 Token 以後,可以用密鑰再加密一次(前端若需要使用 token 中的相關信息,請告訴其編碼規則。)。
- JWT 不加密的情況下,不能將秘密數據寫入 JWT。
- JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。
- JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是説,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯,但是也有好處,可以方便分佈式服務器管理,因為密鑰和解析加密邏輯都在代碼裏面。
- JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。為了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用户進行認證。
- 為了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。
- 由於 JWT 的有效期應該設置得比較短,所以就產生了登錄狀態信息續簽問題。比如設置 token 的有效期為一個小時,那麼一個小時後,如果用户仍然在這個應用上,這個時候當然不能指望用户再登錄一次。目前可用的解決辦法是在每次用户發出請求都返回一個新的 token,前端再用這個新的 token 來替代舊的,這樣每一次請求都會刷新 token 的有效期。但是這樣,需要頻繁的生成 token。另外一種方案是用户每次請求時判斷還有多久這個 token 會過期,在 token 快要過期時,返回一個新的 token。如果前端不想每次自己操作請求頭,則可以放到 cookie 中,只需 set-cookie 並設置 http-only 與 HTTPS 即可。
- 用户主動註銷時 JWT 並不支持用户主動退出登錄,客户端在別處使用 token 仍然可以正常訪問。為了支持註銷,可以在註銷時將該 token 加入到服務器的 redis 黑名單中或者設置數據庫存儲也可。
OAuth
OAuth 協議為用户資源的授權提供了一個安全的、開放而又簡易的標準。與以往的授權方式不同之處是 OAuth 的授權不會使第三方觸及到用户的帳號信息(如用户名與密碼),即第三方無需使用用户的用户名與密碼就可以申請獲得該用户資源的授權,因此 OAuth 是安全的。
同時,任何第三方都可以使用 OAuth 認證服務,任何服務提供商都可以實現自身的 OAuth 認證服務,因而 OAuth 是開放的。我們常見的提供 OAuth 認證服務的廠商有支付寶、QQ、微信、微博、Github等。
OAuth 協議又有 1.0 和 2.0 兩個版本。相比較 1.0(存在嚴重安全漏洞已停用),2.0 版整個授權驗證流程更簡單更安全,也是目前最主要的用户身份驗證和授權方式。
-
與 JWT 區別
- OAuth2.0 是一種授權框架(鑑權的流程理念),用在使用第三方賬號登錄的情況,比如使用 QQ 登錄某個 app。
- JWT 是一種認證協議(鑑權的方法方式),用在前後端分離,需要簡單的對後台 API 進行保護時使用。
- 無論使用哪種方式切記用 HTTPS 來保證數據的安全性
基本流程
-
請求認證(認證第三方應用是否合法):客户端(第三方應用)向 OAuth 服務提供商請求未授權的 RequestToken。即向
RequestToken URL發起請求。- OAuth 服務提供商同意使用者的請求,並向其頒發未經用户授權的 oauth_token 與對應的 oauth_token_secret,並返回給使用者。
-
用户同意(確認用户是否同意):使用者向 OAuth 服務提供商請求用户授權的 RequestToken。即向
UserAuthorization URL發起請求並在請求中攜帶上一步服務提供商頒發的未授權的 oauth_token 與 oauth_token_secret。- OAuth 服務提供商通過網頁要求用户登錄並引導用户完成授權。
-
換取 AccessToken(提供 AccessToken&RefreshToken(可選) 給第三方應用):RequestToken 授權後,使用者將向
AccessToken URL發起請求,將上步授權的 RequestToken 換取成 AccessToken 與 RefreshToken。- OAuth 服務提供商同意使用者的請求,並向其頒發 AccessToken 與對應的密鑰,並返回給使用者。
-
使用 AccessToken 換取資源(第三方應用通過 AccessToken 獲取用户授權的相關資源):使用者以後就可以使用上步返回的 AccessToken 訪問用户授權的資源。
- 使用 AccessToken 換取資源失敗:使用 RefreshToken 換取新的 AccessToken 來重新請求資源
OAuth2.0 提供了四種授權模式,開發者可以根據自己的業務情況自由選擇。
- 授權碼授權模式(Authorization Code Grant)
- 隱式授權模式(簡化模式)(Implicit Grant)
- 密碼授權模式(Resource Owner Password Credentials Grant)
- 客户端憑證授權模式(Client Credentials Grant)
- 可參考阮一峯老師的介紹理解 OAuth2.0、四種模式