以JWT 代替傳統Token
JSON Web Token (JWT)是由Auth0所提構出的一個新Token想法,這並不是一套軟件、也不是一個技術,如果你在做網站時有用Token驗證使用者身份的習慣,那么這個方法你應該很快就能上手。我們先來講一講為什么JWT會比傳統Token要好。
在傳統網站中我們會以Session 來判定使用者是否有登入,由於Session 只會被服務端知道,所以我們就可以Session 中存放一些重要數據並且供之后驗證用。
為什么不用Session了?
隨着網絡的擴展,Session 有個問題,那就是具狀態性(Stateful)還有容易受跨網域請求偽造攻擊(CSRF Attack)。 先讓我們先以具狀態性的問題為例。
極具狀態性
假設今天有兩台服務器通過負載均衡來分派使用者的請求,由於Session是儲存在服務端上的,第一次使用者登入時是由Server 1處理,那么這個Session自然也就儲存在Server 1 。
但下次負載平衡指派使用者到Server 2 的時候呢? 這個Session也就不存在 ,所以使用者就需要重新登入一次(雖然有辦法可解決,但暫不討論)。
易受跨站請求偽造攻擊
由於Session是儲存在服務端的,這意味着客戶端在發送請求時幾乎不用提供什么數據,對吧? 如果今天有人發送了一個刪除文章的鏈接給你,然后你在不知情的情況下就按下去的時候會發生什么事情? 你的文章會這樣被刪除掉。
為此,有人想出了Token 來解決這個問題,並且能夠在多個服務器上跨域使用。
Token解決了什么?
當使用者登入成功時,他會得到一串看起來毫無意義的亂數字串,但這個字串實際上會對應到數據庫中使用者的身份,簡單說就是另類的帳號,這也被叫做Token。
無狀態性
Token本身是不攜帶數據的且無狀態性的(Stateless),當服務器接收到Token時,會主動去數據庫中找到使用者的數據表,接着就能夠知道這個Token代表着哪個使用者,然后獲取相關的數據來使用。
由於多個服務器都是共享一個數據庫的,所以我們的服務器現在不會有Session 那樣的問題。 但需要注意的是Token 不可以讓別人知道,否則別人就能夠擁有你的身份、偽造成你。
安全性提高
由於使用者現在必須主動提供 Token,也順帶解決了跨網域請求偽造攻擊,如果今天朋友再次給了你刪除文章的鏈接,由於這個鏈接並不帶有 Token,服務器也就不能知道是誰想要刪除文章,所以這個動作自然就會變成無效。
為什么要用JWT 取代傳統Token?
你能夠直接在JWT 中存放數據,而不用額外的查詢數據庫。
當我們使用傳統Token時,我們需要查詢數據庫並且比對這一個Token是誰的,然后我們才能夠取得該使用者的數據。 當有大量使用者涌入時,這可能會讓服務器負荷不堪。
而JWT解決了這個問題,因為我們可以直接把使用者數據存放在「Token」中,所以也就省去了額外的數據庫開銷。 且在微服務架構中也能夠方便地獲取使用者數據。
到這里你可能會開始想:「 這樣不是很危險嗎? 」、「 如果使用者自行修改了Token該怎么辦? 」,而我們接下來就要講一下什么是JWT。
什么是JSON Web Token(JWT)?
先看看JWT 的長相吧。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
上面是一個來自官方網站范例 ,我們將以此做為講解。
JWT 的構成
JWT 實際上是由三個部分組成的。
標頭.內容.簽名
標頭
JWT的標頭包含了兩個部分,一個是加密類型( alg
),另一個則是定義類型( typ
)。
{ "alg": "HS256", "typ": "JWT" }
而這些內容通過Base64轉化就能夠得到我們的第一段字串。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
內容
內容是一個自定義的地方,你可以在里面存放使用者的帳號、昵稱,這樣你就不需要再去數據庫查詢,這聽起來很不安全,但是不用擔心。
不過需要注意的是這些數據並沒有被「加密」,使用者可以直接看到這些數據,所以不推薦在這里擺放信用卡信息、密碼。
{ "sub": "1234567890", "name": "John Doe", "admin": true }
這部分通過Base64轉化也就能得到以下字串。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
簽名
這就是好玩的地方了,當我們要簽發一個JWT 的時候,我們會用一組密碼來簽名,這就是為了避免有人自己更改內容,然后拼湊一個根本不是我們所產生的JWT 來欺瞞服務端。
這部分的算法是由上方兩個區塊的Base64以點( .
)符號組合起來,並以一個字符串(在這里是secret
)加密。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), "secret")
然后我們就會得到簽名。
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
接着組合起來就是JWT 本體。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
由於secret
這個字符串是儲存在服務端的,所以也就沒有人能夠知道(除非暴力破解)。 任何人都可以修改JWT 的內容,但是當他簽發的時候並不知道這個字符串,所以就會有不對的簽名,服務端也就自然不會接受這個錯誤的JWT。
使用JWT 不僅能夠節省服務端的數據庫連接開銷,又能夠在數據分享上變得更加便利。