這個章節要來討論的是以下內容
GoGo!
先來看看「JWT」是什麼的介紹
嗯,當然可以為你解釋 JWT。JWT 的全名是「JSON Web Token」,它是一種用來在網路上安全地傳輸資訊的方式。
簡單來說,JWT 是一個簡潔且安全的方式,讓我們可以在不同的系統之間傳遞資訊,並確保資訊的真實性和完整性。這在前端工程師的工作中經常會用到,特別是在處理身份驗證和授權的情境。
一個 JWT 主要由三部分組成:
- 標頭 (Header): 通常包含了該 Token 的類型以及使用的簽名算法。這部分通常是固定不變的。
- 有效載荷 (Payload): 包含了要傳遞的資訊,以 JSON 格式儲存。例如,可以包含使用者的識別資訊或者其他需要傳遞的資訊。
- 簽名 (Signature): 這部分使用了前兩部分的資訊,並加上一個密鑰來進行簽名。這可以確保在傳輸過程中 Token 沒有被篡改。
當收到一個 JWT 時,我們可以用相同的密鑰來驗證簽名,確保該 Token 的真實性。這讓我們可以信任這個 Token 中的資訊。
這在前端開發中特別有用,因為它讓我們可以在不同的系統之間傳遞資訊,同時又保持了安全性。
如果你需要更詳細的範例或程式碼,也可以告訴我哦!
ChatGPT只有簡單的說「是一個簡潔且安全的方式」
JWT到底長什麼樣子?怎麼個簡潔安全?
我們繼續問下去
當然可以,讓我用簡單清楚的方式來介紹 JWT 的樣子和運作原理。
JWT 的樣子:
一個 JWT 通常看起來像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
它由三個部分以點(
.
)分隔而成:
標頭 (Header): 通常包含了該 Token 的類型以及使用的簽名算法。這通常是固定的,以 JSON 格式表示。
範例:
{ "alg": "HS256", "typ": "JWT" }
有效載荷 (Payload): 包含了要傳遞的資訊,以 JSON 格式儲存。例如,可以包含使用者的識別資訊或者其他需要傳遞的資訊。
範例:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
簽名 (Signature): 這部分使用了前兩部分的資訊,並加上一個密鑰來進行簽名,以確保 Token 的真實性。
範例:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
JWT 的運作原理:
- 建立 JWT:
- 伺服器收到一個請求,需要生成一個 JWT 來回應客戶端。
- 伺服器將需要傳遞的資訊放入 Payload 中,例如使用者識別資訊。
- 伺服器使用特定的密鑰對 Header 和 Payload 進行簽名,生成簽名部分。
- 傳遞 JWT:
- 伺服器將生成的 JWT 回傳給客戶端,客戶端可以將其保存在 Local Storage 或者 Cookie 中。
- 驗證 JWT:
- 當客戶端發送請求時,將 JWT 放在請求的 Header 中(通常以
Authorization
標頭)。- 伺服器收到請求後,將收到的 JWT 進行解析,並使用相同的密鑰驗證簽名部分。
- 使用 JWT:
- 如果簽名驗證成功,伺服器可以信任 JWT 中的資訊,例如使用者識別資訊,並執行相應的操作。
這樣,JWT 提供了一種安全且可靠的方式來在不同系統之間傳遞資訊,同時確保了資訊的真實性和完整性。如果你需要更多範例或者有任何問題,隨時告訴我哦!
上面的那串「JWT」,如果用 .
分隔開來,分出的三段內容就是這樣
標頭 (Header):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
有效載荷 (Payload):
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
簽名 (Signature):
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
根據簽名的製作方法,可以推斷知道前兩段是經過base64的 編碼(encode)獲得
在js中將編碼解開的方式是 atob('已編碼的字串')
(ascii to binary)
所以我們簡單點擊F12,打開console來試看看
果然前兩段都可以透過 atob()
還原,變成ChatGPT說的JSON內容
用 btoa (binary to ascii) 就是 ChatGPT說的 base64UrlEncode 步驟了
而最後一段「簽名」,則是把前兩個編碼字串用.
組合之後
加上「密鑰(secret)」,放入「HMACSHA256」的處理
這個「HMACSHA256」是什麼?為什麼這樣就會「安全」?
透過將標頭和有效載荷與一個秘密(稱為 secret)傳遞給 HMACSHA256 函數,可以保證 JWT 的安全性的原因如下:
- 秘密 (Secret): 秘密是一個只有伺服器知道的隨機字串。它作為簽名的關鍵。因為只有伺服器知道這個秘密,其他人無法生成相同的簽名。
- HMACSHA256 函數: 這是一種基於哈希函數的加密方式,它將輸入資料(在這裡是標頭和有效載荷)與秘密結合起來,生成一個固定長度的雜湊值。這個雜湊值即為簽名。
- 保證資訊完整性: 簽名的存在保證了 JWT 的完整性。如果有人在傳輸過程中偷偷修改了標頭或有效載荷,簽名將無法驗證,伺服器會知道這個 JWT 已經被篡改。
- 驗證來源: 由於只有伺服器知道秘密,只有伺服器才能生成正確的簽名。這意味著當伺服器收到一個 JWT 時,它可以使用相同的秘密來重新計算簽名,並確保它與 JWT 中的簽名匹配。
總的來說,使用 HMACSHA256 函數與秘密可以確保 JWT 在傳輸過程中不被篡改,同時也可以驗證 JWT 的來源是否為可信任的伺服器。這樣就保證了 JWT 的安全性和可靠性。
獲得了一些關鍵字:「哈希函數 (Hash)」
但他沒有跟我們解釋這樣驗證的原理是什麼,只說可用相同的「secret」來重新計算簽名
我們再問清楚一點
簽名的驗證原理和哈希函數是密切相關的,讓我來簡單地解釋:
哈希函數:
哈希函數是一種數學運算,它可以將任意大小的輸入(訊息)轉換成固定大小的輸出(雜湊值)。這個過程是單向的,也就是說,可以從雜湊值反推出原始訊息幾乎是不可能的。
哈希函數的特性包括:
- 固定輸出: 無論輸入的訊息大小如何,哈希函數總是產生相同長度的雜湊值。
- 快速運算: 能夠在相對較短的時間內計算出雜湊值。
- 即時性: 對於相同的輸入,哈希函數總是生成相同的雜湊值。
- 碰撞耐受: 雖然理論上可能會有相同的雜湊值對應到不同的輸入,但實際上在強大的哈希函數中,這是非常困難的。
簽名的驗證原理:
在 JWT 的情境下,簽名的驗證原理如下:
- 伺服器接收到一個 JWT,並提取其中的標頭和有效載荷。
- 伺服器使用相同的密鑰以及相同的哈希函數(在這裡是 HMACSHA256)來重新計算簽名。
- 伺服器將重新計算的簽名與 JWT 中的簽名進行比較。
- 如果兩者相符,這表示 JWT 沒有在傳輸過程中被篡改,並且伺服器可以信任其中的資訊。
- 如果簽名不符,這表示 JWT 已經被篡改,伺服器會拒絕信任其中的資訊。
簽名的驗證原理基於哈希函數的特性,確保了在傳輸過程中 JWT 的完整性。只有知道密鑰的伺服器才能正確生成相應的簽名,進而驗證 JWT 的真實性。
所以,前面範例中,第三部分的簽名: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
是用某個我們不曉得內容的固定「密鑰」,搭配固定的hash函數,所生成的字串
因為「相同輸入可以生成相同雜湊」
所以只要對方偽造的JWT更改了任何一個部分,像是第二段的JSON資料
經過base64編碼再丟入hash,就會跑出不一樣的內容
而且這邊要注意的是,經過Hash產生的字串,我們並沒有要把它解開。能解開的是前面兩段base64
請ChatGPT再把流程圖畫一下
當然可以!以下是使用 Mermaid 來表示JWT身份驗證機制的流程以及簽名驗證的流程:
graph TD; A[客戶端] -->|請求| B[伺服器] B -->|生成JWT| C[客戶端] C -->|攜帶JWT| B D[伺服器] -->|接收請求| E[客戶端] E -->|攜帶JWT| D D -->|解析JWT| F[取得資訊] F -->|進行身份驗證| G[驗證通過] G -->|回應| E
這是JWT身份驗證機制的流程:
- 客戶端向伺服器發送請求。
- 伺服器生成JWT並返回給客戶端。
- 客戶端攜帶JWT在未來的請求中。
在驗證的流程中:
- 伺服器接收到請求並從中提取JWT。
- 伺服器解析JWT,獲取其中的資訊。
- 進行身份驗證,確保使用者的身份有效。
- 驗證通過,伺服器回應客戶端。
接下來是簽名驗證的流程:
graph TD; A[伺服器] -->|收到JWT| B[解析JWT] B -->|取得標頭和有效載荷| C[重新計算簽名] C -->|取得儲存在JWT中的簽名| D[比較兩者] D -->|簽名相符| E[驗證通過] D -->|簽名不符| F[驗證失敗]
這是簽名驗證的流程:
- 伺服器收到JWT並解析它。
- 伺服器取得JWT中的標頭和有效載荷。
- 重新計算簽名,使用相同的密鑰和哈希函數。
- 取得儲存在JWT中的簽名。
- 比較重新計算的簽名與JWT中的簽名。
- 如果兩者相符,驗證通過;如果不符,驗證失敗。
這兩個流程描述了JWT的身份驗證機制以及簽名驗證的流程。如果你需要更多的解釋或範例,請隨時告訴我!
Wow這個流程圖很可以
接下來就是請ChatGPT直接用程式碼示範一下要怎麼製作JWT的簽名與驗證了
當然可以!以下是使用 Node.js 實作JWT的簽名與驗證機制的範例:
首先,你需要安裝
jsonwebtoken
套件,這是用於處理JWT的套件。npm install jsonwebtoken
接著,你可以使用以下的程式碼來進行簽名與驗證:
簽名 (Sign):
const jwt = require('jsonwebtoken'); // 假設這是你的秘密 const secret = 'mySecretKey'; const payload = { userId: '123456', username: 'john_doe' }; const token = jwt.sign(payload, secret, { expiresIn: '1h' }); console.log('生成的Token:', token);
驗證 (Verify):
const jwt = require('jsonwebtoken'); const secret = 'mySecretKey'; const token = '在這裡插入你的JWT'; jwt.verify(token, secret, (err, decoded) => { if (err) { console.error('驗證失敗:', err); } else { console.log('解碼後的資訊:', decoded); } });
這個範例演示了如何使用 Node.js 和
jsonwebtoken
套件來進行JWT的簽名與驗證。請確保在實際應用中將secret
替換為你自己的私鑰,並適當地處理錯誤。如果你有任何進一步的問題或需要更多的說明,請隨時告訴我!
好…的,現在的套件實在很方便啊
直接用套件來處理,非常方便!我們等最後再請ChatGPT示範一個不用套件的版本
先繼續回到我們的主題,當想要在NestJS中處理JWT身份驗證的話要怎麼做呢
當然可以!以下是使用 NestJS 框架實作JWT機制的示範,包含取得JWT以及在接受請求時進行身份驗證:
首先,確保你已經建立了一個 NestJS 專案。如果尚未建立,你可以使用以下指令創建一個新的 NestJS 專案:
nest new project-name
接著,我們將會進行以下步驟:
步驟一:安裝相關套件
首先,我們需要安裝
jsonwebtoken
套件,這是用於處理JWT的套件:npm install jsonwebtoken
步驟二:建立JWT Service
在
src
資料夾下創建一個jwt.service.ts
檔案,並添加以下程式碼:import { Injectable } from '@nestjs/common'; import * as jwt from 'jsonwebtoken'; @Injectable() export class JwtService { private readonly secretKey = 'mySecretKey'; // 這裡放置你的私鑰 generateToken(payload: any): string { return jwt.sign(payload, this.secretKey, { expiresIn: '1h' }); } verifyToken(token: string): any { try { return jwt.verify(token, this.secretKey); } catch (error) { throw new Error('Token 驗證失敗'); } } }
步驟三:建立AuthGuard
在 NestJS 中,我們可以使用
AuthGuard
來進行身份驗證。在src
資料夾下創建一個jwt.guard.ts
檔案,並添加以下程式碼:import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; import { JwtService } from './jwt.service'; @Injectable() export class JwtAuthGuard implements CanActivate { constructor(private readonly jwtService: JwtService) {} canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); const token = request.headers.authorization?.split(' ')[1]; if (token) { try { const decoded = this.jwtService.verifyToken(token); request.user = decoded; return true; } catch (error) { return false; } } return false; } }
步驟四:使用AuthGuard進行身份驗證
在你的 Controller 中使用
JwtAuthGuard
來進行身份驗證。例如:import { Controller, Get, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from './jwt.guard'; @Controller('protected') export class ProtectedController { @Get() @UseGuards(JwtAuthGuard) getProtectedData(): string { return '這是受保護的資料'; } }
步驟五:生成並返回JWT
在你的 Controller 中使用
JwtService
來生成並返回JWT。例如:import { Controller, Get } from '@nestjs/common'; import { JwtService } from './jwt.service'; @Controller('auth') export class AuthController { constructor(private readonly jwtService: JwtService) {} @Get('login') login(): { access_token: string } { const token = this.jwtService.generateToken({ userId: '123456' }); return { access_token: token }; } }
現在,你已經設置了一個基本的JWT機制,包括生成JWT和在接受請求時進行身份驗證。當客戶端訪問
auth/login
時,將生成一個JWT並返回給客戶端。當客戶端訪問protected
路由時,會先經過JwtAuthGuard
進行身份驗證。請確保在實際應用中適當地處理錯誤和設定私鑰。如果你有任何進一步的問題或需要更多的說明,請隨時告訴我!
恩..雖然提供了程式碼
但是一來他放了兩個controller,但沒有對應模組引用,就預設注入了JwtService
二來也沒有一起提供app.module.ts要怎麼設置
不夠完整,為了更好測試和讓情況簡單化,請ChatGPT給更完整可以跑的程式碼
當然可以!我們可以把
login
和getProtectedData
先放在AppController
裡面。首先,讓我們在
app.controller.ts
中簡單實作這兩個路由:import { Controller, Get, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from './jwt.guard'; import { JwtService } from './jwt.service'; @Controller() export class AppController { constructor(private readonly jwtService: JwtService) {} @Get('login') login(): { access_token: string } { const token = this.jwtService.generateToken({ userId: '123456' }); return { access_token: token }; } @Get('protected') @UseGuards(JwtAuthGuard) getProtectedData(): string { return '這是受保護的資料'; } }
接下來,我們需要更新
app.module.ts
來設置我們的相依模組:import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { JwtAuthGuard } from './jwt.guard'; import { JwtService } from './jwt.service'; @Module({ imports: [], controllers: [AppController], providers: [JwtAuthGuard, JwtService], }) export class AppModule {}
這樣我們就在
AppModule
中註冊了我們的AppController
、JwtAuthGuard
以及JwtService
。現在,你可以使用
npm run start
啟動你的 NestJS 應用,並在瀏覽器或 Postman 中訪問http://localhost:3000/login
和http://localhost:3000/protected
來測試JWT的機制了。如果你有任何進一步的問題或需要更多的說明,請隨時告訴我!
好耶夠完整了,照著上面的做之後
先來看看那個 'protected'
的GET方法,http://localhost:3000/protected
如果沒有攜帶任何JWT資料的時候長什麼樣子
果然直接得到了403
接著我們要一下JWT: http://localhost:3000/login
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTYiLCJpYXQiOjE2OTg0NzQ0NDgsImV4cCI6MTY5ODQ3ODA0OH0.e9oMjkyJdWtUmIPIx3UqOHZwl76MRqurfzb5Txs3r1I
一樣拆解一下前兩個header與payload的部分
注意的是,程式碼中有有效期限的部分return jwt.sign(payload, this.secretKey, { expiresIn: '1h' });
簽出來的payload也就帶有一小時的有效期限
拿到了之後要怎麼測試呢?
如果是前端使用Axios的話,就可以輕鬆在header中夾帶JWT
如果身為後端要自己測試的話該怎麼辦呢?
要在請求中帶著JWT token來測試
http://localhost:3000/protected
,你可以在請求的標頭(headers)中添加Authorization
標頭,值為Bearer <token>
,其中<token>
是你生成的JWT token。例如,使用curl來模擬一個帶有JWT token的請求:
curl -X GET http://localhost:3000/protected -H "Authorization: Bearer <your_token>"
請確保將
<your_token>
替換為你實際生成的JWT token。如果你使用的是Postman或其他API測試工具,可以在請求的標頭中手動添加
Authorization
標頭,並將值設為Bearer <your_token>
。這樣就可以在請求中攜帶JWT token進行測試了。如果有任何進一步的問題或需要更多的說明,請隨時告訴我!
OK這兩種我們都試看看
用curl :打開終端機,把JWT組合一下,變成這樣
curl -X GET http://localhost:3000/protected -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTYiLCJpYXQiOjE2OTg0NzQ0NDgsImV4cCI6MTY5ODQ3ODA0OH0.e9oMjkyJdWtUmIPIx3UqOHZwl76MRqurfzb5Txs3r1I"
用postman看看:記得選到Auth的欄位,類型照著ChatGPT說的選 Bearer Token
透過這樣的方式設定Authorization
,就可以正常的請求了!
啊.然後Postman我沒有規劃要做成一篇來介紹
但是下一篇會介紹其他更方便前後端測試的方式,就簡單說一下而已
再回顧一下 JwtService
的程式碼
private readonly secretKey = 'mySecretKey'; // 這裡放置你的私鑰
這邊的話就是把你的私鑰放在程式碼中
這麼一來,如果把這段程式碼推上git等地方,你的密鑰就被知道了
就如同你能夠製作簽名&驗證,關鍵都是這個密鑰
被別人知道的話,別人就能偽造JWT傳送了
所以該怎麼辦才好呢?
當你需要保護你的私鑰(例如在版本控制中不公開)時,你可以使用環境變數來存儲私鑰。
以下是一個使用環境變數來保護私鑰的方法:
步驟一:在專案中建立 .env 檔案
在專案的根目錄中,建立一個名為
.env
的檔案,並在裡面添加私鑰:SECRET_KEY=mySecretKey
步驟二:使用 dotenv 套件
安裝
dotenv
套件來載入.env
檔案中的環境變數:npm install dotenv
在你的
main.ts
(或main.js
) 檔案中加入以下程式碼:import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as dotenv from 'dotenv'; async function bootstrap() { dotenv.config(); // 讀取 .env 檔案中的環境變數 const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();
步驟三:在 JwtService 中使用環境變數
在
JwtService
中,取得環境變數的私鑰:import { Injectable } from '@nestjs/common'; import * as jwt from 'jsonwebtoken'; @Injectable() export class JwtService { private readonly secretKey = process.env.SECRET_KEY; // 使用環境變數中的私鑰 generateToken(payload: any): string { return jwt.sign(payload, this.secretKey, { expiresIn: '1h' }); } verifyToken(token: string): any { try { return jwt.verify(token, this.secretKey); } catch (error) { throw new Error('Token 驗證失敗'); } } }
步驟四:將 .env 加入 .gitignore
確保你的
.env
檔案被加入到你的.gitignore
檔案中,這樣它就不會被提交到版本控制中:.env
這樣你的私鑰就不會被公開在版本控制中,而是以環境變數的方式來存取。當你部署到不同的環境時,只需在該環境中設定相對應的環境變數即可。
照著做之後,設定成功!一樣可以跑、可以驗證和讀取
實作完之後,我們再來了解一些和JWT相關的其他問題
Bearer
是一種身份驗證機制中的一個認證方式,通常用於 HTTP 的標頭(headers)中,以識別傳送的資訊的類型。在 JWT 中,通常在使用 JWT token 進行身份驗證時,會將 token 放在Authorization
標頭中,並以Bearer
作為前綴。這是一個範例
Authorization
標頭:Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
這裡的
Bearer
表示這是一個使用 Bearer 認證方式的 token。為什麼需要Bearer?
- 明確的指示身份驗證方式:
Bearer
告訴服務器,這是一個使用 Bearer token 身份驗證的請求。- 區分不同的身份驗證方式:在 HTTP 標頭中可以有多種不同的身份驗證方式(例如 Basic、Digest、Bearer 等)。使用
Bearer
前綴可以清楚地指示正在使用的是 Bearer token。總的來說,
Bearer
是一種標準的方式,用於指示請求中的 token 是屬於 Bearer token 身份驗證方式。這樣伺服器就能夠正確地處理這個請求,並使用對應的驗證機制來進行身份驗證。
當然可以,你可以手動實作JWT的簽名和驗證機制,而不依賴
jsonwebtoken
套件。以下是一個簡單的範例,展示如何在Node.js中手動實作JWT的簽名和驗證。簽名 (Sign):
const crypto = require('crypto'); const secret = 'mySecretKey'; const payload = { userId: '123456', username: 'john_doe' }; const header = { alg: 'HS256', // HMACSHA256 簽名算法 typ: 'JWT', }; // 將標頭和有效載荷轉為Base64編碼 const base64Header = Buffer.from(JSON.stringify(header)).toString('base64'); const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64'); // 將標頭和有效載荷以.相連 const dataToSign = `${base64Header}.${base64Payload}`; // 使用HMACSHA256算法簽名 const signature = crypto.createHmac('sha256', secret).update(dataToSign).digest('base64'); // 最後,將標頭、有效載荷和簽名組合成JWT const token = `${base64Header}.${base64Payload}.${signature}`; console.log('生成的Token:', token);
驗證 (Verify):
const crypto = require('crypto'); const secret = 'mySecretKey'; const token = '在這裡插入你的JWT'; const [header, payload, signature] = token.split('.'); const dataToSign = `${header}.${payload}`; // 使用HMACSHA256算法驗證簽名 const expectedSignature = crypto.createHmac('sha256', secret).update(dataToSign).digest('base64'); if (expectedSignature === signature) { // 簽名驗證成功 console.log('簽名驗證成功'); const decodedPayload = JSON.parse(Buffer.from(payload, 'base64').toString()); console.log('解碼後的資訊:', decodedPayload); } else { // 簽名驗證失敗 console.error('簽名驗證失敗'); }
這個範例演示了如何手動實作JWT的簽名和驗證,而不依賴
jsonwebtoken
套件。請確保在實際應用中將secret
替換為你自己的私鑰,並適當地處理錯誤。
這邊就可以看到一開始繪製的驗證流程圖,確實就是拿token中的payload內容,再用自己的密鑰簽一次,然後確認看看是不是簽出來跟JWT token中的 signature 一樣
payload被動過,或者signature是用不同私鑰偽造的,驗證時簽出來的signature 比對就會失敗
好的,我們已經徹底了解JWT的運作流程與機制後,來了解一下其他驗證方式
如果不使用JWT這種特定的驗證機制,你可以考慮以下幾種常見的身份驗證方式:
- 基本身份驗證(Basic Authentication):
- 在每個請求的標頭中包含使用者名稱和密碼的Base64編碼。伺服器將解碼它們,並進行身份驗證。
- 這種方式不是最安全的,因為Base64編碼可以相對容易地被解碼,所以通常搭配使用HTTPS。
- Digest 身份驗證(Digest Authentication):
- 這是基於基本身份驗證的一種更安全的選擇。它使用一種加密的挑戰-回應方式,使攻擊者更難破解使用者的密碼。
- OAuth 2.0:
- OAuth 2.0 是一種開放標準,它允許第三方應用獲取受保護的資源(如用戶資料)的訪問權限。
- 它支援多種流程,包括授權碼流程、隱式流程、密碼憑證流程等。
- OpenID Connect:
- OpenID Connect 是建立在OAuth 2.0之上的標準,用於身份驗證和授權。
- 它允許用戶使用現有的帳戶(如Google、Facebook等)來登入其他網站,而不需要在這些網站上創建新的帳戶。
- Session-based 身份驗證:
- 通過在伺服器上保持關於用戶的狀態來實現,每當用戶登入時,伺服器會創建一個唯一的識別碼(通常是Session ID),並將其存儲在Cookie中。
- 伺服器在後續請求中使用Session ID來識別用戶。
以上提到的是一些常見的身份驗證方式,每種方式都有其優缺點,選擇適合你的應用場景的方式非常重要。請注意,不同的場景可能需要不同的身份驗證方式,並且安全性應該是你考慮的首要因素。
JWT(JSON Web Tokens)相較於其他驗證方式具有一些優點和優勢:
優點:
- 輕量且可攜帶:JWT 是一個輕量且簡單的標準,可以輕易在網絡間傳輸。
- 跨平台和語言:由於 JWT 是基於JSON的開放標準,因此它在各種語言和平台上都有支持。
- 無需在伺服器上保留狀態:每個 JWT 都包含了足夠的資訊來驗證用戶的身份,因此伺服器無需在內部保留與用戶相關的狀態。
- 簽名驗證:JWT可以使用簽名來保護內容免受篡改,確保它來自可信的來源。
- 可以加密:除了簽名之外,JWT還可以使用加密機制,以保護內容不被第三方獲取。
- 可擴展:JWT的標頭允許添加自定義的屬性,這使得它可以被擴展以滿足特定需求。
缺點:
- 無法撤回:一旦生成了JWT,它的有效期開始運行,無法撤回或取消。
- 資料量較大:相對於一些其他驗證方式(如基本身份驗證),JWT可能會較大,尤其當內容較多時。
- 無法單獨撤銷單個Token:在傳統的基於Session的驗證中,伺服器可以單獨撤銷特定的Session,但對於JWT,伺服器無法單獨撤銷單個Token,除非更改密鑰或等待其過期。
- 不適合保管重要信息:儘管JWT可以簽名和加密,但它不適合用於保管敏感信息,因為這些信息可以被解碼。
總的來說,JWT提供了一種方便和靈活的方式來實現驗證,特別是在無狀態的API或分散式系統中。然而,它也有一些缺點,並且在特定情況下可能不適用。因此,在選擇驗證方式時,你應該根據你的應用場景和安全需求來進行選擇。
當使用JWT驗證機制時,有一些重要的事情需要特別注意:
- 保護私鑰:
- 確保你的私鑰保密且安全存放。不要將私鑰直接放在程式碼中或公開在版本控制系統中。
- 適當設置過期時間:
- 在生成JWT時,請為其設定一個適當的過期時間(exp)。這樣可以確保即使token被截取,也不會永遠有效。
- 不要在token中存儲敏感信息:
- 避免將敏感信息直接存儲在JWT中,即使它已經經過簽名或加密。最好只將非敏感的基本信息放在token中。
- 適當處理Refresh Token:
- 如果你使用Refresh Token來延長JWT的有效期,請確保這個機制是安全的,並定期檢查Refresh Token的有效性。
- 檢查簽名:
- 在驗證JWT時,確保簽名是有效的。只有當簽名驗證通過時,才能信任這個token。
- 保護免受中間人攻擊:
- 如果你使用HTTP而不是HTTPS,請注意可能會受到中間人攻擊。最好總是使用HTTPS。
- 適當處理過期token:
- 當JWT過期時,你需要適當地處理它。你可以選擇延長token的有效期(使用Refresh Token),或者要求用戶重新登入。
- 審查應用程式中的權限和許可權:
- 確保只有授予了相應權限的用戶可以訪問相應的資源。
- 記錄和監控:
- 監控你的應用程式,以偵測異常活動或異常的登入嘗試。設置適當的記錄以協助追蹤和調查問題。
- 避免重複使用token:
- 當你發現一個token被多次使用,這可能表示有人正在嘗試進行重放攻擊。確保每個token只能被使用一次。
總的來說,使用JWT時,請確保你瞭解並實施了相應的安全最佳實踐,以確保你的應用程式免受潛在的安全威脅。
綜合以上對話內容,以下是使用JWT(JSON Web Tokens)時的重點:
重點:
- JWT 簡介:
- JWT 是一種用於在網路上安全傳輸資訊的開放標準,通常用於驗證用戶的身份。
- JWT 結構:
- JWT 由三部分組成:標頭(Header)、有效載荷(Payload)和簽名(Signature)。
- 簽名驗證原理:
- JWT 通過將標頭和有效載荷進行簽名,來確保資訊的完整性和可信度。
- 私鑰保密:
- 確保私鑰保密且安全存放,避免直接放在程式碼或公開在版本控制系統中。
- 適當設置過期時間:
- 為JWT設定適當的過期時間,以保護資訊的有效性。
- 不要在token中存儲敏感信息:
- 避免將敏感信息直接存儲在JWT中,最好只放基本的非敏感信息。
- HTTPS 使用:
- 使用HTTPS 來保護免受中間人攻擊。
- 適當處理 Refresh Token:
- 如果使用Refresh Token來延長JWT的有效期,需確保安全,並定期檢查Refresh Token的有效性。
- 監控與記錄:
- 監控應用程式,記錄異常活動或異常的登入嘗試,協助解決問題。
- 避免重複使用token:
- 確保每個token只能被使用一次,避免重放攻擊。
結語:
使用JWT作為身份驗證機制是一種方便且靈活的方式,特別適用於無狀態的API或分散式系統。然而,它也有一些需要注意的地方,特別是在保護私鑰、適當設置過期時間等方面。請根據你的應用場景和安全需求來選擇合適的身份驗證方式。當適當地實施和監控時,JWT能夠提供一個有效的身份驗證解決方案。
本次對話內容記錄在這邊:
https://chat.openai.com/share/6ea45d2e-1b24-4b13-b241-43c4ec8d00c8
對於REST API的實行上,JWT是無狀態的這一點非常適合
不需要在伺服端保留資料,而JWT機制整體運作起來難度也相當低
整篇看下來確實是簡潔又安全的方式
我自己工作上也是從Session-based 身份驗證,在近一年轉為JWT驗證的方式
不過,我想起好像多年前,曾經有一些混蛋在FB做了一個「心理測驗」的遊戲
大概是要你打開F12,輸入指令或是把cookie的內容複製下來貼給他
然後這個「心理測驗」就會告訴你「測試結果」
再來,這些參與遊戲的人,就會被盜了,發給親友病毒連結或個資外洩等等
想想這種手法真的很混蛋。
像JWT身份驗證的方式,通常也都是把Token放在cookie或是localStorage
像localStorage 的話,
使用 localStorage.setItem('token','JWT token')
就可以儲存token
接著使用localStorage.getItem('token')
就可以取得剛剛設定的token
只要拿到了Token,對伺服器來說,就是合格的使用者
因此就能不用輸入任何帳號密碼,直接使用這個token使用你的帳號
當時實際的運作邏輯和詐騙原理我並不是很清楚(那時候我只是一個孩子啊)
不過目前的Facebook打開F12的話,也會在console中顯示這樣的訊息
說得很好沒錯,住手,你們這些麻瓜 (X)
好的!以上就是有關JWT的介紹,以及實作在nestJS上的範例
下一篇!要來介紹方便前後端測試API的神套件: swagger
測試app就不用使用postman或是ChatGPT說得直接用curl 去發送請求
詳細就下一篇再介紹了
啊對了題外話,ChatGPT對我的機器人驗證,後來變得寬鬆了
一來是驗證的遊戲變簡單了(或是人類解起來比較簡單)
也沒有每一題都問我了!大概放置一段時間才問我一次
開心開心,這個簡單跟快很多
就這樣!
我是喜歡使用JWT驗證的前端工程師一宵三筵,我們明天見~