> For the complete documentation index, see [llms.txt](https://tobyisme.gitbook.io/php/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tobyisme.gitbook.io/php/jwtjson-web-token.md).

# 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 ]來得到這個字串

```javascript
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對象。

```javascript
{
  "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那本筆記有紀錄)。
