1. 授權認證
目前主流使用的授權認證方案是使用者-角色-許可權的模式
如下圖所示:
對於一個使用者的賬號,其可以滿足多種角色,對於每一種角色其可以有多種許可權,對於這種多對多對多的關係,我們可以建立相應的資料庫進行維護
如何儲存使用者的資訊:
Cookie-Session是一種儲存使用者資訊的方法,可以用來進行身份的認證。
對於Cookie來說,其存在於客戶端,用於存放使用者登入等資訊,對於敏感欄位需要進行加密,前後端編碼都可以設定Cookie。Session存在於伺服器,所以它更加安全。利用兩者可以實現對當前登入使用者的身份認證。
使用者使用客戶端登陸後,服務端建立一個session,並把session-id返回給客戶端,我們把這個id存放在cookie中。當使用者以後傳送請求時帶上這個id,就可以驗證使用者的身份。但是這個方法存在一定的安全性問題,當第三方獲取到使用者的cookie後可以偽造身份向服務端傳送請求,造成使用者的不安全狀態。
這個問題的解決方案是使用token進行身份驗證。在我們登入成功獲得 Token 之後,一般會選擇存放在 localStorage (瀏覽器本地儲存)中。然後我們在前端透過某些方式會給每個發到後端的請求加上這個 token,這樣就不會出現上述的問題。
2. 許可權系統設計
使用者的許可權完全由他所擁有的角色來控制,但是這樣會有一個缺點,就是給使用者加許可權必須新增一個角色,導致實際操作起來效率比較低。所以我們在 RBAC模型 的基礎上,新增了給使用者直接增加許可權的能力,也就是說既可以給使用者新增角色,也可以給使用者直接新增許可權。終端使用者的許可權是由擁有的角色和許可權點組合而成。新許可權系統的許可權模型:使用者最終許可權 = 使用者擁有的角色帶來的許可權 + 使用者獨立配置的許可權,兩者取並集。
3.OAuth2.0
OAuth 是一個行業的標準授權協議,主要用來授權第三方應用獲取有限的許可權。OAuth 2.0 比較常用的場景就是第三方登入,當你的網站接入了第三方登入的時候一般就是使用的 OAuth 2.0 協議。
相關連結:
https://tools.ietf.org/html/rfc6749
OAuth 就是一種授權機制。資料的所有者告訴系統,同意授權第三方應用進入系統,獲取這些資料。系統從而產生一個短期的進入令牌(token),用來代替密碼,供第三方應用使用。
第三方系統進入系統直接分享密碼是一種不妥的行為,第三方系統會長期保持密碼而造成不安全的問題,所以採用令牌的方式代替分享密碼。對於令牌token來說,他是短期有效的,並且它的有效性在系統管理員的控制之下。並且我們能對令牌做一些許可權設定,比如令牌可以設定成只讀令牌。
3.1四種獲得令牌的流程
3.1.1授權碼
授權碼(authorization code)方式,指的是第三方應用先申請一個授權碼,然後再用該碼獲取令牌。
這種方式是最常用的流程,安全性也最高,它適用於那些有後端的 Web 應用。授權碼透過前端傳送,令牌則是儲存在後端,而且所有與資源伺服器的通訊都在後端完成。這樣的前後端分離,可以避免令牌洩漏。
第一步,A 網站提供一個連結,使用者點選後就會跳轉到 B 網站,授權使用者資料給 A 網站使用。下面就是 A 網站跳轉 B 網站的一個示意連結。
https://b.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參數列示要求的授權範圍(這裡是只讀)。
第二步,使用者跳轉後,B 網站會要求使用者登入,然後詢問是否同意給予 A 網站授權。使用者表示同意,這時 B 網站就會跳回redirect_uri引數指定的網址。跳轉時,會傳回一個授權碼,就像下面這樣。
https://a.com/callback?code=AUTHORIZATION_CODE
上面 URL 中,code引數就是授權碼。
第三步,A 網站拿到授權碼以後,就可以在後端,向 B 網站請求令牌。
https://b.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引數用來讓 B 確認 A 的身份(client_secret引數是保密的,因此只能在後端發請求),grant_type引數的值是AUTHORIZATION_CODE,表示採用的授權方式是授權碼,code引數是上一步拿到的授權碼,redirect_uri引數是令牌頒發後的回撥網址
第四步,B 網站收到請求以後,就會頒發令牌。具體做法是向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 網站在後端拿到了。
3.1.2隱藏式
適用於 純前端web應用,這裡不做介紹
3.1.3密碼式
透過賬號密碼而不是授權碼獲得token,流程和方法一基本一致
3.1.4憑證式
不適用
3.2使用和更新令牌
A 網站拿到令牌以後,就可以向 B 網站的 API 請求資料了。
此時,每個發到 API 的請求,都必須帶有令牌。具體做法是在請求的頭資訊,加上一個Authorization欄位,令牌就放在這個欄位裡面。
curl -H "Authorization: Bearer ACCESS_TOKEN" \
"https://api.b.com"
上面命令中,ACCESS_TOKEN就是上一步拿到的令牌。
令牌的有效期到了,如果讓使用者重新走一遍上面的流程,再申請一個新的令牌,很可能體驗不好,而且也沒有必要。OAuth 2.0 允許使用者自動更新令牌。
具體方法是,B 網站頒發令牌的時候,一次性頒發兩個令牌,一個用於獲取資料,另一個用於獲取新的令牌(refresh token 欄位)。令牌到期前,使用者使用 refresh token 發一個請求,去更新令牌。
3.3實際操作
GitHub OAuth 第三方登入示例教程 - 阮一峰的網路日誌 (ruanyifeng.com)
4.JWT
4.1Jwt的概念
JWT 本質上就是一組字串,透過(.)切分成三個為 Base64 編碼的部分:Header : 描述 JWT 的後設資料,定義了生成簽名的演算法以及 Token 的型別。Payload : 用來存放實際需要傳遞的資料Signature(簽名):伺服器透過 Payload、Header 和一個金鑰(Secret)使用 Header 裡面指定的簽名演算法(預設是 HMAC SHA256)生成。
如:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
線上解析網站:https://jwt.io/
Header 通常由兩部分組成:typ(Type):令牌型別,也就是 JWT。alg(Algorithm):簽名演算法,比如 HS256。
示例:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
iss(issuer):JWT 簽發方。
iat(issued at time):JWT 簽發時間。
sub(subject):JWT 主題。
aud(audience):JWT 接收方。
exp(expiration time):JWT 的過期時間。
nbf(not before time):JWT 生效時間,早於該定義的時間的 JWT 不能被接受處理。
jti(JWT ID):JWT 唯一標識。
示例:{
"uid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
"sub": "1234567890",
"name": "John Doe",
"exp": 15323232,
"iat": 1516239022,
"scope": ["admin", "user"]
}
Payload 部分預設是不加密的,一定不要將隱私資訊存放在 Payload 當中!!!JSON 形式的 Payload 被轉換成 Base64 編碼,成為 JWT 的第二部分。
Signature 部分是對前兩部分的簽名,作用是防止 JWT(主要是 payload) 被篡改。這個簽名的生成需要用到:Header + Payload。存放在服務端的金鑰(一定不要洩露出去)。簽名演算法。簽名的計算公式如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出簽名以後,把 Header、Payload、Signature 三個部分拼成一個字串,每個部分之間用"點"(.)分隔,這個字串就是 JWT 。
4.2如何利用JWT進行身份驗證
使用者向伺服器傳送使用者名稱、密碼以及驗證碼用於登陸系統。
如果使用者使用者名稱、密碼以及驗證碼校驗正確的話,服務端會返回已經簽名的 Token,也就是 JWT。
使用者以後每次向後端發請求都在 Header 中帶上這個 JWT 。服務端檢查 JWT 並從中獲取使用者相關資訊。
兩點建議:
建議將 JWT 存放在 localStorage 中,放在 Cookie 中會有 CSRF 風險。
請求服務端並攜帶 JWT 的常見做法是將其放在 HTTP Header 的 Authorization 欄位中(Authorization: Bearer Token)。
4.3安全性
有了簽名之後,即使 JWT 被洩露或者截獲,駭客也沒辦法同時篡改 Signature、Header、Payload。
這是為什麼呢?因為服務端拿到 JWT 之後,會解析出其中包含的 Header、Payload 以及 Signature 。服務端會根據 Header、Payload、金鑰再次生成一個 Signature。拿新生成的 Signature 和 JWT 中的 Signature 作對比,如果一樣就說明 Header 和 Payload 沒有被修改。
不過,如果服務端的秘鑰也被洩露的話,駭客就可以同時篡改 Signature、Header、Payload 了。駭客直接修改了 Header 和 Payload 之後,再重新生成一個 Signature 就可以了。
金鑰一定保管好,一定不要洩露出去。JWT 安全的核心在於簽名,簽名安全的核心在金鑰。