一、HTTP 认证体系概览
1.1 RFC 标准的认证方案
WWW-Authenticate 响应头定义认证方案:
1. Basic — 最简单,base64 编码用户名:密码
2. Digest — 改进版,挑战-响应模式,防明文泄露
3. Bearer — OAuth 2.0 / JWT 使用的令牌模式
4. Negotiate — Kerberos / SPNEGO 集成 Windows 认证
5. Mutual (mTLS) — 客户端证书双向认证
6. API Key — 私有扩展,通常用自定义 Header 或 Query 参数
安全等级(从弱到强):
Basic < Digest < Bearer < Negotiate < mTLS
1.2 统一交互流程
Client Server
│ │
│ 1. GET /api/protected │
│ ─────────────────────────────────────▶ │
│ │
│ 2. 401 Unauthorized │
│ WWW-Authenticate: Bearer realm="api" │
│ WWW-Authenticate: Basic realm="api" │
│ ◀───────────────────────────────────── │
│ │
│ 3. GET /api/protected │
│ Authorization: Bearer eyJhbGci... │
│ Authorization: Basic dXNlcjpwYXNz │
│ ─────────────────────────────────────▶ │
│ │
│ 4. 200 OK + Protected Resource │
│ ◀───────────────────────────────────── │
二、Basic 认证:简单但极不安全
2.1 工作原理
1. 客户端发送:Authorization: Basic base64(username:password)
2. 服务端解码后得到用户名和密码
3. 校验密码是否正确
例:用户名 admin,密码 secret
→ admin:secret → base64 编码 → YWRtaW46c2VjcmV0
→ Authorization: Basic YWRtaW46c2VjcmV0
2.2 攻击一:中间人窃听(HTTP 明文)
# 如果 HTTP 没有加密,攻击者可以轻易截获凭据
tcpdump -i eth0 -A 'tcp port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'
# 或用 mitmproxy
mitmproxy -p 8080 --mode regular
# 浏览 HTTP 站点时可以看到所有 Authorization 头
2.3 攻击二:base64 解码
import base64
# Base64 编码不是加密!任何人都可以解码
encoded = 'YWRtaW46c2VjcmV0' # 来自 Authorization: Basic YWRtaW46c2VjcmV0
decoded = base64.b64decode(encoded).decode()
print(f"[+] Credentials: {decoded}") # admin:secret
2.4 攻击三:暴力破解
import requests
import base64
TARGET = 'https://api.example.com/admin'
USERNAMES = ['admin', 'root', 'administrator', 'superuser']
PASSWORDS_WORDLIST = '/path/to/rockyou.txt'
def brute_force_basic_auth():
found = []
with open(PASSWORDS_WORDLIST, 'r', errors='ignore') as f:
passwords = [line.strip() for line in f if line.strip()]
for username in USERNAMES:
for password in passwords:
auth_str = base64.b64encode(f'{username}:{password}'.encode()).decode()
headers = {'Authorization': f'Basic {auth_str}'}
resp = requests.get(TARGET, headers=headers, timeout=5)
if resp.status_code == 200:
print(f"[+] FOUND! {username}:{password}")
found.append((username, password))
elif resp.status_code == 429:
time.sleep(2)
return found
2.5 安全使用 Basic Auth
# ✅ Basic Auth 只能在 HTTPS 下使用
from flask import request, Response
import hmac
BASIC_USERS = {
'admin': hashed_password,
'user1': hashed_password,
}
def check_basic_auth():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Basic '):
return None, None, False
try:
decoded = base64.b64decode(auth[6:]).decode()
username, password = decoded.split(':', 1)
except Exception:
return None, None, False
expected_hash = BASIC_USERS.get(username)
if not expected_hash:
return username, password, False
# 🔑 使用 Argon2/bcrypt 存储,不要明文
valid = argon2.verify(password, expected_hash)
return username, password, valid
# Nginx 配置(只允许 HTTPS 下使用 Basic Auth)
# server {
# listen 443 ssl;
# server_name api.example.com;
#
# auth_basic "Protected API";
# auth_basic_user_file /etc/nginx/.htpasswd;
#
# # 强制所有 HTTP 请求重定向到 HTTPS
# # return 301 https://$server_name$request_uri;
# }
三、Digest 认证:改进但仍有缺陷
3.1 工作原理
Digest 认证使用挑战-响应模式,避免密码明文传输:
1. 客户端请求 → 服务端返回 401 + nonce(随机数)
2. 客户端用 H(username:realm:password, nonce, uri) 生成 response
3. 服务端验证 response 是否正确
公式(MD5 已过时,应用 SHA-256):
HA1 = MD5(username:realm:password)
HA2 = MD5(method:uri)
response = MD5(HA1:nonce:HA2)
# 现代 Digest 使用 SHA-256:
HA1 = SHA-256(username:realm:password)
HA2 = SHA-256(method:uri)
response = SHA-256(HA1:nonce:HA2)
3.2 攻击一:重放攻击
# 如果服务端没有正确维护 nonce 的一次性性,相同的认证响应可以重复使用
# 攻击者截获一次 Digest 认证成功的请求后
# 可以在 nonce 过期前反复发送,每次都能认证成功
# 服务端必须:
# 1. 每个 nonce 只能使用一次
# 2. nonce 必须有严格的过期时间(如 30 秒)
# 3. 存储已使用的 nonce 并校验
used_nonces = set() # 或 Redis SADD
def verify_digest_nonce(nonce: str, client_nonce: str) -> bool:
# 检查是否重复使用
if nonce in used_nonces:
return False
# 检查服务端 nonce 是否在有效期内
server_nonce = redis.get(f'digest_nonce:{nonce}')
if not server_nonce:
return False
# 记录已使用
redis.delete(f'digest_nonce:{nonce}')
used_nonces.add(nonce)
return True
3.3 攻击二:弱哈希碰撞
Digest 认证历史上默认使用 MD5,而 MD5 已被证明不安全。现代实现应使用 SHA-256 或更强算法。
3.4 Digest vs Basic vs Bearer
Digest 的优势:
✅ 密码不以明文/可逆编码形式发送
✅ 可以防止简单的重放攻击(如果 nonce 管理正确)
✅ 不需要预先存储密码(只需存储 HA1)
Digest 的劣势:
❌ 服务端仍需维护 per-request state(nonce 校验)
❌ 实现复杂,容易出错
❌ 容易被钓鱼(用户可能在钓鱼服务器认证)
❌ 不传递会话上下文,无法实现 SSO
Bearer Token 的优势:
✅ 无状态,服务端不需要维护认证状态
✅ 可以携带丰富的用户信息(角色、权限等)
✅ 支持 OAuth 2.0 授权码流程
✅ 可通过 refresh token 轮换
Bearer Token 的劣势:
⚠️ Token 一旦泄露就可以无限次使用
⚠️ 需要额外的 Token 吊销机制
四、Bearer Token:现代主流方案
4.1 完整的 Bearer Token 校验中间件
from functools import wraps
from jose import jwt, JWTError, ExpiredSignatureError
def require_bearer_token(f):
@wraps(f)
def wrapper(*args, **kwargs):
auth_header = request.headers.get('Authorization', '')
# 1. 检查格式
if not auth_header.startswith('Bearer '):
return jsonify({'error': 'invalid authorization header'}), 401
token = auth_header[7:]
if not token or token == 'undefined' or token == 'null':
return jsonify({'error': 'token required'}), 401
try:
# 2. 解码并校验所有标准声明
payload = jwt.decode(
token,
PUBLIC_KEY, # 或 SECRET_KEY for HS256
algorithms=['RS256'], # 🔑 固定算法
audience='api.example.com',
issuer='https://auth.example.com',
options={
'verify_exp': True,
'verify_iat': True,
'verify_nbf': True,
'verify_aud': True,
'verify_iss': True,
'require': ['exp', 'iat', 'sub', 'aud', 'iss'],
},
)
# 3. 检查是否在黑名单中(吊销)
jti = payload.get('jti')
if jti and is_token_revoked(jti):
return jsonify({'error': 'token revoked'}), 401
# 4. 注入用户上下文
request.user = payload
except ExpiredSignatureError:
return jsonify({'error': 'token expired'}), 401
except JWTError as e:
return jsonify({'error': f'invalid token: {e}'}), 401
return f(*args, **kwargs)
return wrapper
4.2 Token 泄露的防护
方法一:Bearer Token 绑定到请求源
# DPoP (Demonstration of Proof-of-Possession) RFC 9449
# 客户端生成临时密钥对,Token 只能与该密钥对的签名一起使用
def validate_dpop(token: str, dpop_proof: str, http_method: str, target_uri: str) -> bool:
"""验证 DPoP 证明"""
try:
proof_payload = jwt.decode(dpop_proof, None, algorithms=['ES256'])
# 检查 payload 中的非标准字段
ath = hashlib.sha256(token.encode()).digest()
ath_b64 = base64.urlsafe_b64encode(ath).rstrip(b'=').decode()
# 检查 jtu(JWT Target URI)
if proof_payload.get('htu') != target_uri:
return False
# 检查 jtm(JWT HTTP Method)
if proof_payload.get('htm') != http_method:
return False
# 检查 ath(JWT Access Token Hash)
if proof_payload.get('ath') != ath_b64:
return False
# 检查时间窗口
now = time.time()
if proof_payload.get('iat', 0) < now - 300:
return False
# 验证签名(使用客户端的临时密钥)
public_key = load_ec_public_key(proof_payload['jti'])
jwt.decode(dpop_proof, public_key, algorithms=['ES256'])
return True
except Exception:
return False
方法二:限制 Bearer Token 的使用场景
# Token 中包含 audience 和 issuer
Bearer eyJ...
{
"iss": "https://auth.example.com", # 谁签发的
"aud": "api.example.com", # 给谁用的(不能给其他 API 用)
"scope": "read:orders write:orders", # 能做什么
"exp": 1700003600, # 什么时候过期
"jti": "unique-token-id", # 唯一标识(用于吊销)
"sub": "user-123", # 是谁
"client_id": "public-spa-client", # 给哪个客户端
}
五、API Key 认证
5.1 常见方式
方式一:自定义 Header(最推荐)
X-API-Key: sk_live_abc123...
Authorization: ApiKey sk_live_abc123...
Authorization: Bearer sk_live_abc123... # 有些 API Key 也用 Bearer
方式二:Query Parameter
GET /api/resources?api_key=sk_live_abc123 # 🔴 易泄露到日志/Referer
方式三:HTTP Header(厂商自定义)
Stripe-Account: acct_xxx
X-Stripe-Signature: t=123,v1=...
5.2 安全的 API Key 生成与校验
import secrets
import hashlib
class ApiKeyManager:
"""安全的 API Key 管理"""
KEY_PREFIXES = {
'test': 'sk_test_',
'live': 'sk_live_',
'restricted': 'sk_restricted_', # 只读
}
def generate_key(self, environment: str, scopes: list) -> dict:
"""生成 API Key"""
key_id = secrets.token_hex(16) # 唯一 ID
secret = secrets.token_urlsafe(48) # 随机密钥(384 位)
prefix = self.KEY_PREFIXES.get(environment, 'sk_')
# 只存储密钥的 hash,不存明文
secret_hash = hashlib.sha256(secret.encode()).hexdigest()
db.execute("""
INSERT INTO api_keys (key_id, secret_hash, environment, scopes, created_at, last_used_at)
VALUES (?, ?, ?, ?, ?, NULL)
""", (key_id, secret_hash, environment, ','.join(scopes), time.time()))
# 明文只返回一次!用户必须安全保存
return {
'key': f"{prefix}{key_id}{secret}", # 完整 key(包含前缀+ID+密钥)
'key_id': key_id,
'environment': environment,
'note': 'Store this key securely. It will not be shown again.',
}
def verify_key(self, api_key: str) -> dict | None:
"""校验 API Key"""
try:
# 解析 key 组成
for prefix in self.KEY_PREFIXES.values():
if api_key.startswith(prefix):
remaining = api_key[len(prefix):]
break
else:
return None
key_id = remaining[:32] # 前 16 字节 hex = 32 字符
secret_part = remaining[32:]
# 从 DB 查询 hash
row = db.execute(
"SELECT * FROM api_keys WHERE key_id = ? AND revoked = 0",
(key_id,),
).fetchone()
if not row:
return None
# 校验 hash
secret_hash = hashlib.sha256(secret_part.encode()).hexdigest()
if not hmac.compare_digest(secret_hash, row['secret_hash']):
return None
# 更新最后使用时间
db.execute(
"UPDATE api_keys SET last_used_at = ? WHERE key_id = ?",
(time.time(), key_id),
)
return row
except Exception:
return None
def revoke_key(self, key_id: str):
"""吊销 API Key"""
db.execute("UPDATE api_keys SET revoked = 1 WHERE key_id = ?", (key_id,))
5.3 高级:签名请求(HMAC-SHA256)
# Stripe / AWS 等 API 的签名方案
class RequestSigner:
"""API 请求签名(防篡改 + 防重放)"""
def sign_request(self, http_method: str, path: str, body: str, timestamp: str, secret: str) -> str:
"""计算 HMAC 签名"""
# canonical_string = timestamp + http_method + path + body
canonical = f"{timestamp}.{http_method.upper()}.{path}.{body}"
signature = hmac.new(
secret.encode(),
canonical.encode(),
hashlib.sha256,
).hexdigest()
return signature
def verify_request(self, signature: str, timestamp: str, http_method: str, path: str, body: str, secret: str) -> bool:
"""验证请求签名"""
# 1. 检查时间戳有效性(防重放)
try:
ts = int(timestamp)
except ValueError:
return False
now = int(time.time())
if abs(now - ts) > 300: # 允许 ±5 分钟
return False
# 2. 计算预期签名
expected = self.sign_request(http_method, path, body, timestamp, secret)
# 3. timing-safe 比较
return hmac.compare_digest(expected, signature)
# 使用示例
# 客户端:
headers = {
'X-Api-Key': api_key,
'X-Timestamp': str(int(time.time())),
'X-Signature': signer.sign_request(
'POST', '/api/payments',
request_body, str(int(time.time())), secret,
),
}
# 服务端:
@app.before_request
def verify_signature():
signature = request.headers.get('X-Signature')
timestamp = request.headers.get('X-Timestamp')
if not signature or not timestamp:
return jsonify({'error': 'signature required'}), 401
api_key = request.headers.get('X-Api-Key')
key_info = api_key_manager.verify_key(api_key)
if not key_info:
return jsonify({'error': 'invalid api key'}), 401
request_body = request.get_data(as_text=True)
path = request.path
if not signer.verify_request(
signature, timestamp,
request.method, path, request_body,
key_info['secret'],
):
return jsonify({'error': 'invalid signature'}), 401
六、攻击:HTTP 头注入
6.1 Proxy-Authorization 绕过
某些应用只检查 Authorization 头,忽略了 Proxy-Authorization 头。
# 攻击者可以尝试:
GET /api/protected
Authorization: Bearer <invalid_token>
Proxy-Authorization: Bearer <stolen_token>
如果应用用 Proxy-Authorization 也认证 → 绕过
6.2 自定义 Header 绕过
有些系统通过 Nginx/CDN 在入口检查 Auth 头,但应用层自己又有一层检查。
# 绕过尝试:
GET /api/protected
Authorization: <empty or invalid>
X-Authorization: Bearer <valid_token>
X-API-Auth: Bearer <valid_token>
Token: <valid_token>
6.3 修复:统一从 Authorization 头取 token
def extract_auth_token(request) -> str | None:
"""统一提取 token,忽略非标准头"""
# 🔑 只从标准 Authorization 头提取
auth = request.headers.get('Authorization', '')
if auth.startswith('Bearer '):
return auth[7:]
elif auth.startswith('Basic '):
return auth[6:]
# 忽略所有其他头
# X-Authorization, X-Token, X-API-Key, Token, Proxy-Authorization 等
# 这些都是非标准的,可能被用于绕过
return None
七、HTTP 认证与 CORS
7.1 问题
Bearer Token 在跨域请求中不会自动携带,需要前端显式添加 Authorization 头。如果 CORS 配置不当,可能导致:
- Token 在错误的 Origin 下被发送
- 来自恶意 Origin 的请求被允许携带 Token
7.2 安全的 CORS 配置
from flask_cors import CORS
# ✅ 严格的 CORS 配置
CORS(
app,
resources={
r"/api/*": {
"origins": [
"https://app.example.com",
"https://admin.example.com",
],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": [
"Authorization",
"Content-Type",
"X-Requested-With",
],
"expose_headers": ["X-Total-Count"],
"supports_credentials": True, # 允许带 Cookie
"max_age": 600,
}
},
send_wildcard=False,
always_send=True,
)
# 🔑 绝不要设置 origins: "*" 与 supports_credentials: True 同时开启
# 🔑 绝不要动态反射 Origin 头
@app.after_request
def strict_cors(response):
origin = request.headers.get('Origin')
# 严格白名单
ALLOWED_ORIGINS = [
'https://app.example.com',
'https://admin.example.com',
]
if origin in ALLOWED_ORIGINS:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Vary'] = 'Origin'
# 其他安全头
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
return response
八、Nginx 认证配置实战
8.1 正确的 Basic Auth 配置
# 🔴 错误:允许 HTTP 下使用 Basic Auth
server {
listen 80;
auth_basic "Admin Panel";
auth_basic_user_file /etc/nginx/.htpasswd;
# HTTP 传输,凭据可能被窃取
}
# ✅ 正确:只在 HTTPS 下使用 Basic Auth
server {
listen 80;
return 301 https://$host$request_uri; # 强制跳转
# 或者完全拒绝 HTTP
# return 444;
}
server {
listen 443 ssl http2;
server_name admin.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
auth_basic "Admin Panel";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://backend;
}
}
8.2 Bearer Token 认证配置
# Nginx subrequest 做 Bearer Token 校验
location /api/protected/ {
auth_request /auth; # 子请求校验
auth_request_set $user $upstream_http_x_user;
auth_request_set $roles $upstream_http_x_roles;
proxy_set_header X-Authenticated-User $user;
proxy_set_header X-User-Roles $roles;
proxy_pass http://backend;
}
location = /auth {
internal;
proxy_pass http://auth-service/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Authorization $http_authorization;
}
# auth-service 的 verify 端点
@app.route('/verify', methods=['GET'])
def verify():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return Response(status=401)
try:
payload = jwt.decode(auth[7:], PUBLIC_KEY, algorithms=['RS256'])
return Response(
status=200,
headers={
'X-User': payload['sub'],
'X-Roles': payload.get('roles', ''),
},
)
except jwt.InvalidTokenError:
return Response(status=401)
8.3 mTLS(双向 TLS)配置
# mTLS 是 HTTP 认证的"终极方案"——客户端必须出示受信任的证书
server {
listen 443 ssl http2;
server_name internal-api.example.com;
ssl_certificate /etc/nginx/certs/server.pem;
ssl_certificate_key /etc/nginx/certs/server.key;
# 客户端证书验证
ssl_client_certificate /etc/nginx/certs/client-ca.pem;
ssl_verify_client on;
ssl_verify_depth 2;
# 证书映射
# 将客户端证书的 Subject DN 映射到用户身份
ssl_client_header_map $ssl_client_s_dn $client_email {
~emailAddress=(.*) $1;
}
location / {
if ($ssl_client_verify != SUCCESS) {
return 403 "Client certificate required";
}
proxy_set_header X-Client-Certificate-DN $ssl_client_s_dn;
proxy_set_header X-Client-Email $client_email;
proxy_set_header X-SSL-Verified $ssl_client_verify;
proxy_pass http://backend;
}
}
九、认证头安全审计清单
- 是否强制 HTTPS(不允许明文传输 Authorization 头)
- 是否统一从标准 Authorization 头提取凭据(忽略非标准头)
- Bearer Token 是否校验了所有标准声明(exp、iat、aud、iss、nbf)
- Token 是否有吊销机制(黑名单 / introspection 端点)
- CORS 是否配置了严格的 Origin 白名单
- API Key 是否只存 hash(不存明文)
- API Key 是否使用前缀区分环境
- Bearer Token 是否考虑了 DPoP 绑定
- mTLS 客户端证书是否定期轮换
- Basic/Digest Auth 是否有速率限制
十、总结
HTTP 认证机制的安全性从弱到强排列:
Basic Auth → Digest Auth → Bearer Token → Negotiate (Kerberos) → mTLS
(极弱) (弱) (中) (中强) (强)
现代 API 的推荐组合:
1. **OAuth 2.0 + Bearer Token + JWT**(通用场景)
2. **HTTPS + Strict-Transport-Security**(基础强制)
3. **CORS 严格白名单**(前端 SPA 场景)
4. **Token 黑名单 + DPoP 绑定**(Token 泄露防护)
5. **API Key + HMAC 请求签名**(第三方 API)
6. **mTLS**(服务间调用,零信任架构)
核心原则:HTTP 认证头只是凭据的传递管道,真正的安全在于:凭据的生成强度 + 传输加密 + 校验严格性 + 吊销机制 + 绑定策略。 单独讨论任何一个认证机制的安全性都是片面的。
十一、参考资料
- RFC 7235: HTTP/1.1 Authentication
- RFC 6749: OAuth 2.0 Authorization Framework
- RFC 7519: JSON Web Token
- RFC 9449: DPoP
- OWASP Authentication Cheat Sheet
- OWASP API Security Top 10
- Stripe API Security Guide
- AWS Signature V4 Reference
- PortSwigger API Authentication