Complete Mediation 完全仲裁原則
確保每次存取請求都經過嚴格安全驗證的核心機制
在現代資訊安全的設計哲學中,Complete Mediation(完全仲裁原則)是由 Jerome Saltzer 與 Michael Schroeder 於 1975 年在其經典論文《The Protection of Information in Computer Systems》中所提出的八大安全設計原則之一。這項原則看似直觀,卻往往是系統在面對複雜存取情境時最容易忽略的防線。一旦任何一次存取請求未經驗證便被放行,就可能成為攻擊者藉以突破整個防護體系的缺口。
本文將深入解析完全仲裁原則的核心概念、常見的違反情境、以及如何透過實際程式碼與架構設計確保每一次存取都受到嚴格管控,協助開發者在日常工程實踐中真正落實這項關鍵安全原則。
什麼是 Complete Mediation?核心概念解析
Complete Mediation(完全仲裁)的核心定義是:系統中的每一次對受保護資源的存取請求,都必須通過存取控制機制的完整驗證,不得因任何理由(如快取、效能考量或假設前次已驗證)而跳過這道程序。這與「一次驗證、終身信任」的思維完全相反。
Saltzer 與 Schroeder 在原始論文中以作業系統的檔案存取為例:當使用者第一次開啟檔案時,系統會驗證其權限;但若系統在後續操作中信賴了已快取的權限結果,卻未察覺使用者的存取權限已在此期間被撤銷,便構成了對此原則的違反。這種場景在現代 Web 應用程式、微服務架構與 API 設計中同樣普遍存在。
📌 原則核心三要素
- 每次(Every time):不存在「免驗證的第二次請求」,每一次存取皆需重新驗證。
- 每個主體(Every subject):無論是人類使用者、服務帳號或內部系統元件,皆需受到相同的驗證規則約束。
- 每個物件(Every object):所有受保護的資源,包含 API 端點、檔案、資料庫記錄,皆在保護範疇之內。
常見的違反情境:快取權限與信任假設的陷阱
違反完全仲裁原則的情況往往不是刻意為之,而是來自效能最佳化的過度妥協或對安全邊界的錯誤假設。以下是幾個高頻發生的違反情境,值得開發者特別留意。
情境一:快取存取令牌未即時失效(Token Caching Without Revocation Check)。許多系統為了降低對認證伺服器的請求壓力,會在應用層快取使用者的 JWT 或 Session 令牌的驗證結果。當管理員撤銷某位使用者的存取權限時,若應用程式仍信任快取中的舊驗證結果,就會讓已被撤銷權限的使用者在快取過期前繼續存取系統。
情境二:前端路由守衛取代後端驗證(Client-Side Only Authorization)。在單頁應用程式(SPA)中,開發者有時會僅在前端的路由守衛(Route Guard)中進行權限判斷,而後端 API 未做獨立驗證。攻擊者只需繞過前端限制直接呼叫 API,便可輕易取得未授權資料。後端永遠不應信任前端傳來的權限宣告。
情境三:微服務內部呼叫的信任漏洞(Inter-Service Trust Without Re-Authentication)。在微服務架構中,若服務 A 對服務 B 的呼叫被視為「內部可信任」而免除驗證,一旦服務 A 遭到入侵,攻擊者便可藉此橫向移動,存取服務 B 所保護的資源,造成大規模的安全事故。
架構設計最佳實踐:如何在系統中落實完全仲裁
落實完全仲裁原則需要從架構層面而非單點修補的角度出發。以下介紹幾項關鍵的設計策略,並搭配可執行的程式碼範例說明具體實作方式。
策略一:在每個 API 端點強制執行授權中介軟體(Per-Request Authorization Middleware)。以 Node.js(Express.js)為例,應在每個路由上明確掛載驗證與授權中介軟體,而非僅在應用程式入口點做一次性檢查。
// Node.js + Express.js 範例(適用 Express 4.x / 5.x)
// ✅ 正確做法:每個路由都獨立掛載 authenticate + authorize 中介軟體
const express = require('express');
const router = express.Router();
// 認證中介軟體:驗證 JWT 有效性(每次請求皆執行)
async function authenticate(req, res, next) {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized: Missing token' });
}
const token = authHeader.split(' ')[1];
try {
// 每次都向 Token Introspection Endpoint 或驗證服務確認令牌狀態
// 避免僅依賴本地快取的驗證結果
const payload = await verifyTokenWithRemoteCheck(token);
req.user = payload;
next();
} catch (err) {
return res.status(401).json({ error: 'Unauthorized: Invalid or revoked token' });
}
}
// 授權中介軟體:根據使用者角色決定是否允許存取特定資源
function authorize(requiredRole) {
return (req, res, next) => {
if (!req.user || !req.user.roles.includes(requiredRole)) {
return res.status(403).json({ error: 'Forbidden: Insufficient permissions' });
}
next();
};
}
// 每個端點明確指定所需的認證與授權(完全仲裁:無例外)
router.get(
'/admin/reports',
authenticate, // Step 1:驗證身份
authorize('admin'), // Step 2:驗證角色權限
async (req, res) => {
// Step 3:僅在通過以上兩層驗證後才執行業務邏輯
const reports = await getAdminReports();
res.json(reports);
}
);
router.get(
'/user/profile/:id',
authenticate,
authorize('user'),
async (req, res) => {
// 額外的物件層級存取控制(Object-Level Authorization)
// 確保使用者只能存取自己的資料,而非他人的
if (req.user.id !== req.params.id) {
return res.status(403).json({ error: 'Forbidden: Cannot access other user profile' });
}
const profile = await getUserProfile(req.params.id);
res.json(profile);
}
);
module.exports = router;
策略二:使用 Token Introspection 避免依賴本地快取的失效令牌。OAuth 2.0 的 Token Introspection(RFC 7662)允許資源伺服器在每次請求時向授權伺服器查詢令牌的當前狀態,是實踐完全仲裁的標準化手段。
// Token Introspection 實作範例(Node.js,符合 RFC 7662)
// 適用於需要即時偵測已撤銷令牌的高安全性場景
const axios = require('axios');
async function verifyTokenWithRemoteCheck(token) {
const introspectionEndpoint = process.env.AUTH_INTROSPECTION_URL;
// 例如:'https://auth.example.com/oauth2/introspect'
const response = await axios.post(
introspectionEndpoint,
new URLSearchParams({ token }),
{
auth: {
username: process.env.RESOURCE_SERVER_CLIENT_ID,
password: process.env.RESOURCE_SERVER_CLIENT_SECRET,
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}
);
const { active, sub, roles, exp } = response.data;
// 令牌必須處於 active 狀態,否則視為無效(含已撤銷、已過期)
if (!active) {
throw new Error('Token is inactive or revoked');
}
// 額外確認過期時間(雙重保護)
if (exp && Date.now() / 1000 > exp) {
throw new Error('Token has expired');
}
return { id: sub, roles };
}
// ⚠️ 效能考量:若 Introspection 對效能影響過大,
// 可搭配極短 TTL 的分散式快取(如 Redis,TTL ≤ 30s)
// 但需確保撤銷事件能主動使快取失效(Cache Invalidation on Revocation)
策略三:微服務間採用 Zero Trust 網路模型與 mTLS 相互驗證。在微服務環境中,應採用零信任架構(Zero Trust Architecture),要求每個服務在呼叫其他服務時都必須提供可驗證的身份憑證,並由被呼叫方進行驗證,而非依賴網路位置(如「同一個內部網段」)來建立信任。
# 使用 Istio Service Mesh 實施服務間強制 mTLS 驗證
# 適用於 Kubernetes 環境,Istio 版本 1.15+
# 1. 設定 PeerAuthentication:強制所有服務間通訊使用 mTLS
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT # 嚴格模式:拒絕所
留言
張貼留言