JWT(Json Web Token)

安全的在web應用間傳遞信息

JSON Web Token(以下簡稱JWT)是一個非常輕巧的規範。這個規範允許我們使用JWT在用戶和服務器之間傳遞安全可靠的信息。

來假想一下。用戶甲關注了用戶乙,系統發mail給用戶乙,並且附有一個連結"點此關注用戶甲"。連結的地址為:

https://your.awesome-app.com/add-friend/?from_user=乙&target_user=甲

上面的URL主要通過URL來描述,這樣作有一個弊端,必須要要求用戶乙先登錄。可不可以簡化這個流程,讓用戶乙部用登錄就可以完成這個操作,JWT就允許我們作到這點。

JWT的組成

一個JWT實際上就是一個字串,它由三部份組成,頭部、載荷以及簽名。

載荷(Payload)

我們繼續沿用上面假想的加好友情境,將這操作描述成一個JSON對象。其中添加了一些其它的信息,幫助今後收到這個JWT的服務器理解這個JWT。

{
    "iss": "Jude Chih JWT",
    "iat": 1234567890,
    "exp": 1234567890,
    "aud": "www.example.com",
    "sub": "sausage@example.com",
    "from_user": "乙",
    "target_user": "甲"
}

這裡面前五個都是由JWT的標準所定義的。

  • iss:該JWT的簽發者

  • sub:該JWT所面向的用戶

  • aud:接收該JWT的一方

  • exp:什麼時候過期,這裡是一個Unix的timestamp

  • iat:在什麼時候簽發的

將上面的JSON對象進行[base64]編碼,可以得到下面的字串,這個字串我們將它稱作JWT的Payload。

eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9

如果你使用Node.js,可以用Node.js的包[ base64url ]來得到這個字串

var base64url = require('base64url')
var header = {
    "from_user": "B",
    "target_user": "A"
}
console.log(base64url(JSON.stringify(header)))
// 输出:eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9

PS:Base64是一種編碼,也就是說它是可以被翻譯回原來的樣子的,它並不是一種加密過程。

頭部(Header)

JWT還需要一個頭部,頭部用於描述關於該JWT的最基本的信息,例如其類型以及簽名所用的算法等,這也可以被看成一個JSON對象。

{
  "typ": "JWT",
  "alg": "HS256"
}

在這裡,我們說明了這是一個JWT,並且我們所用的簽名算法(後面會提到)是HS256算法。

我們將頭部也進行Base64編碼,出來的字串就是JWT的頭部了。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

簽名

將上面兩個編碼字串用.連接就形成了

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0

最後,我們將上面拼接完的字串用HS256算法進行加密。在加密的時候,我們還需要個密鑰,假設我們用mystar做為密鑰的話,那麼就可以得到我們加密後的內容

rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

這就是簽名

最後將頭部、載荷以及簽名之間都用.連接,我們就可以得到完整的JWT

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

於是我們就可以把剛剛那個加好友的URL改成下面這樣

https://your.awesome-app.com/make-friend/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

這樣就安全的完成天加好友的操作了。

簽名的目的

最後一步簽名的過程,實際上是對頭部以及載荷內容進行簽名。一般而言,加密算法對於不同的輸入產生的輸出會不一樣。

所以,如果有人對頭部以及載荷的內容解碼之後進行修改,再進行編碼,那麼新的頭部和載荷的簽名和之前的簽名就將是不一樣的,而且如果不知道服務器加密的時候用的密鑰的話,得出來的簽名也一定會是不一樣的。

服務器應用在接受到JWT後,會首先對頭部和載荷的內容用同一算法再次簽名,那麼服務器應用是怎麼知道我們用的是哪一種算法呢?因為我們在JWT的頭部中已經用alg指明了我們的加密算法了。

如果服務器應用對頭部和載荷再次以同樣方法簽名之後發現,自己計算出來的簽名和接受到的簽名不一樣,那麼就說明這個Token的內容被別人動過,我們將拒絕這個Token,跳出401錯誤頁面。

信息會暴露

所以,在JWT中,不應該在載荷裡面加入任何敏感的數據,在上面的例子中,我們傳輸的是用戶的UserID,這個實際上不是什麼敏感內容,一般情況下被知道也是安全的。

但是像密碼這樣的內容就不能被放在JWT中了。如果將用戶的密碼放在了JWT中,那麼遇到惡意的第三方就可以通過base64解碼很快的知道你的密碼了。

JWT的適用場景

我們可以看到,JWT適合用於向Web應用傳遞一些非敏感信息。例如在上面題到的完成加好友的操作,還有諸如下訂單的操作等等。

其實JWT還經常用於設計用戶認證和授權系統。甚至實現Web應用的單點登入(這個功能我在Laravel那本筆記有紀錄)。

Last updated