API 安全全攻略:從漏洞風險到最佳實踐
更新日期: 2025 年 3 月 4 日
本文為 Web 架構進化史 系列文,第 5 篇:
當你的 API 成為駭客的遊樂場
想像你蓋了一棟豪宅(後端服務),卻忘記裝鎖(安全措施)——任何人都能隨意進出、搬走家具甚至放火燒屋。這就是未受保護的 API 面臨的風險。
2021 年 Facebook 因 API 漏洞外洩 5.33 億用戶資料,正是血淋淋的教訓。
本文將帶新手理解:如何用三道鎖(認證、授權、防護)守護你的 API 大門,從基礎的 API Key 到進階的 OAuth2.0,搭配實戰範例全面解析。
API 安全的三道防線
在現代應用程式開發中,API(應用程式介面)已成為各種服務之間溝通的橋樑。
然而,開放的 API 也成為攻擊者的目標,因此 API 安全至關重要。
API 安全機制主要建立在三道防線之上,分別是 認證(Authentication)、授權(Authorization)、防護(Protection)。
這三道防線相輔相成,確保 API 只允許合法的使用者執行合法的操作,並抵禦潛在的攻擊。
認證(Authentication):你是誰?
認證的目標是確認請求 API 的使用者身份,類似於我們進入某個場所時需要出示身份證件。
這個過程的核心問題是:「如何證明『你是你』?」
當使用者嘗試存取 API 時,系統會要求他們提供身份憑證,例如:
- 使用者名稱 + 密碼
- API 金鑰(API Key)
- 身分驗證令牌(如 JWT、OAuth Token)
這些憑證將由伺服器驗證,以確保使用者的身份是可信的。
如果認證失敗,API 會拒絕提供服務。
授權(Authorization):你能做什麼?
通過認證後,系統還需要確認使用者是否擁有執行特定操作的權限,這就是授權(Authorization)。
授權的概念類似於飯店房卡,即使你進入了飯店(通過認證),但你的房卡可能只能開啟特定房間,而無法進入其他住客的房間。
授權的核心問題是:「你有權限執行此操作嗎?」
常見的授權方式包括:
- 角色與權限(Role-Based Access Control, RBAC):根據使用者的角色來決定他們能夠存取哪些資源,例如「管理員」可以查看和修改所有資料,而「一般使用者」只能查看自己的資料。
- 基於範圍的授權(Scopes):使用 OAuth2 時,開發者可以定義不同的範圍(Scope),讓使用者授權特定操作,例如「只允許讀取資料,但不能修改」。
- 資源擁有權(Ownership-based Access):使用者只能存取自己創建的資源,而不能存取其他人的資源。
如果授權檢查未通過,API 會回應「403 Forbidden」,拒絕執行請求。
防護(Protection):如何抵禦惡意行為?
即使 API 已經實施了認證與授權,仍然可能遭受各種攻擊,例如暴力破解、惡意請求或 API 金鑰洩露等問題。
因此,防護機制的目標是阻擋異常請求,確保 API 不會被濫用。
防護的核心問題是:「如何阻擋異常請求?」
常見的防護措施包括:
- 速率限制(Rate Limiting):限制 API 請求的頻率,例如「同一 IP 每分鐘最多可發送 100 次請求」,以防止 DDoS 攻擊或暴力破解。
- IP 白名單與黑名單:允許特定 IP 存取 API,或封鎖可疑的 IP 地址。
- Web 應用防火牆(WAF):監控和攔截惡意請求,例如 SQL 注入、XSS 攻擊等。
- 記錄與監控(Logging & Monitoring):透過日誌與監控系統偵測異常行為,並在發現可疑活動時發出警報。
認證機制:三大主流方案解析
目前主流的 API 認證方案主要包括 API Key、JWT(JSON Web Token)、OAuth 2.0。
這些方案各有適用場景與優缺點,開發者應根據需求選擇合適的機制。
API Key:最簡單的通行證
運作原理
API Key 是最基本的身份驗證方式,類似於一把「通行證」,只要持有這把鑰匙,就可以存取 API。
使用 API Key 進行身份驗證的流程如下:
- 伺服器產生一個唯一的 API Key,並分發給使用者。
- 使用者在發送 API 請求時,將 API Key 附加在標頭(Header)或查詢參數(Query Parameter)中,例如:
curl -H "X-API-Key: abc123" https://api.example.com/data
- 伺服器驗證 API Key 是否有效,決定是否允許請求通過。
適用場景
- 內部系統或機器對機器(M2M)溝通
- 快速原型開發
缺點
- 金鑰洩露風險高(例如,若 API Key 被硬編碼在程式碼中,可能被攻擊者竊取)
- 無法區分不同使用者的權限
JWT(JSON Web Token):自帶身份證的加密令牌
運作原理
JWT 是一種無狀態(Stateless)的身份驗證機制,它將身份資訊加密並存放在令牌(Token)內。
使用者無需每次請求都與伺服器驗證身份,而是直接攜帶 JWT 令牌即可。
- 使用者登入後,伺服器生成一個 JWT,並回傳給用戶端。
- 用戶端在後續請求時,將 JWT 放入
Authorization: Bearer <token>
標頭中。 - 伺服器透過解碼 JWT 來驗證身份,無需查詢資料庫。
JWT 結構拆解
JWT 由三部分組成,並以 .
進行分隔:
Header(加密算法) . Payload(資料) . Signature(簽章)
它的三部分結構讓 API 能夠驗證請求者的身份,同時確保數據未被篡改:
- Header(標頭):定義加密演算法與令牌類型
- Payload(有效負載):存放用戶資訊與權限聲明
- Signature(簽章):確保令牌未被篡改
這種方式可以讓伺服器在不依賴資料庫查詢的情況下驗證使用者身份,因此減少了伺服器負擔,但同時需要妥善管理 JWT 的過期時間與密鑰,以確保安全性。
例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
優勢
- 無狀態(Stateless):減輕伺服器負擔
- 可攜帶自訂資料(如用戶角色、過期時間)
風險警示
- 令牌過期時間過長可能導致被盜用
- 敏感資料不應存放在 Payload(因為它可以被 Base64 解碼)
OAuth2.0:第三方授權的黃金標準
OAuth 2.0 是什麼?
OAuth 2.0(Open Authorization 2.0)是一種開放標準的授權協議,用於讓使用者能夠安全地授權第三方應用程式存取自己的資源,而不需要直接提供帳號密碼。
簡單來說,OAuth 2.0 允許應用程式 “代表用戶” 存取受保護的資源,例如:
- 允許一個應用程式使用 Google 帳戶登入(例如「使用 Google 登入 Facebook」)
- 讓第三方應用存取 GitHub 的私人儲存庫
- 讓手機 App 授權 Spotify 播放用戶的播放清單
這種方式既能 保護使用者的帳號密碼,又能控制授權範圍,確保第三方應用只能存取必要的資料。
OAuth 2.0 的核心概念
OAuth 2.0 主要涉及 四個角色:
- 資源擁有者(Resource Owner)
- 也就是 使用者,擁有某個資源(例如 Google Drive 檔案或 Facebook 帳戶)。
- 客戶端(Client)
- 需要存取資源的應用程式,例如一個想要讀取使用者 Google Drive 檔案的筆記 App。
- 授權伺服器(Authorization Server)
- 由資源擁有者的服務提供者(如 Google、Facebook) 負責驗證使用者身份並發放授權(Access Token)。
- 資源伺服器(Resource Server)
- 存放受保護資源的伺服器,例如 Google Drive API、Facebook API。
OAuth 2.0 授權流程(Authorization Flow)
OAuth 2.0 的基本流程如下:
- 使用者發起授權請求
- 客戶端(應用程式)要求使用者授權,跳轉到授權伺服器(例如 Google 登入頁面)。
- 使用者同意授權
- 使用者在授權頁面上確認應用程式的請求,允許存取指定的資源。
- 授權伺服器發放授權碼(Authorization Code)
- 如果使用者同意,授權伺服器會返回一個短期有效的授權碼(Authorization Code)給客戶端。
- 客戶端用授權碼換取 Access Token
- 客戶端使用授權碼向授權伺服器請求存取令牌(Access Token)。
- 客戶端使用 Access Token 存取資源
- 之後,客戶端就能帶著 Access Token 向資源伺服器請求受保護的資源(例如存取 Google Drive 檔案)。
簡單比喻:
這就像你要幫朋友取一封存放在郵局的信件,流程如下:
- 你(應用程式)去找你的朋友(使用者),請求授權。
- 朋友(使用者)寫了一張授權便條(授權碼)給你。
- 你拿著便條去郵局(授權伺服器)換取正式的取件憑證(Access Token)。
- 你用這張取件憑證(Access Token)成功領取信件(受保護資源)。

OAuth 2.0 的四種授權模式
OAuth 2.0 提供 四種不同的授權模式,適用於不同的應用場景:
1. 授權碼模式(Authorization Code)— 最安全的方式
這是最常見、最安全的 OAuth2.0 授權方式,適用於有後端的 Web 應用。
流程如下:
- 應用程式跳轉到授權頁面
https://accounts.google.com/o/oauth2/auth?
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
response_type=code&
scope=email profile
- 使用者登入並授權(Google 顯示授權頁面,使用者點擊「允許」)
- Google 回傳授權碼(Authorization Code)到應用程式的 Redirect URI
- 應用程式用授權碼換取 Access Token
授權碼(Authorization Code) vs. 存取令牌(Access Token)
在 OAuth 2.0 的授權碼模式中,確實授權碼(Authorization Code) 會透過 redirect_uri
直接傳回給應用程式,例如:
https://your-app.com/callback?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7
但是,這裡有 兩個關鍵點:
- 授權碼(Authorization Code)不是 Access Token,它本身不能直接存取 API 資源,只是一個短期有效(通常 10 分鐘內有效)的「交換憑證」。
- 授權碼只能被「真正的應用程式伺服器」交換 Access Token,因為交換過程需要
client_secret
,這個client_secret
只存放在伺服器端,瀏覽器無法拿到。
為什麼授權碼暴露沒關係?
授權碼暴露在 redirect_uri
看起來像是被攔截的風險,但其實有 三個保護機制 確保它不會被濫用:
(1) 授權碼是一次性使用的
授權碼只能用 一次,如果攻擊者拿到了,也沒辦法重複使用它。
當合法的應用程式用這個授權碼交換 Access Token 後,這個授權碼就會失效。
(2) 授權碼只能配合「Client ID + Client Secret」交換 Token
應用程式要交換 Access Token,除了提供授權碼,還需要提供「Client ID + Client Secret」,請求格式如下:
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&code=AUTHORIZATION_CODE
&redirect_uri=YOUR_REDIRECT_URI
&grant_type=authorization_code
但這個 Client Secret 只存在伺服器端,瀏覽器根本無法存取,攻擊者如果只偷到 code
,但沒有 client_secret
,就無法換取 Access Token。
(3) PKCE(Proof Key for Code Exchange)進一步防止攻擊
現在,OAuth 2.0 標準建議所有應用程式使用 PKCE(Proof Key for Code Exchange)來保護授權碼。
PKCE 的機制如下:
- 應用程式在請求授權碼時,會隨機產生一個
code_verifier
,然後用哈希演算法計算出code_challenge
。 - 授權伺服器會存儲
code_challenge
,但不會存儲code_verifier
。 - 當應用程式交換 Access Token 時,必須提供
code_verifier
,授權伺服器會用它來驗證是否符合code_challenge
。
這樣,即使攻擊者竊取了授權碼,他也無法計算出對應的 code_verifier
,所以還是換不到 Access Token。
請求授權碼時加上 PKCE:
https://accounts.google.com/o/oauth2/auth?
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
response_type=code&
scope=email profile&
code_challenge=hashed_value&
code_challenge_method=S256
用授權碼換取 Access Token 時,也要提供 code_verifier
:
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR_CLIENT_ID
&code=AUTHORIZATION_CODE
&redirect_uri=YOUR_REDIRECT_URI
&grant_type=authorization_code
&code_verifier=original_random_value
2. 隱式授權模式(Implicit)— 適用於純前端應用
適用於單頁應用(SPA),如 Vue、React 前端應用程式,但不夠安全,因為 Access Token 直接存放在瀏覽器 URL,容易被竊取。
因此,這種模式現在不再推薦使用,建議改用 PKCE(Proof Key for Code Exchange) 增強安全性。
為什麼「隱式模式(Implicit Flow)」才是不安全的?
在 OAuth 2.0 隱式模式(Implicit Flow),應用程式不需要交換授權碼,而是直接從 URL 中拿到 Access Token,例如:
https://your-app.com/callback#access_token=ya29.a0AfH6...
但這樣有 三個大問題:
- Access Token 直接暴露在 URL 中,容易被竊取(例如透過惡意 JavaScript 讀取)。
- Access Token 不能加密保護,只要被攔截到,就可以直接用來存取 API。
- Access Token 通常有較長的有效期,一旦被竊取,攻擊者可長時間使用。
由於這些風險,現在 OAuth 2.0 已經不推薦使用隱式模式,前端應用應該改用「授權碼模式 + PKCE」。
3. 用戶端憑證模式(Client Credentials)— 伺服器之間的授權
適用於 機器對機器(M2M) 溝通,例如後端服務存取另一個 API。
因為沒有使用者介入,因此只需要發送 Client ID 和 Client Secret 來獲取 Access Token。
curl -X POST "https://auth.example.com/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"
4. 密碼模式(Password Credentials)— 低安全性的授權方式
這種方式直接讓使用者在應用程式中輸入 帳號密碼,然後由伺服器換取 Access Token。
這種方式的 安全性較低,因為應用程式會直接存取使用者的密碼,只適用於完全信任的應用程式(如公司內部系統)。
現在不推薦使用此模式,建議使用「授權碼模式」。
授權模式 | 適用場景 | 安全等級 |
---|---|---|
Authorization Code(授權碼模式) | 有後端的 Web 應用 | 高 |
Implicit(隱式授權模式) | 純前端(SPA, JavaScript) | 中 |
Client Credentials(用戶端憑證模式) | 伺服器對伺服器 | 高 |
Password Credentials(密碼模式) | 受信任的第一方應用 | 低 |
防護策略:築起 API 的萬里長城
速率限制(Rate Limiting)
作用:防止暴力破解與 DDoS 攻擊
實作方法:
- 固定窗口:每分鐘允許 100 次請求
- 滑動窗口:動態計算近期請求量
- 依客戶端區分:IP 地址、API Key、用戶 ID
HTTP 回應提示:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
輸入驗證(Input Validation)
原則:永遠不要信任客戶端傳來的資料!
常見攻擊與防禦:
攻擊類型 | 防禦方法 | 程式碼範例(Python) |
---|---|---|
SQL 注入 | 參數化查詢 | cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) |
XSS | 過濾 HTML 標籤 | from bleach import clean; clean(user_input) |
檔案上傳漏洞 | 檢查副檔名與 MIME 類型 | if file.content_type not in ['image/png', 'image/jpeg']: raise error |
進階防護:深度防禦策略
- HTTPS 強制加密:避免敏感資料明文傳輸
- CORS 嚴格設定:只允許信任的網域存取
- 日誌監控與告警:即時發現異常行為模式
實戰演練:用 Express.js 實作安全 API
情境:保護一個「取得用戶資料」的 API
步驟分解:
- 安裝依賴:JWT 驗證、速率限制套件
npm install express jsonwebtoken express-rate-limit
- 實作 JWT 中間件
const jwt = require('jsonwebtoken');
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: '缺少令牌' });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ error: '無效令牌' });
}
}
- 設定速率限制
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分鐘
max: 100, // 每個 IP 限 100 次請求
message: '請求過於頻繁,請稍後再試'
});
app.use('/api/', limiter);
- 輸入驗證範例
app.get('/api/user/:id', authenticate, (req, res) => {
const userId = parseInt(req.params.id);
if (isNaN(userId)) {
return res.status(400).json({ error: '無效的用戶 ID' });
}
// 查詢資料庫並回傳...
});
總結:安全不是功能,而是責任
API 安全就像保險——平時覺得麻煩,出事時才知不可或缺。記住三大心法:
- 最小權限原則:只授予必要權限
- 縱深防禦:多層次阻擋攻擊
- 持續更新:定期檢視與修補漏洞