Session + Cookie & Token(JWT)
Published: 2022-09-25 17:48:54

這篇會分為幾個重點講：
1. 會員登入對應 Client-Server 流程
2. 使用 Token 還是 Session 作為使用者身份驗證？
3. 使用 JWT 還是傳統的 Token？


------------------------------------------------------------
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hmtpknoajc8qhj8yudeo.png)

## HTTP 無狀態
由於 HTTP 是一個無狀態協議，也就是說，Client 每次對 Server 發出的請求都是獨立的，Server 並不會記住 Client 所發出的每個請求及其資訊，因此無法辨識上一次與這一次的訪問者身份是否為同一人，然而我們可以透過 Cookie 或 Session 等機制來判斷每一次發送的請求其身份以及當前使用者的登入狀態等資訊。

## Cookie 和 Session 的區別
- 安全性： Session 相較於 Cookie 安全，Session 是儲存在伺服器端，Cookie 則是儲存在客戶端。

- 存取值的類型不同：Cookie 只能儲存字串資料，Session 則可以儲存任意的資料類型。

- 有效期不同：Cookie 可設置為長時間保持，例如預設登入功能，Session 一般失效時間較短，客戶端關閉（預設情況下）或 Session 超時都會失效。

- 儲存大小不同： Session 可儲存的空間較大，但是客戶過多會佔用大量的資源; Cookie 限制儲存最大不超過 4KB。

------------------------------------------------------------

## Client 訪問 Server 流程
### Session ID 是連接 Cookie 和 Session 的一道橋樑

1. 首先，Client 端會發送一個 Http 請求到 Server 端。

2. Server 端接受 Client 端請求後，會在 Server 端建立 Session，並提供 Client 端相對應的 Session ID，這個 Session ID 將存放於 Response Headers 的 `set-cookie` 欄位。格式如下：

    `Set-Cookie: sessionId=xxx; expires=xxx; path=/;....`

       ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1a3idewa50an5nfu5qx.png)

3. 往後來自 Client 的請求都會帶著 Cookie 的資料去跟後端核對使用者身份，也就是說，Server 會以 Cookie 中 Session ID 尋找 Session 確認使用者驗證狀態。

      ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cyf5cie32ipzhtf5zlgf.png)

**這意味著...**
每位使用者經過驗證後，都需要為其建立和存取 Session 然而：
a. 當使用者量增加，資料庫的花費成本將不斷提高。
b. 每當使用者拜訪需經驗證的路徑時，也需要額外到資料庫執行資料查詢，花費額外的效能。

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmld7iv557psx16fv0sl.png)

> JSON Web Token（JWT）也因此誕生，它更符合設計 RESTful API 時「Stateless 無狀態」原則：意味著每一次從客戶端向伺服器端發出的請求都是獨立的，使用者經驗證後，在伺服器端不會將用戶驗證狀態透過 Session 儲存起來，因此每次客戶端發出的請求都將帶有伺服器端需要的所有資訊（JWT 字串） 。

### JSON Web Token（JWT）
是一種認證授權機制。
作用：驗證使用者身分，並可跨域獲取資源的作法
特性：
            - Stateless 無狀態
            - 安全

### JWT 與 Token 認證方式：
- Token：Server 驗證 Client 發送過來的 Token 時，還需要查詢資料庫獲取用戶資訊，然後驗證 Token 是否有效。（會將 Token 儲存在 Server）

- JWT： 將 Token 和 Payload 加密後儲存於 Client 端，Server 只需要對 Token 進行解密並驗證，不需要查詢資料庫或減少查詢資料庫，因為 JWT 本身包含了用戶資訊和加密的資料。（Server 不會儲存 Token）

### JWT 簽名(Signature)
**產生 Token：**
由 header、payload、signature 三段字串所組成，並以`.`區隔。格式如下：

```javascript
// header
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
// payload
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
// Signature
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```

1. header：`alg`用來做簽名或加密解密的演算法; `typ`為 Token 類型

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

2. payload：用來存放用戶資訊，通常不放隱私資訊在 payload。`sub`用戶id; `iss`簽發人; `iat`簽發時間; `aud`接受方; `nbf`生效時間; `exp`過期時間; `jti`編號等預設字段

       ```json
{
  "sub": "1234567890",
  "name": "John Doe",
  "exp": 1516239022
}
```

3. 帶有兩個參數，第一個參數：對 header、payload 進行 base64 加密演算法生成的簽名; 第二個參數(secret)：為 Server 端自定義的字串

       ```javascript
signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
```

**驗證 Token：**
1. 對 Token 進行切割成三部分

2. 對 payload 進行 base64 解密，並驗證 Token 時效性

3. 對 header、payload 進行字串拼接

4. 再次進行 HS256 加密生成字串

### Token 登出、改密後失效
使用 JWT 時，一般修改密碼或登出時，需要把正在使用的 Token 做失效處理，以防止其他客戶端請求訪問資訊。以下為 Token 失效的方法：
- 方法一：在每次修改密碼或登出時，都去修改自定義的 Salt 值，如此一來，當使用者再次訪問時，會判斷 Salt 值是否修改，修改過即表示驗證 Token 失敗。

- 方法二：將修改、登出以及創建 Token 時間儲存在資料庫，如此一來，將可判斷創建時間若小於修改或登出時間，即表示 Token 過期。

------------------------------------------------------------

### 有了 JWT 之後
1. 使用者登入之後，Server 將進行驗證並返回 JWT 到 Client 端。

2. Client 收到響應後，存進 Cookie。

3. 往後使用者再次登入時，瀏覽器將自動把 Cookie(JWT) 存放於 Request Headers 的 `authorization` 欄位。格式如下：

       ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3e6p6gljs9d9tejp8cgg.png)

4. Server 接受請求，解析並驗證 Cookie，核對成功後返回響應給 Client 端。

       ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wtjadsdgrjlvz60kgbkf.png)

------------------------------------------------------------

### 總結：
- Token 取代 Session：用解析 Token 的計算時間換取 Session 的儲存空間，從而減輕伺服器的壓力，減少頻繁的查詢資料庫。

- JWT 取代傳統的 Token：將可省去額外的資料庫開銷，讓我們可以直接把使用者資料存放在 Token 中。

- 沒有簽名的 JWT：由 header、payload 通過編碼生成的字串。

- 有簽名的 JWT：通過 header 裡指定的演算法(HS256)對 header、payload 進行加密，主要用於確保資料不被篡改。

- 每一個 JWT token 都應該在送出給 Client 前進行簽名，如果一個 Token 沒有簽名，那麼 Client 即可自由修改 Token 中的內容。