Skip to content

Commit

Permalink
done Part15-User-jsonwebtoken
Browse files Browse the repository at this point in the history
  • Loading branch information
andy6804tw committed Jan 11, 2018
1 parent dcfae96 commit 3821b8e
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 2 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@
- API Error
- [Link 連結](https://github.com/andy6804tw/RESTful_API_start_kit/tree/Part14-API-Error)
- [Tutorial 教學](/tutorials/Part14-API-Error.md)
- 用JWT取代傳統Session來驗證使用者身份
- [Link 連結](https://github.com/andy6804tw/RESTful_API_start_kit/tree/Part15-User-jsonwebtoken)
- [Tutorial 教學](/tutorials/Part15-User-jsonwebtoken.md)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"express-validation": "^1.0.2",
"http-status": "^1.0.1",
"joi": "^13.0.2",
"jsonwebtoken": "^8.1.0",
"morgan": "^1.9.0",
"mysql": "^2.15.0"
},
Expand Down
11 changes: 10 additions & 1 deletion src/server/modules/user.module.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import mysql from 'mysql';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import config from '../../config/config';
import APPError from '../helper/AppError';

Expand Down Expand Up @@ -127,7 +128,15 @@ const selectUserLogin = (insertValues) => {
const userPassword = insertValues.user_password; // 使用者登入輸入的密碼
bcrypt.compare(userPassword, dbHashPassword).then((res) => { // 使用bcrypt做解密驗證
if (res) {
resolve('登入成功'); // 登入成功
// 產生 JWT
const payload = {
user_id: result[0].user_id,
user_name: result[0].user_name,
user_mail: result[0].user_mail
};
// 取得 API Token
const token = jwt.sign({ payload, exp: Math.floor(Date.now() / 1000) + (60 * 15) }, 'my_secret_key');
resolve(Object.assign({ code: 200 }, { message: '登入成功', token })); // 登入成功
} else {
reject(new APPError.LoginError2()); // 登入失敗 輸入的密碼有誤
}
Expand Down
78 changes: 78 additions & 0 deletions tutorials/Part15-User-jsonwebtoken.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
layout: post
title: '[Node.js打造API] (實作)用JWT取代傳統Session來驗證使用者身份'
categories: '2018iT邦鐵人賽'
description: 'JSON Web Token'
keywords: api
---

## 本文你將會學到
- 了解 JWT 運作原理
- 實作使用者登入並取得一組 API Token

## 前言
為什麼要有 API Token 呢?各位可以想想若今天我要存取特別資料例如交易紀錄或是發文紀錄,這些資料都有獨特性也就是這些資料只有你能做存取與修改,所以在訪問某些重要的 API 前就必須要有一個 Token 來驗證你是否有權限來訪問裡面的資料,那我們該怎去實作呢?早期作法就是使用 Session 並且生成一個 Session 的 Token 我們稱 SessionID,所謂 SessionID 就是一個既不會重複,又不容易被找到規律以免被仿造的字符串,你可以自己隨機產生一組字串,可能用當下時間戳記、年、月、日、時、分、秒來當種子搭配英文26個字母外加標點符號經亂數搗亂後所產生的一組字串可能長度30、20之類的,每次訪問某特別 API 前就由 SessionID 來做 Token 驗證,但這不是今天的重點!今天要用一個比較新的方法而且更方便更快速的來產生我們要的 API Token 就是 JWT !

## 何謂 JWT
JWT 是 JSON Web Token 的縮寫,通常用來解決身份認證的問題,JWT 是一個很長的 base64 字串在這字串中分為三個部分別用點號來分隔,第一個部分為 `Header` ,裡面分別儲存型態和加密方法,通常系統是預設 `HS256` 雜湊演算法來加密,官方也提供許多演算法加密也可以手動更改加密的演算法,第二部分為 payload,它和 Session 一樣,可以把一些自定義的數據存儲在 `Payload` 裡例如像是用戶資料,第三個部分為 `Signature`,做為檢查碼是為了預防前兩部分被中間人偽照修改或利用的機制。

- Header(標頭): 用來指定 hash algorithm(預設為 HMAC SHA256)
- Payload(內容): 可以放一些自己要傳遞的資料
- Signature(簽名): 為簽名檢查碼用,會有一個 serect string 來做一個字串簽署

把上面三個用「.」接起來就是一個完整的 JWT 了!更多詳細內容可以參考[這篇](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/)文章。

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJBbmR5MTAiLCJ1c2VyX21haWwiOiJhbmR5QGdtYWlsLmNvbSIsInVzZXJfcGFzc3dvcmQiOiJwYXNzd29yZDEwIiwiaWF0IjoxNTE1MTQwNDg0fQ.P41UlFdYNIho2EA8T5k9iNK0EMC-Wn06RKk_0FFNjLo
```

<img src="/images/posts/it2018/img1070111-1.png">

流程: 使用者登入 -> 產生 API Token -> 進行 API 路徑存取時先 JWT 驗證 -> 驗證成功才允許訪問該 API


## 安裝 jsonwebtoken
安裝 Node.js 的 jsonwebtoken 組件。

```bash
yarn add jsonwebtoken
```

## 修改 user.module.js
開啟 `user.module.js` 並修改 `selectUserLogin` 函式中登入成功的地方,首先建立一個 payload 內容為你要傳遞的資料,這邊放入使用者的資訊包含 id、姓名、信箱,密碼這邊就不用存了一方面也是安全,建立好 payload 後就可以利用 jsonwebtoken 組件 來產生一個 Token 囉!我們使用 `jwt.sign()` 來取得 Token ,該方法有兩個參數第一部份是 Payload + 狀態資料,所謂的狀態資料有 expiresIn、notBefore、audience、subject、issuer,其中我們有使用 expiresIn(exp) 這是設定 Token 的時效性,格式為時間戳記這邊範例就以15分鐘為例,第二個參數為 Signature 是個字串型態的簽署金鑰,這個金鑰是保密的也只存放在後端不能前端用戶知道,否則會被不法人士利用來修改內容,此秘密金鑰在最後的 API 驗證才會使用到。

最後取得了 Token 就回傳結果,這邊使用 ES6 的物件生成寫法 `Object.assign()` 裡面用大括號包起來內容回傳 HTTP 狀態碼、登入訊息、和一組 API Token。

<img src="/images/posts/it2018/img1070111-2.png">

```js
/** user.module.js
selectUserLogin 程式片段 **/
...
// 產生 JWT
const payload = {
user_id: result[0].user_id,
user_name: result[0].user_name,
user_mail: result[0].user_mail
};
// 取得 API Token
const token = jwt.sign({ payload, exp: Math.floor(Date.now() / 1000) + (60 * 15) }, 'my_secret_key');
resolve(Object.assign({ code: 200 }, { message: '登入成功', token })); // 登入成功
...
```

## 登入測試
將程式碼 `yarn build``yarn start` 後,開啟Postman在網址列輸入 `http://127.0.0.1:3000/api/user/login` 並選擇 POST 請求方式,接下來是要放入修改的內容,`Body > raw > 選擇 JSON(application/json)`,將信箱與密碼用 JSON 格式送出。

**登入成功**

輸入一筆當時建立用戶的信箱與密碼,登入成功後會取得一個 JSON 物件,包含 HTTP 狀態碼、登入訊息、和一組 API Token。

```json
{
"user_mail":"[email protected]",
"user_password":"password10"
}
```

<img src="/images/posts/it2018/img1070111-3.png">
81 changes: 80 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,10 @@ base64-js@^1.0.2:
version "1.2.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"

[email protected], base64url@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb"

basic-auth@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba"
Expand Down Expand Up @@ -906,6 +910,10 @@ browserslist@^2.1.2:
caniuse-lite "^1.0.30000770"
electron-to-chromium "^1.3.27"

[email protected]:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"

buffer-xor@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
Expand Down Expand Up @@ -1313,6 +1321,13 @@ ecc-jsbn@~0.1.1:
dependencies:
jsbn "~0.1.0"

[email protected]:
version "1.0.9"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1"
dependencies:
base64url "^2.0.0"
safe-buffer "^5.0.1"

[email protected]:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
Expand Down Expand Up @@ -2320,6 +2335,21 @@ jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"

jsonwebtoken@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz#c6397cd2e5fd583d65c007a83dc7bb78e6982b83"
dependencies:
jws "^3.1.4"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.0.0"
xtend "^4.0.1"

jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
Expand All @@ -2329,6 +2359,23 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"

jwa@^1.1.4:
version "1.1.5"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
dependencies:
base64url "2.0.0"
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.9"
safe-buffer "^5.0.1"

jws@^3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2"
dependencies:
base64url "^2.0.0"
jwa "^1.1.4"
safe-buffer "^5.0.1"

kind-of@^3.0.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
Expand Down Expand Up @@ -2390,6 +2437,34 @@ lodash.cond@^4.3.0:
version "4.5.2"
resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"

lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"

lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"

lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"

lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"

lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"

lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"

lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"

lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.9.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
Expand Down Expand Up @@ -2534,6 +2609,10 @@ [email protected]:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"

ms@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"

[email protected]:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
Expand Down Expand Up @@ -3735,7 +3814,7 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"

xtend@^4.0.0:
xtend@^4.0.0, xtend@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"

Expand Down

0 comments on commit 3821b8e

Please sign in to comment.