認證和受權,其實吧簡單來講就是:認證就是讓服務器知道你是誰,受權就是服務器讓你知道你什麼能幹,什麼不能幹,認證受權倆種方式:Session-Cookie與JWT,下面咱們就針對這兩種方案就行闡述。前端
當 client經過用戶名密碼請求server並經過身份認證後,server就會生成身份認證相關的 session 數據,而且保存在內存或者內存數據庫。並將對應的 sesssion_id返回給client,client會把保存session_id(能夠加密簽名下防止篡改)在cookie。此後client的全部請求都會附帶該session_id(畢竟默認會把cookie傳給server),以肯定server是否存在對應的session數據以及檢驗登陸狀態以及擁有什麼權限,若是經過校驗就該幹嗎幹嗎,不然從新登陸。node
前端退出的話就清cookie。後端強制前端從新認證的話就清或者修改session。python
相比JWT,最大的優點就在於能夠主動清除session了web
session保存在服務器端,相對較爲安全redis
結合cookie使用,較爲靈活,兼容性較好算法
cookie + session在跨域場景表現並很差數據庫
若是是分佈式部署,須要作多機共享session機制,實現方法可將session存儲到數據庫中或者redis中npm
基於 cookie 的機制很容易被 CSRFjson
查詢session信息可能會有數據庫查詢操做後端
session: 主要存放在服務器端,相對安全
cookie: 可設置有效時間,默認是關閉瀏覽器後失效,主要存放在客戶端,而且不是很安全,可存儲大小約爲4kb
sessionStorage: 僅在當前會話下有效,關閉頁面或瀏覽器後被清除
localstorage: 除非被清除,不然永久保存
JSON Web Token(JWT)是一種開放標準(RFC 7519),它定義了一種緊湊且獨立的方式,能夠將各方之間的信息做爲JSON對象進行安全傳輸。該信息能夠驗證和信任,由於是通過數字簽名的。
JWT基本上由.分隔的三部分組成,分別是頭部,有效載荷和簽名。 一個簡單的JWT的例子,以下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ.ec7IVPU-ePtbdkb85IRnK4t4nUVvF2bBf8fGhJmEwSs
複製代碼
若是你細緻得去看的話會發現其實這是一個分爲 3 段的字符串,段與段之間用 點號 隔開,在 JWT 的概念中,每一段的名稱分別爲:
Header.Payload.Signature
複製代碼
在字符串中每一段都是被 base64url 編碼後的 JSON,其中 Payload 段可能被加密。
JWT 的 Header 一般包含兩個字段,分別是:typ(type) 和 alg(algorithm)。
typ:token的類型,這裏固定爲 JWT
alg:使用的 hash 算法,例如:HMAC SHA256 或者 RSA
一個簡單的例子:
{
"alg": "HS256",
"typ": "JWT"
}
複製代碼
咱們對他進行編碼後是:
>>> base64.b64encode(json.dumps({"alg":"HS256","typ":"JWT"}))
'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
複製代碼
JWT 中的 Payload 其實就是真實存儲咱們須要傳遞的信息的部分,例如正常咱們會存儲些用戶 ID、用戶名之類的。此外,還包含一些例如發佈人、過時日期等的元數據。
可是,這部分和 Header 部分不同的地方在於這個地方能夠加密,而不是簡單得直接進行 BASE64 編碼。可是這裏我爲了解釋方便就直接使用 BASE64 編碼,須要注意的是,這裏的 BASE64 編碼稍微有點不同,切確得說應該是 Base64UrlEncoder,和 Base64 編碼的區別在於會忽略最後的 padding(=號),而後 '-' 會被替換成'_'。
舉個例子,例如咱們的 Payload 是:
{"user_id":"zhangsan"}
複製代碼
那麼直接 Base64 的話應該是:
>>> base64.urlsafe_b64encode('{"user_id":"zhangsan"}')
'eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ=='
複製代碼
而後去掉 = 號,最後應該是:
'eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ'
複製代碼
Signature 部分其實就是對咱們前面的 Header 和 Payload 部分進行簽名,保證 Token 在傳輸的過程當中沒有被篡改或者損壞,簽名的算法也很簡單,可是,爲了加密,因此除了 Header 和 Payload 以外,還多了一個密鑰字段,完整算法爲:
Signature = HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
複製代碼
仍是之前面的例子爲例,
base64UrlEncode(header) =》 eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9
base64UrlEncode(payload) =》 eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ
複製代碼
secret 就設爲:"secret", 那最後出來的簽名應該是:
>>> import hmac
>>> import hashlib
>>> import base64
>>> dig = hmac.new('secret', >>> msg="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ",
digestmod=
>>> base64.b64encode(dig.digest())
'ec7IVPU-ePtbdkb85IRnK4t4nUVvF2bBf8fGhJmEwSs='
複製代碼
將上面三個部分組裝起來就組成了咱們的 JWT token了,因此咱們的
{'user_id': 'zhangsan'}
複製代碼
的 token 就是:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ.ec7IVPU-ePtbdkb85IRnK4t4nUVvF2bBf8fGhJmEwSs
複製代碼
1.首先,前端經過Web表單將本身的用戶名和密碼發送到後端的接口。這一過程通常是一個HTTP POST請求。建議的方式是經過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。
2.後端覈對用戶名和密碼成功後,將用戶的id等其餘信息做爲JWT Payload(負載),將其與頭部分別進行Base64編碼拼接後簽名,造成一個JWT。造成的JWT就是一個形同lll.zzz.xxx的字符串。
3.後端將JWT字符串做爲登陸成功的返回結果返回給前端。前端能夠將返回的結果保存在localStorage或sessionStorage上,退出登陸時前端刪除保存的JWT便可。
4.前端在每次請求時將JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問題)
5.後端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正確;檢查Token是否過時;檢查Token的接收方是不是本身(可選)。
6.驗證經過後後端使用JWT中包含的用戶信息進行其餘邏輯操做,返回相應結果。
隨着應用程序的擴大和用戶數量的增長,你必將開始水平或垂直擴展。session數據經過文件或數據庫存儲在服務器的內存中。在水平擴展方案中,你必須開始複製服務器數據,你必須建立一個獨立的中央session存儲系統,以便全部應用程序服務器均可以訪問。不然,因爲session存儲的缺陷,你將沒法擴展應用程序。解決這個挑戰的另外一種方法是使用 sticky session。你還能夠將session存儲在磁盤上,使你的應用程序在雲環境中輕鬆擴展。這類解決方法在現代大型應用中並無真正發揮做用。創建和維護這種分佈式系統涉及到深層次的技術知識,並隨之產生更高的財務成本。在這種狀況下,使用JWT是無縫的;因爲基於token的身份驗證是無狀態的,因此不須要在session中存儲用戶信息。咱們的應用程序能夠輕鬆擴展,由於咱們可使用token從不一樣的服務器訪問資源,而不用擔憂用戶是否真的登陸到某臺服務器上。你也能夠節省成本,由於你不須要專門的服務器來存儲session。爲何?由於沒有session!
注意:若是你正在構建一個小型應用程序,這個程序徹底不須要在多臺服務器上擴展,而且不須要RESTful API的,那麼session機制是很棒的。 若是你使用專用服務器運行像Redis那樣的工具來存儲session,那麼session也可能會爲你完美地運做!
JWT簽名旨在防止在客戶端被篡改,但也能夠對其進行加密,以確保token攜帶的claim 很是安全。JWT主要是直接存儲在web存儲(本地/session存儲)或cookies中。 JavaScript能夠訪問同一個域上的Web存儲。這意味着你的JWT可能容易受到XSS(跨站腳本)攻擊。惡意JavaScript嵌入在頁面上,以讀取和破壞Web存儲的內容。事實上,不少人主張,因爲XSS攻擊,一些很是敏感的數據不該該存放在Web存儲中。一個很是典型的例子是確保你的JWT不將過於敏感/可信的數據進行編碼,例如用戶的社會安全號碼。
最初,我提到JWT能夠存儲在cookie中。事實上,JWT在許多狀況下被存儲爲cookie,而且cookies很容易受到CSRF(跨站請求僞造)攻擊。預防CSRF攻擊的許多方法之一是確保你的cookie只能由你的域訪問。做爲開發人員,無論是否使用JWT,確保必要的CSRF保護措施到位以免這些攻擊。
如今,JWT和session ID也會暴露於未經防範的重放攻擊。創建適合系統的重放防範技術,徹底取決於開發者。解決這個問題的一個方法是確保JWT具備短時間過時時間。雖然這種技術並不能徹底解決問題。然而,解決這個挑戰的其餘替代方案是將JWT發佈到特定的IP地址並使用瀏覽器指紋。
注意:使用HTTPS / SSL確保你的Cookie和JWT在客戶端和服務器傳輸期間默認加密。這有助於避免中間人攻擊!
現代應用程序的常見模式是從RESTful API查詢使用JSON數據。目前大多數應用程序都有RESTful API供其餘開發人員或應用程序使用。由API提供的數據具備幾個明顯的優勢,其中之一就是這些數據能夠被多個應用程序使用。在這種狀況下,傳統的使用session和Cookie的方法在用戶認證方面效果不佳,由於它們將狀態引入到應用程序中。
RESTful API的原則之一是它應該是無狀態的,這意味着當發出請求時,總會返回帶有參數的響應,不會產生附加影響。用戶的認證狀態引入這種附加影響,這破壞了這一原則。保持API無狀態,不產生附加影響,意味着維護和調試變得更加容易。
另外一個挑戰是,由一個服務器提供API,而實際應用程序從另外一個服務器調用它的模式是很常見的。爲了實現這一點,咱們須要啓用跨域資源共享(CORS)。Cookie只能用於其發起的域,相對於應用程序,對不一樣域的API來講,幫助不大。在這種狀況下使用JWT進行身份驗證能夠確保RESTful API是無狀態的,你也不用擔憂API或應用程序由誰提供服務。
對此的批判性分析是很是必要的。當從客戶端向服務器發出請求時,若是大量數據在JWT內進行編碼,則每一個HTTP請求都會產生大量的開銷。然而,在會話中,只有少許的開銷,由於SESSION ID實際上很是小。看下面這個例子:
JWT有5個claim:
{
"sub": "1234567890",
"name": "Prosper Otemuyiwa",
"admin": true,
"role": "manager",
"company": "Auth0"
}
複製代碼
編碼時,JWT的大小將是SESSION ID(標識符)的幾倍,從而在每一個HTTP請求中,JWT比SESSION ID增長更多的開銷。而對於session,每一個請求在服務器上須要查找和反序列化session。
JWT經過將數據保留在客戶端的方式以空間換時間。你應用程序的數據模型是一個重要的影響因素,由於經過防止對服務器數據庫不間斷的調用和查詢來減小延遲。須要注意的是不要在JWT中存儲太多的claim,以免發生巨大的,過分膨脹的請求。
值得一提的是,token可能須要訪問後端的數據庫。特別是刷新token的狀況。他們可能須要訪問受權服務器上的數據庫以進行黑名單處理。獲取有關刷新token和什麼時候使用它們的更多信息。另外,請查看本文,瞭解有關黑名單的更多信息(auth0.com/blog/blackl…)。
現代web應用程序的另外一種常見模式是,它們一般依賴於下游服務。例如,在原始請求被解析以前,對主應用服務器的調用可能會向下遊服務器發出請求。這裏的問題是,cookie不能很方便地流到下游服務器,也不能告訴這些服務器關於用戶的身份驗證狀態。因爲每一個服務器都有本身的cookie方案,因此阻力很大,而且鏈接它們也是困難的。JSON Web Token再次垂手可得地作到了!
此外,無狀態JWT的實效性相比session太差,只有等到過時纔可銷燬,而session則可手動銷燬。
例若有個這種場景,若是JWT中存儲有權限相關信息,好比當前角色爲 admin,可是因爲JWT全部者濫用自身權利,高級管理員將權利濫用者的角色降爲 user。可是因爲 JWT 沒法實時刷新,必須要等到 JWT 過時,強制從新登陸時,高級管理員的設置才能生效。
或者是用戶發現帳號被異地登陸,而後修改密碼,此時token還未過時,異地的帳號同樣能夠進行操做包括修改密碼。
但這種場景也不是沒有辦法解決,解決辦法就是將JWT生成的token存入到redis或者數據庫中,當用戶登出或做出其餘想要讓token失效的舉動,可經過刪除token在數據庫或者redis裏面的對應關係來解決這個問題。
我這個項目中使用的是JWT,使用方法以下:
首先安裝JWT庫:
npm install jsonwebtoken
複製代碼
而後建立簽名數據,生成token:
let jwt = require('jsonwebtoken');
var token = jwt.sign({ name: '張三' }, 'shhhhh');
console.log(token);
複製代碼
運行程序能夠看到打印出來的內容相似這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi5byg5LiJIiwiaWF0IjoxNDYyODgxNDM3fQ.uVWC2h0_r1F4FZ3qDLkGN5KoFYbyZrFpRJMONZrJJog
複製代碼
以後,對token字符串,能夠這樣解碼:
let decoded=jwt.decode(token);
console.log(decoded);
複製代碼
將打印出:
{ name: '張三', iat: 1462881437 }
複製代碼
其中iat是時間戳,即簽名時的時間(注意:單位是秒)。
不過,通常咱們不會使用decode方法,由於它只是簡單的對claims部分的作base64解碼。
咱們須要的是驗證claims的內容是否被篡改。
此時咱們須要使用verify方法:
let decoded = jwt.verify(token, 'shhhhh');
console.log(decoded);
複製代碼
雖然打印出的內容和decode方法是同樣的。可是是通過校驗的。
咱們能夠改變校驗用的密鑰,好比改成shzzzz,使之和加密時的密鑰不一致。那麼解碼就會出現報錯:
JsonWebTokenError: invalid signature
複製代碼
咱們也能夠偷偷修改token的claims或者header部分,會獲得這樣的報錯:
JsonWebTokenError: invalid token
複製代碼
最後,根據本身的需求,決定是否須要將生成的token存入數據庫或者redis,但建議不要存儲用戶密碼等敏感信息。