一、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