引語
最近正好在獨立開發一個后台管理系統,涉及到了基於Token的身份認證,自己邊學邊用邊做整理和總結,對基於JWT實現的Token的身份認證做一次相對比較全面的認識。
一、基於session的跨域身份驗證
Internet服務無法與用戶身份驗證分開。一般過程如下。
用戶向服務器發送用戶名和密碼。
驗證服務器后,相關數據(如用戶角色,登錄時間等)將保存在當前會話中。
服務器向用戶返回session_id,session信息都會寫入到用戶的Cookie。
用戶的每個后續請求都將通過在Cookie中取出session_id傳給服務器。
服務器收到session_id並對比之前保存的數據,確認用戶的身份。
996415-20180508203515598-1955105543.png
這種模式最大的問題是,沒有分布式架構,無法支持橫向擴展。如果使用一個服務器,該模式完全沒有問題。但是,如果它是服務器群集或面向服務的跨域體系結構的話,則需要一個統一的session數據庫庫來保存會話數據實現共享,這樣負載均衡下的每個服務器才可以正確的驗證用戶身份。
例如蟲蟲舉一個實際中常見的單點登陸的需求:站點A和站點B提供同一公司的相關服務。現在要求用戶只需要登錄其中一個網站,然后它就會自動登錄到另一個網站。怎么做?
1350514-20180504122814029-1201707523.png
一種解決方案是聽過持久化session數據,寫入數據庫或文件持久層等。收到請求后,驗證服務從持久層請求數據。該解決方案的優點在於架構清晰,而缺點是架構修改比較費勁,整個服務的驗證邏輯層都需要重寫,工作量相對較大。而且由於依賴於持久層的數據庫或者問題系統,會有單點風險,如果持久層失敗,整個認證體系都會掛掉。
1350514-20180504123036062-1920411426.png
總結基於服務器驗證方式暴露的一些明顯的問題:
Session:每次認證用戶發起請求時,服務器需要去創建一個記錄來存儲信息。當越來越多的用戶發請求時,內存的開銷也會不斷增加。
可擴展性:在服務端的內存中使用Seesion存儲登錄信息,伴隨而來的是可擴展性問題。
CORS(跨域資源共享):當我們需要讓數據跨多台移動設備上使用時,跨域資源的共享會是一個讓人頭疼的問題。在使用Ajax抓取另一個域的資源,就可以會出現禁止請求的情況。
CSRF(跨站請求偽造):用戶在訪問銀行網站時,他們很容易受到跨站請求偽造的攻擊,並且能夠被利用其訪問其他的網站。
在這些問題中,可擴展行是最突出的。因此我們有必要去尋求一種更有行之有效的方法。
基於Token認證的身份認證方案
那么有什么更好的方案嗎?當然有,那就是基於Token的身份認證方案。
那么基於Token的身份驗證可以解決哪些問題呢?
Token 完全由應用管理,所以它可以避開同源策略
Token 可以避免 CSRF 攻擊
Token 可以是無狀態的,可以在多個服務間共享
基於Token認證的原理
Token 是在服務端產生的,是無狀態的,我們不將用戶信息存在服務器或Session中。如果客戶端使用用戶名/密碼向服務端請求認證,服務端認證成功,那么在服務端會返回 Token 給客戶端。
客戶端可以在每次請求的時候帶上 Token 證明自己的合法地位。如果這個 Token 在服務端持久化(比如存入數據庫),那它就是一個永久的身份令牌。基於Token的身份驗證這種概念解決了在服務端存儲信息時的許多問題。NoSession意味着你的程序可以根據需要去增減機器,而不用去擔心用戶是否登錄。
996415-20180508211129069-742527294.png
基於Token的身份驗證的方案過程如下:
用戶通過用戶名和密碼發送請求。
服務端驗證。
服務端返回一個簽名的token 給客戶端。
客戶端儲存token,並且每次發送請求都會攜帶token。
服務端驗證token並返回數據。
Token的優勢
無狀態、可擴展
在客戶端存儲的Tokens是無狀態的,並且能夠被擴展。基於這種無狀態和不存儲Session信息,負載負載均衡器能夠將用戶信息從一個服務傳到其他服務器上。
安全性
請求中發送token而不再是發送cookie能夠防止CSRF(跨站請求偽造)。即使在客戶端使用cookie存儲token,cookie也僅僅是一個存儲機制而不是用於認證。不將信息存儲在Session中,讓我們少了對session操作。
Token是有時效的,一段時間之后用戶需要重新驗證。我們也不一定需要等到token自動失效,token有撤回的操作,通過token revocataion可以使一個特定的token或是一組有相同認證的token無效。
可擴展性
Tokens能夠創建與其它程序共享權限的程序。例如,能將一個隨便的社交帳號和自己的大號(Fackbook或是Twitter)聯系起來。當通過服務登錄Twitter(我們將這個過程Buffer)時,我們可以將這些Buffer附到Twitter的數據流上(we are allowing Buffer to post to our Twitter stream)。
使用tokens時,可以提供可選的權限給第三方應用程序。當用戶想讓另一個應用程序訪問它們的數據,我們可以通過建立自己的API,得出特殊權限的tokens。
多平台跨域
我們提前先來談論一下CORS(跨域資源共享),對應用程序和服務進行擴展的時候,需要介入各種各種的設備和應用程序。
Having our API just serve data, we can also make the design choice to serve assets from a CDN. This eliminates the issues that CORS brings up after we set a quick header configuration for our application.
只要用戶有一個通過了驗證的token,數據和資源就能夠在任何域上被請求到。
基於標准
創建token的時候,你可以設定一些選項。我們在后續的文章中會進行更加詳盡的描述,但是標准的用法會在JSON Web Tokens體現。
最近的程序和文檔是供給JSON Web Tokens的。它支持眾多的語言。這意味在未來的使用中你可以真正的轉換你的認證機制。
無狀態 Token
如果我們把所有狀態信息都附加在 Token 上,服務器就可以不保存。但是服務端仍然需要認證 Token 有效。不過只要服務端能確認是自己簽發的 Token,而且其信息未被改動過,那就可以認為 Token 有效——“簽名”可以作此保證。平時常說的簽名都存在一方簽發,另一方驗證的情況,所以要使用非對稱加密算法。但是在這里,簽發和驗證都是同一方,所以對稱加密算法就能達到要求,而對稱算法比非對稱算法要快得多(可達數十倍差距)。更進一步思考,對稱加密算法除了加密,還帶有還原加密內容的功能,而這一功能在對 Token 簽名時並無必要——既然不需要解密,摘要(散列)算法就會更快。可以指定密碼的散列算法,自然是 HMAC。
上面說了這么多,還需要自己去實現嗎?不用! JWT 已經定義了詳細的規范,而且有各種語言的若干實現。
在使用無狀態 Token 的時候,有兩點需要注意:
Refresh Token 有效時間較長,所以它應該在服務器端有狀態,以增強安全性,確保用戶注銷時可控
應該考慮使用二次認證來增強敏感操作的安全性
基於JWT實現的Token認證方案
JSON Web Token是什么?
JSON Web Token(JWT)是目前最流行的跨域身份驗證解決方案。
JSON Web Token(JWT)是一個開放標准(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間作為JSON對象安全地傳輸信息。由於此信息是經過數字簽名的,因此可以被驗證和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公用/專用密鑰對對JWT進行簽名。
JWT架構:
截屏2019-12-0615.41.15.png
什么時候應該使用 JSON Web Token?
身份驗證
這是使用JWT的最常見方案。一旦用戶登錄,每個后續請求將包括JWT,從而允許用戶訪問該令牌允許的路由,服務和資源。
單一登錄是當今廣泛使用JWT的一項功能,因為它的開銷很小並且可以在不同的域中輕松使用。
信息交換
JSON Web令牌是在各方之間安全地傳輸信息的一種好方法。因為可以對JWT進行簽名(例如,使用公鑰/私鑰對),所以您可以確定發件人是本人。
另外,由於簽名是使用標頭和有效負載計算的,因此您還可以驗證內容是否未被篡改。
JSON Web Token 結構
JSON Web令牌以緊湊的形式由三部分組成,這些部分由點 (. )分隔,分別是:
Header:標頭
Payload: 有效載荷
Signature: 簽名
因此,JWT通常如下所示
xxxxx.yyyyy.zzzzz
Header:標頭
讓我們分解不同的部分。
標頭通常由兩部分組成:令牌的類型(即JWT)和所使用的簽名算法,例如HMAC SHA256或RSA。
例如:
{
"alg": "HS256",
"typ": "JWT"
}
然后,此JSON被Base64Url編碼以形成JWT的第一部分。
Payload: 有效載荷
令牌的第二部分是包含聲明的有效負載。聲明是關於實體(通常是用戶)和其他數據的聲明。有三種類型的聲明:已注冊聲明、公共聲明和私有聲明。
已注冊的聲明:這些是一組預定義的聲明,它們不是強制的,而是推薦的,以提供一組有用的、可互操作的聲明。主要有:
iss:發行人
exp:到期時間
sub:主題
aud:用戶
nbf:在此之前不可用
Iat:發布時間
jti:JWT ID用於標識該JWT
請注意,聲明名稱僅是三個字符,因為JWT是緊湊的。
公共聲明:這些聲明可以由使用JWTs的用戶隨意定義。但是,為了避免沖突,應該在IANA JSON Web令牌注冊表中定義它們,或者將它們定義為包含防沖突命名空間的URI。
私有聲明:這些是為在同意使用它們的各方之間共享信息而創建的自定義索賠,既不是注冊索賠,也不是公開索賠。
有效負載示例可以是:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
對有效負載進行Base64Url編碼,以形成JSON Web令牌的第二部分。
請注意,對於已簽名的令牌,此信息盡管可以防止篡改,但任何人都可以讀取。除非將其加密,否則請勿將機密信息放入JWT的有效負載或報頭元素中。
簽名
要創建簽名部分,您必須獲取編碼的頭、編碼的負載、密鑰、頭中指定的算法,並對其進行簽名。
例如,如果要使用HMAC SHA256算法,則將通過以下方式創建簽名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
簽名用於驗證消息在整個過程中沒有更改,並且對於使用私鑰進行簽名的令牌,它還可以驗證JWT的發送者是它所說的真實身份。
整合在一起
輸出是三個由點分隔的Base64 URL字符串,這些點可以在HTML和HTTP環境中輕松傳遞,同時與基於XML的標准(如SAML)相比更加緊湊。
下面顯示了一個JWT,它對前一個報頭和有效負載進行了編碼,並用一個秘密進行了簽名。
encoded-jwt3.png
可以從此圖中看出JWT生成的令牌的格式與其對應餓原文之間的關聯。這里也順帶推薦一下jwt官網的JWT debuger工具。
legacy-app-auth-5.png
JSON Web Token工作原理
在身份驗證中,當用戶使用其憑據成功登錄時,將返回一個JSON Web Token。由於Token是憑據,必須非常小心地防止安全問題。一般來說,您不應該將令牌保留的時間超過所需的時間。
由於缺乏安全性,也不應將敏感會話數據存儲在瀏覽器存儲中。
當用戶想要訪問受保護的路由或資源時,用戶代理應該發送JWT,通常在授權頭中使用承載模式。標題的內容應如下所示:
Authorization: Bearer
在某些情況下,這可以是無狀態授權機制。服務器的受保護路由將檢查授權頭中是否存在有效的JWT,如果存在,則允許用戶訪問受保護的資源。如果JWT包含必要的數據,則可以減少查詢數據庫以執行某些操作的需要,盡管情況並非總是如此。
如果令牌在授權頭中發送,則跨源資源共享(CORS)不會成為問題,因為它不使用cookies。
下圖顯示了如何獲取JWT並將其用於訪問API或資源:
17.png
應用程序或客戶端向授權服務器請求授權。這是通過不同的授權流之一執行的。例如,典型的符合OpenID Connect的web應用程序將使用授權代碼流通過/oauth/authorize端點。
當授權被授予時,授權服務器將向應用程序返回一個訪問Token。
應用程序使用訪問令牌訪問受保護的資源(如API)。
請注意:使用簽名的Token,Token中包含的所有信息都將向用戶或其他方公開,即使他們無法更改它。這意味着您不應將機密信息放入Token中。
JWT的使用
生成令牌
jwt.sign(payload, secretOrPrivateKey, [options, callback])
// paylod: 有效載荷
// secretOrPrivateKey: 加密密鑰或私鑰
// option(可選): 生成令牌設置
// callback(可選): 回調函數
其中option可配置屬性,屬性均為可選:
algorithm: 算法(默認: HS256)
noTimestamp: 無時間戳
header: 頭部
keyid: 鍵值編號
mutatePayload: 是否對payload進行轉化,若為true則會用payload初始值生成令牌
以下6相即可在payload中配置也可在option中配置,注意只可在一處出現。
expiresIn: 令牌過期時間(可為數字(單位秒)或帶單位的字符串,例如 60, "2 days", "10h", "7d"等)
notBefore: 在此之前不可用(格式如上述expiresIn)
audience: 用戶
issuer: 發布者
jwtid: 令牌id
subject: 主題
生成令牌實例
// 異步回調方式
Let privateKey = 'Cloudy'
jwt.sign({ id: '1', exp:'7d' }, privateKey, { algorithm: 'RS256' }, function(err, token) {
console.log(token);
});
// 同步方式(推薦用promise對其進行進一步封裝)
let token = jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256', expiresIn: '1h' });
令牌驗證
jwt.verify(token, secretOrPublicKey, [options, callback])
// token: 令牌
// secretOrPublicKey: 密鑰或公鑰
// option: 生成令牌設置(可選)
// callback: 回調函數(可選)
其中option可配置屬性有:
algorithms: 算法
audience: 如果你想驗證用戶,為其提供一個字符串或正則表達式
complete: Boolean值,若為true則完整輸出令牌
issuer(可選): 如果你想驗證發布者,為其提供一個字符串或正則表達式
ignoreExpiration: Boolean值,若為true則不會驗證過期時間
subject: 如果你想驗證主題,為其提供一個字符串或正則表達式
clockTolerance: 時鍾容忍,在檢查nbf和exp聲明時,處理不同服務器之間的小時鍾差異所允許的秒數
maxAge: 允許令牌的最大允許年齡仍然有效。它以秒或描述時間跨度zeit/ms的字符串表示。Eg: 1000, "2 days", "10h", "7d".
clockTimestamp: 時間戳,應用作所有必要比較的當前時間(秒)。
nonce:如果要檢查nonce聲明,請在此處提供一個字符串值。
驗證令牌實例
// 同步驗證(對稱加密算法)
var decoded = jwt.verify(token, 'shhhhh');
console.log(decoded.foo) // bar
// 異步驗證用戶(使用不對稱加密算法)
var cert = fs.readFileSync('public.pem'); // 獲取公鑰
jwt.verify(token, cert, { audience: 'urn:foo', jwtid: 'jwtid', subject: 'subject' }, function(err, decoded) {
// if audience mismatch, err == invalid audience
});
簡單解碼(無驗證)
(同步)返回解碼的有效負載,而不驗證簽名是否有效。
jwt.decode(token [, options])
解碼options可配置屬性:
json 在負載上強制JSON.parse,即使頭不包含“typ”:“JWT”。
complete 返回一個帶有解碼有效負載和頭的對象。
支持的算法數組。目前支持以下算法。
alg Parameter Value Digital Signature or MAC Algorithm
HS256 HMAC using SHA-256 hash algorithm
HS384 HMAC using SHA-384 hash algorithm
HS512 HMAC using SHA-512 hash algorithm
RS256 RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
RS384 RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm
RS512 RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm
PS256 RSASSA-PSS using SHA-256 hash algorithm (only node ^6.12.0 OR >=8.0.0)
PS384 RSASSA-PSS using SHA-384 hash algorithm (only node ^6.12.0 OR >=8.0.0)
PS512 RSASSA-PSS using SHA-512 hash algorithm (only node ^6.12.0 OR >=8.0.0)
ES256 ECDSA using P-256 curve and SHA-256 hash algorithm
ES384 ECDSA using P-384 curve and SHA-384 hash algorithm
ES512 ECDSA using P-521 curve and SHA-512 hash algorithm
none No digital signature or MAC value included
JWT問題和趨勢
1、JWT默認不加密,但可以加密。生成原始令牌后,可以使用改令牌再次對其進行加密。
2、當JWT未加密方法是,一些私密數據無法通過JWT傳輸。
3、JWT不僅可用於認證,還可用於信息交換。善用JWT有助於減少服務器請求數據庫的次數。
4、JWT的最大缺點是服務器不保存會話狀態,所以在使用期間不可能取消令牌或更改令牌的權限。也就是說,一旦JWT簽發,在有效期內將會一直有效。
5、JWT本身包含認證信息,因此一旦信息泄露,任何人都可以獲得令牌的所有權限。為了減少盜用,JWT的有效期不宜設置太長。對於某些重要操作,用戶在使用時應該每次都進行進行身份驗證。
6、為了減少盜用和竊取,JWT不建議使用HTTP協議來傳輸代碼,而是使用加密的HTTPS協議進行傳輸。
結語
最近正好在獨立開發一個后台管理系統,涉及到了Token驗證,如果覺得文章對你有用,請你幫我點個贊吧,你的點贊和關注是我一直堅持分享的動力!
推薦閱讀:
【專題:JavaScript進階之路】
深入理解 ES6 Promise
JavaScript之函數柯理化
ES6 尾調用和尾遞歸
Git常用命令小結
淺談 MVC 和 MVVM 模型
參考:
node-jsonwebtoken
Introduction to JSON Web Tokens
Token 認證的來龍去脈
徹底理解cookie,session,token
前后端分離使用 Token 登錄解決方案
基於JWT的token身份認證方案