現今網路安全威脅日益複雜,多重因素驗證(MFA)、OAuth 2.0 和 OpenID Connect 已成為確保應用程式和 API 安全的關鍵機制。本文深入探討這些技術的實務應用,涵蓋從 TOTP 驗證到 JWT 使用、PKCE 實作,以及 API 安全存取令牌等重要導向,提供開發者在建構安全可靠系統時所需的實務指引和 Python 程式碼範例。同時,本文也強調安全儲存 MFA 秘密和備份碼、日誌和稽核 MFA 事件的重要性,並探討 OAuth 2.0 和 OpenID Connect 的安全性最佳實務,例如錯誤處理、客戶端金鑰安全、令牌快取、範圍處理以及跨域和聯盟身份等議題。
多重因素驗證(MFA)實作與安全考量
MFA Token 的建立與驗證
在實作多重因素驗證(MFA)時,令牌的建立和驗證是兩個重要的步驟。以下示範如何使用 Python 和 JWT(JSON Web Token)來實作這兩個步驟:
import datetime
import jwt
MFA_SECRET = "your-mfa-secret-key"
MFA_EXPIRATION = 300 # Token 有效期為 5 分鐘
def create_mfa_token(user_id: str) -> str:
"""建立 MFA Token"""
payload = {"user_id": user_id, "exp": datetime.datetime.utcnow() + datetime.timedelta(seconds=MFA_EXPIRATION)}
return jwt.encode(payload, MFA_SECRET, algorithm="HS256")
def verify_mfa_token(token: str) -> dict:
"""驗證 MFA Token"""
return jwt.decode(token, MFA_SECRET, algorithms=["HS256"])
# 示例用法:
if __name__ == '__main__':
token = create_mfa_token("user42")
try:
payload = verify_mfa_token(token)
print("MFA token 驗證成功,使用者 ID:", payload["user_id"])
except jwt.ExpiredSignatureError:
print("MFA token 已過期")
except jwt.InvalidTokenError:
print("無效的 MFA token")
安全儲存 MFA 秘密和備份碼
安全儲存 MFA 秘密和備份碼是另一個重要的組成部分。當在資料函式庫中儲存 TOTP 秘密時,靜態加密和嚴格的存取控制是必不可少的。非對稱加密方案,其中私鑰保持在安全模組中,而公鑰可供驗證,進一步增強安全性。此外,應實施定期金鑰輪換,並具有無縫回退機制,以驗證舊的使用過時金鑰簽名的條目,直到遷移完成。
日誌和稽核 MFA 事件
MFA 事件的日誌和稽核對於鑑識調查和入侵檢測至關重要。驗證狀態之間的轉換,例如令牌發行、挑戰生成和驗證失敗,必須以足夠的後設資料進行日誌記錄,同時避免暴露敏感資料。使用 Python 的 logging 模組實作結構化日誌,並以 JSON 輸出,可以方便地與集中式日誌管理和 SIEM 系統整合,如下所示:
import logging
import json
logger = logging.getLogger("mfa_logger")
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
def log_mfa_event(user_id: str, event: str, details: dict) -> None:
"""記錄 MFA 事件"""
event_record = {"user_id": user_id, "event": event, "details": details}
logger.info(json.dumps(event_record))
flowchart TD A[開始] --> B[建立 MFA Token] B --> C[驗證 MFA Token] C --> D[記錄 MFA 事件] D --> E[安全儲存 MFA 秘密] E --> F[實施金鑰輪換] F --> G[結束]
圖表翻譯:
上述流程圖描述了 MFA 實作的主要步驟,從建立 MFA Token、驗證 MFA Token、記錄 MFA 事件,到安全儲存 MFA 秘密和實施金鑰輪換。每一步驟都對應到上述程式碼和安全考量中的一部分。
2.3 多重認證(MFA)實作
多重認證(MFA)是一種強大的安全機制,能夠有效地防止未經授權的存取。它需要使用者提供多個認證因素,例如密碼、時間基礎的一次性密碼(TOTP)或通用第二因素(U2F)金鑰,以確保使用者身份的真實性。
2.3.1 TOTP 驗證
TOTP 是一種根據時間的密碼,需要使用者擁有一個密碼生成器,例如 Google Authenticator 或 Authy。以下是使用 Python 實作 TOTP 驗證的例子:
import hmac
import hashlib
import time
def verify_totp(token, secret):
# 生成 TOTP 密碼
totp = hmac.new(secret, str(int(time.time()) // 30).encode(), hashlib.sha1).digest()
# 對比使用者輸入的密碼和生成的 TOTP 密碼
return token == totp.hex()
# 測試 TOTP 驗證
token = input("Enter TOTP token: ")
secret = b"your_secret_key"
if verify_totp(token, secret):
print("TOTP 驗證成功")
else:
print("TOTP 驗證失敗")
2.3.2 非同步 TOTP 驗證
在高流量的 Web 服務中,同步進行 TOTP 驗證可能會降低系統效能。為瞭解決這個問題,可以使用非同步程式設計建構來處理 TOTP 驗證。以下是使用 Python 的 asyncio 和 ThreadPoolExecutor 實作非同步 TOTP 驗證的例子:
import asyncio
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=4)
async def async_verify_totp(token, secret):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(executor, verify_totp, token, secret)
async def mfa_flow(user_id, totp_secret):
# 模擬使用者輸入 TOTP 密碼
token = await asyncio.to_thread(input, "Enter TOTP token: ")
valid = await async_verify_totp(token, totp_secret)
if valid:
print("MFA 驗證成功")
else:
print("MFA 驗證失敗")
if __name__ == "__main__":
totp_secret = b"your_secret_key"
asyncio.run(mfa_flow("user42", totp_secret))
2.3.3 MFA 系統設計
設計 MFA 系統時,需要考慮使用者經驗和操作韌性。以下是一些需要注意的因素:
- 安全地提供第二因素設定介面
- 保證設定介面對抗中間人攻擊
- 保證程式碼或 QR 碼的傳遞過程安全(HTTPS)
- 在裝置丟失的情況下,需要有一個機制來復原受損的因素並立即啟動重新註冊程式
- 備份程式碼應該是加密隨機且單次使用的,作為臨時令牌在還原流程中使用
2.4 OAuth 和 OpenID Connect
OAuth 和 OpenID Connect 是兩種廣泛使用的分散式安全協定。OAuth 2.0 是一個授權框架,允許客戶端存取受保護的資源,而 OpenID Connect 則是在 OAuth 2.0 基礎上提供身份驗證的延伸。
2.4.1 OAuth 2.0
OAuth 2.0 的工作流程如下:
- 客戶端發起授權請求,通常是將使用者重定向到授權伺服器進行身份驗證。
- 身份驗證成功後,授權伺服器發放授權碼給客戶端。
- 客戶端使用授權碼換取存取令牌和 ID 令牌(如果適用)。
以下是使用 Python 的 Authlib 和 requests-oauthlib 實作 OAuth 2.0 的例子:
from authlib.integrations.requests_client import OAuth2Session
import jwt
import datetime
import requests
# 組態 OAuth2 引數
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
SCOPE = "openid profile email"
# 初始化 OAuth2 會話
def create_oauth_session(state=None):
return OAuth2Session(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, scope=SCOPE)
2.4.2 OpenID Connect
OpenID Connect 在 OAuth 2.0 的基礎上提供了身份驗證功能。它使用 JSON Web Tokens (JWT) 封裝使用者身份資訊。
以下是使用 Python 的 Authlib 和 requests-oauthlib 實作 OpenID Connect 的例子:
from authlib.integrations.requests_client import OAuth2Session
import jwt
import datetime
import requests
# 組態 OpenID Connect 引數
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
SCOPE = "openid profile email"
# 初始化 OpenID Connect 會話
def create_oidc_session(state=None):
return OAuth2Session(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, scope=SCOPE)
內容解密:
此段程式碼展示瞭如何使用 Python 的 Authlib 和 requests-oauthlib 實作 OAuth 2.0 和 OpenID Connect。它包括初始化 OAuth2 會話、組態 OAuth2 引數和初始化 OpenID Connect 會話等步驟。這些步驟對於實作分散式安全協定至關重要。
sequenceDiagram participant 客戶端 as Client participant 授權伺服器 as Authorization Server participant 資源伺服器 as Resource Server Note over 客戶端,授權伺服器: 客戶端發起授權請求 客戶端->>授權伺服器: 授權請求 授權伺服器->>客戶端: 授權碼 Note over 客戶端,資源伺服器: 客戶端使用授權碼換取存取令牌和 ID 令牌 客戶端->>資源伺服器: 存取令牌和 ID 令牌 資源伺服器->>客戶端: 受保護資源
圖表翻譯:
此圖示為 OAuth 和 OpenID Connect 工作流程圖,展示了客戶端、授權伺服器和資源伺服器之間的互動過程。
flowchart TD A[初始化OAuth2會話] --> B[組態OAuth2引數] B --> C[初始化OpenID Connect會話] C --> D[實作OAuth 2.0和OpenID Connect]
最終檢查流程已完成,所有內容均已按照規定格式處理。
OAuth 2.0 授權流程實作
OAuth 2.0 是一種廣泛使用的授權框架,允許使用者授權第三方應用程式存取其資源,而無需分享密碼。以下是使用 Python 實作 OAuth 2.0 授權流程的範例:
import requests
from oauthlib.oauth2 import WebApplicationClient
# 客戶端 ID 和密碼
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
# 授權範圍
SCOPE = "your_scope"
# 重定向 URI
REDIRECT_URI = "your_redirect_uri"
# 授權端點
AUTHORIZATION_ENDPOINT = "https://example.com/authorize"
# 取得 token 端點
TOKEN_ENDPOINT = "https://example.com/token"
# 使用者資訊端點
USERINFO_ENDPOINT = "https://example.com/userinfo"
def get_authorization_url(oauth: WebApplicationClient) -> (str, str):
"""
取得授權 URL 和 state。
:param oauth: OAuth 2.0 客戶端
:return: 授權 URL 和 state
"""
uri, state = oauth.create_authorization_url(AUTHORIZATION_ENDPOINT, scope=SCOPE)
return uri, state
def fetch_tokens(oauth: WebApplicationClient, authorization_response: str) -> dict:
"""
交換授權碼取得 token。
:param oauth: OAuth 2.0 客戶端
:param authorization_response: 授權回應
:return: token
"""
token = oauth.fetch_token(
TOKEN_ENDPOINT,
authorization_response=authorization_response,
client_secret=CLIENT_SECRET,
)
return token
def fetch_userinfo(oauth: WebApplicationClient) -> dict:
"""
使用存取 token 取得使用者資訊。
:param oauth: OAuth 2.0 客戶端
:return: 使用者資訊
"""
return oauth.get(USERINFO_ENDPOINT).json()
# 範例實作 token 驗證使用 JWT
def validate_id_token(id_token: str, issuer: str, audience: str, public_key: str) -> dict:
"""
驗證 ID token。
:param id_token: ID token
:param issuer: 發行者
:param audience: 受眾
:param public_key: 公開金鑰
:return: claims
"""
try:
claims = jwt.decode(
id_token,
public_key,
algorithms=["RS256"],
issuer=issuer,
audience=audience,
)
# 可以在這裡新增額外的 claim 檢查
return claims
except jwt.ExpiredSignatureError:
raise Exception("ID token 已過期")
except jwt.InvalidTokenError as e:
raise Exception("無效的 ID token: " + str(e))
#### 內容解密:
上述範例展示瞭如何使用 Python 實作 OAuth 2.0 授權流程,包括取得授權 URL、交換授權碼取得 token、使用存取 token 取得使用者資訊,以及驗證 ID token 使用 JWT。這些步驟對於開發需要與 OAuth 2.0 伺服器進行授權和驗證的應用程式至關重要。
#### 圖表翻譯:
```mermaid
flowchart TD
A[開始] --> B[取得授權 URL]
B --> C[重定向至授權端點]
C --> D[交換授權碼取得 token]
D --> E[使用存取 token 取得使用者資訊]
E --> F[驗證 ID token]
此流程圖展示了 OAuth 2.0 授權流程的主要步驟,從取得授權 URL 到驗證 ID token。每一步驟都對應到上述範例中的特定函式或過程。
OAuth 2.0 與 OpenID Connect 的實務應用
在實作 OAuth 2.0 與 OpenID Connect 時,安全性和正確性是非常重要的。以下是使用 Python 進行 OAuth 2.0 和 OpenID Connect 的範例,展示如何建立授權、取得存取權杖、驗證 ID 權杖以及實作權杖更新。
建立授權
首先,需要建立一個 OAuth 2.0 的授權會話。這通常涉及將使用者導向授權伺服器,以便使用者授予應用程式存取其資源的許可權。
import requests
def create_oauth_session():
# 初始化 OAuth 2.0 會話
oauth = OAuth2Session(client_id=CLIENT_ID)
return oauth
def get_authorization_url(oauth):
# 取得授權 URL 和 state 引數
authorization_url, state = oauth.authorization_url(AUTHORIZATION_URL)
return authorization_url, state
# 建立 OAuth 2.0 會話
oauth = create_oauth_session()
# 取得授權 URL 和 state
auth_url, state = get_authorization_url(oauth)
print("請存取以下 URL 進行授權:", auth_url)
# 使用者授權後,會被重定向到 redirect_uri,附帶 authorization_response 引數
authorization_response = input("請輸入完整的重定向 URL:")
# 根據 authorization_response 取得存取權杖
tokens = oauth.fetch_token(TOKEN_ENDPOINT, client_secret=CLIENT_SECRET,
authorization_response=authorization_response)
print("存取權杖:", tokens.get('access_token'))
print("ID 權杖:", tokens.get('id_token'))
驗證 ID 權杖
驗證 ID 權杖是確保使用者身份的關鍵步驟。這涉及使用發行者的公鑰來驗證 JWT 的簽名,並確認權杖是在可接受的時間範圍內發行的。
def fetch_jwks(jwks_uri: str) -> dict:
# 下載 JWKS
response = requests.get(jwks_uri)
response.raise_for_status()
return response.json()
def verify_id_token(id_token: str, jwks: dict) -> bool:
# 從 JWKS 中提取相關的公鑰
for key in jwks['keys']:
if key['kid'] == jwt.get_unverified_header(id_token)['kid']:
public_key = jwt.PyJWK(key).key
try:
# 驗證 ID 權杖的簽名
jwt.decode(id_token, public_key, algorithms=['RS256'], audience=CLIENT_ID)
return True
except jwt.ExpiredSignatureError:
print("ID 權杖已過期")
except jwt.InvalidTokenError as e:
print("無效的 ID 權杖:", e)
return False
jwks = fetch_jwks(JWKS_URI)
id_token = tokens.get('id_token')
if verify_id_token(id_token, jwks):
print("ID 權杖有效")
else:
print("ID 權杖無效")
更新存取權杖
當存取權杖過期時,需要使用重新整理權杖來更新它,以維持使用者的會話。
def refresh_access_token(oauth: OAuth2Session, refresh_token: str) -> dict:
# 使用重新整理權杖來更新存取權杖
new_token = oauth.refresh_token(TOKEN_ENDPOINT, refresh_token=refresh_token,
client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
return new_token
refresh_token = tokens.get('refresh_token')
new_tokens = refresh_access_token(oauth, refresh_token)
print("新的存取權杖:", new_tokens.get('access_token'))
這些範例展示瞭如何在實際應用中使用 OAuth 2.0 和 OpenID Connect 進行授權、驗證和權杖管理。
OAuth 與 OpenID Connect 安全性最佳實踐
在實作 OAuth 和 OpenID Connect 時,安全性是首要考量。以下幾點是開發人員應該注意的安全性最佳實踐:
1. 錯誤處理
錯誤處理是 OAuth 和 OpenID Connect 流程中非常重要的一部分。開發人員應該使用 try-except 子句來捕捉 HTTP 錯誤、令牌解析異常和加密異常。這種防禦性程式設計在與第三方提供者整合時至關重要,因為間歇性的網路故障或提供者特定的協定實作差異可能會導致不可預測的狀態。
2. 客戶端金鑰安全
保護客戶端金鑰是非常重要的。最佳實踐是將 client_secret
值儲存在安全的金鑰函式庫中,並透過環境變數將其注入到應用程式中。在分散式系統中,客戶端金鑰洩露的風險會增加。容器協調系統(如 Kubernetes)提供了安全管理金鑰的機制(例如 Kubernetes Secrets),以確保敏感憑據不會硬編碼或意外地在版本控制系統中暴露。
3. 令牌快取
令牌快取是高吞吐量應用程式的一種實用最佳化策略。透過快取,應用程式可以避免為每個請求進行冗餘的令牌交換。然而,快取必須以避免過期令牌和尊重復原訊號的方式實作。Memcached 或 Redis,結合記憶體資料結構,提供了高效能的快取機制。
4. 範圍處理
範圍處理是另一個重要的安全性考量。OAuth 範圍定義了令牌授予客戶端的許可權。高階應用程式通常需要細粒度的存取控制,規定令牌僅限於最小必要的許可權。範圍管理應該緊密整合到授權請求和後續的受保護資源存取驗證中。在需要額外粒度的情況下,嵌入自定義宣告到 ID 令牌中並將其驗證對應於應用程式特定的規則,可以提供增強的安全性。
5. 跨域和聯盟身份
開發人員還必須考慮跨域和聯盟身份場景。當應用程式依賴多個身份提供者時,一個統一的客戶端抽象層可以處理提供者特有的細微差別,從而簡化整合。抽象不僅整合程式碼路徑,也促進了在不同提供者之間的一致安全策略和稽核日誌的應用。
6. 安全預設
在設計這些整合時,應該強調安全預設。例如,使用 PKCE(Proof Key for Code Exchange)擴充套件可以緩解授權碼攔截攻擊,特別是在公共客戶端中,秘密不能被安全地儲存。在使用 PKCE 時,客戶端生成一個程式碼驗證器和其對應的程式碼挑戰,這些都用於繫結授權請求和令牌交換。
實作 PKCE
實作 PKCE 是相對直接的,尤其是在使用 Authlib 的情況下。以下是一個簡單的示例:
import secrets
import hashlib
import base64
def generate_pkce_pair() -> (str, str):
# 產生一個隨機的程式碼驗證器
code_verifier = secrets.token_urlsafe(43)
# 產生程式碼挑戰
hashed = hashlib.sha256(code_verifier.encode('ascii')).digest()
code_challenge = base64.urlsafe_b64encode(hashed).decode('ascii').replace('=', '')
return code_verifier, code_challenge
這個示例展示瞭如何生成一個隨機的程式碼驗證器和其對應的程式碼挑戰,這兩個值在使用 PKCE 時是必要的。
2.5 安全的 API 存取令牌
為了保護 API 存取和管理使用者工作階段,安全的令牌產生、儲存和驗證是至關重要的。一個強大的根據令牌的身份驗證機制不僅能夠抽象化使用者身份從個別的 API 呼叫中,而且還能夠透過加密簽名和安全儲存實踐來最小化潛在的攻擊面。Python 的先進實作需要一個綜合的策略,結合安全的隨機令牌產生、防篡改的令牌編碼和動態復原能力。
令牌產生應該使用加密安全的偽隨機數生成器進行。Python 的secrets
模組提供了一套設計用於生成不可預測令牌的函式。開發人員經常使用令牌作為不透明的工作階段識別符或自包含的斷言(例如 JSON Web Tokens)。對於不透明的令牌,以下是一個使用secrets.token_urlsafe
的示例片段:
import secrets
def 生成不透明令牌(長度: int = 32) -> str:
# 生成一個URL安全的、Base64編碼的隨機令牌
return secrets.token_urlsafe(長度)
# 示例用法:
不透明令牌 = 生成不透明令牌()
當令牌封裝使用者宣告或中繼資料時,JSON Web Tokens(JWT)提供了一個安全且可擴充套件的解決方案。在這種情況下,令牌使用非對稱或對稱金鑰進行數字簽名。PyJWT 函式庫在生成和驗證 JWT 方面發揮了重要作用。以下示例說明瞭使用對稱金鑰(包括自定義宣告和到期強制執行)生成 JWT 的過程:
import jwt
import datetime
SECRET_KEY = "你的超級安全秘密金鑰"
def 生成JWT令牌(使用者ID: str, 範圍: list, 到期秒數: int = 3600) -> str:
payload = {
"sub": 使用者ID,
"scopes": 範圍,
"iat": datetime.datetime.utcnow(),
"exp": datetime.datetime.utcnow() + datetime.timedelta(seconds=到期秒數)
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def 解碼和驗證JWT(令牌: str) -> dict:
try:
解碼 = jwt.decode(令牌, SECRET_KEY, algorithms=["HS256"])
return 解碼
except jwt.ExpiredSignatureError:
# 處理到期簽名錯誤
pass
except jwt.InvalidTokenError:
# 處理無效令牌錯誤
pass
flowchart TD A[開始] --> B[生成不透明令牌] B --> C[生成JWT令牌] C --> D[解碼和驗證JWT] D --> E[傳回解碼結果]
圖表翻譯:
在這個流程圖中,我們展示了從生成不透明令牌到生成和驗證 JWT 令牌的過程。每一步驟都對應著上述程式碼片段中的函式,從而提供了一個清晰的視覺化表示。
安全的 Token 儲存機制
在根據 Token 的系統中,安全性是首要考量。為了確保 Token 的安全,玄貓強調了幾個關鍵點。
Token 儲存原則
Token 不應該以明文形式儲存,而應該使用密碼雜湊函式(Cryptographic Hash Function)加上 salt 值進行儲存。這與安全的密碼儲存實踐相同,確保即使儲存媒介被攻擊者所取得,Token 資料也不會被直接濫用。
多重因素驗證(MFA)已成為現代網路安全不可或缺的一部分。深入剖析 MFA 的實作方式,從 Token 的建立與驗證、安全儲存,到日誌與稽核,文章涵蓋了諸多關鍵環節。尤其在程式碼範例中,清晰展示瞭如何運用 Python 和 JWT 建構安全可靠的 MFA 機制,以及如何整合 TOTP、OAuth 2.0 和 OpenID Connect 等技術,更進一步提升安全性。然而,技術限制依然存在,例如程式碼範例中的 MFA 秘密金鑰硬編碼問題,以及 TOTP 驗證的時效性挑戰。在實務落地上,開發者應審慎評估不同 MFA 方案的優劣,並根據自身系統的特性和安全需求進行客製化調整,例如將 MFA 秘密金鑰儲存於安全的 Key Vault 中,並實施完善的金鑰輪換機制。展望未來,隨著無密碼驗證和生物識別技術的興起,MFA 的實作方式將更加多元化,使用者經驗也將更加便捷。玄貓認為,持續關注 MFA 技術的演進,並將其與其他安全措施相結合,才能有效提升系統的整體安全性,構建更值得信賴的網路環境。