API 安全防护实战:JWT 认证与限流配置指南

适用场景

API 安全防护适用于以下场景:企业提供 RESTful 或 GraphQL 接口供前端、移动端与第三方系统调用,需要统一认证与权限控制;接口被恶意调用造成流量滥用、刷单或数据爬取;微服务架构中网关与各服务间需要身份传递与限流能力。本文以 JWT 认证与接口限流为主线,提供一套可直接落地的 API 安全防护配置方案。

前置条件

  • 一个可运行的 API 服务(本文示例使用 Python 3.9+ 与 FastAPI)
  • 已安装依赖:pip install fastapi uvicorn pyjwt redis
  • 了解 HTTP 状态码与常见认证头(Authorization)格式

原理说明

API 安全的核心是认证(Authentication)授权(Authorization)的分离,以及资源保护。JWT(JSON Web Token)是一种无状态令牌,由 Header、Payload、Signature 三段组成,签名保证了令牌不可伪造。相比 Session,JWT 不需要服务端存储会话,天然适合分布式部署,但正因无状态,令牌一旦泄露无法即时吊销,因此必须配合短有效期HTTPS 传输密钥轮换。限流(Rate Limiting)则通过令牌桶或滑动窗口算法限制单位时间内的请求次数,防止接口被刷爆或拖垮后端。

操作步骤

1. JWT 签发与校验的正确姿势

# auth.py - JWT 签发与校验
import jwt, datetime

SECRET_KEY = "please-change-this-256bit-secret-key"
ALGORITHM = "HS256"

def create_token(user_id: int) -> str:
    payload = {
        "sub": str(user_id),
        "iat": datetime.datetime.now(datetime.timezone.utc),
        "exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=30),
        "scope": ["user:read", "user:write"],
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(token: str) -> dict:
    # verify=True 会自动校验签名与 exp
    return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])

关键参数exp 必须设置(建议 30 分钟以内),algorithms 必须显式指定而非接受任意算法,防止算法混淆攻击。

2. 在 FastAPI 中接入认证与限流

# main.py - FastAPI 认证中间件 + Redis 限流
from fastapi import FastAPI, Depends, HTTPException, Header
import redis, time

r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)
app = FastAPI()

def rate_limit(client_ip: str, limit: int = 60, window: int = 60):
    key = f"rl:{client_ip}:{int(time.time()) // window}"
    count = r.incr(key)
    if count == 1:
        r.expire(key, window)
    if count > limit:
        raise HTTPException(status_code=429, detail="请求过于频繁,请稍后再试")

@app.get("/api/data")
def get_data(authorization: str = Header(...)):
    client_ip = "0.0.0.0"  # 生产环境从 X-Forwarded-For 或真实连接获取
    rate_limit(client_ip)
    token = authorization.replace("Bearer ", "")
    try:
        payload = verify_token(token)
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="令牌已过期")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="令牌无效")
    return {"user": payload["sub"], "data": "sensitive"}

关键参数limitwindow 决定限流阈值(示例为 60 秒内 60 次);status_code=429 是标准的限流响应码,客户端据此做退避重试。

3. 密钥管理与轮换

# 生成强随机密钥(256 bit)
openssl rand -base64 32

# 生产环境务必从环境变量或密钥管理服务读取,禁止硬编码
export JWT_SECRET="$(openssl rand -base64 32)"

4. 网关层加固(Nginx 侧)

# 在 Nginx 中为 /api/ 前缀启用 IP 限流,与业务层形成双重防护
limit_req_zone $binary_remote_addr zone=api_zone:10m rate=10r/s;

server {
    location /api/ {
        limit_req zone=api_zone burst=20 nodelay;
        proxy_pass http://backend:8000;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

关键参数rate=10r/s 为平均速率,burst=20 允许突发峰值,nodelay 让超出突发量的请求立即返回 503/429,避免队列堆积拖垮后端。

5. 常见攻击面封堵

# 1) 校验 Content-Type,拒绝非 JSON 请求
# 2) 输入校验:长度、类型、白名单正则
# 3) 错误信息不泄露内部细节:统一返回 {code, message} 而非堆栈
# 4) 关闭 CORS 通配符,仅允许受信来源
# 5) 日志脱敏:不记录完整令牌与密码字段

配置验证

# 1. 启动服务
uvicorn main:app --host 0.0.0.0 --port 8000

# 2. 获取令牌(假设存在 /api/login 签发接口)
TOKEN=$(curl -s -X POST http://127.0.0.1:8000/api/login -d '{"user":"admin"}' -H "Content-Type: application/json" | python -c "import sys,json;print(json.load(sys.stdin)['token'])")

# 3. 携带令牌访问受保护接口
curl -s http://127.0.0.1:8000/api/data -H "Authorization: Bearer $TOKEN"

# 4. 无令牌访问应返回 401
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/api/data
# 期望输出:401

# 5. 限流验证:连续快速请求 70 次,第 61 次后应返回 429
for i in $(seq 1 70); do curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/api/data -H "Authorization: Bearer $TOKEN"; done | sort | uniq -c

常见问题

Q1:JWT 被泄露后如何快速止损?

JWT 无状态特性决定了无法在签发端直接吊销,止损三板斧:一是把有效期缩短到 15-30 分钟;二是维护一个 jti(令牌 ID)黑名单缓存(Redis 中存储至令牌过期);三是立即轮换签名密钥,让所有旧令牌全部失效并强制用户重新登录。对于高价值接口,建议引入 Refresh Token 机制,Access Token 有效期短、Refresh Token 可吊销。

Q2:限流应该做在网关还是业务层?

两层都要做,职责不同。网关(Nginx/API 网关)限流基于 IP/UA,挡住大部分 DDoS 与爬虫流量,保护整个后端;业务层限流基于用户维度(user_id/API Key),防止单个合法账号刷接口。两者配合才是完整方案。注意用户限流要放在认证之后,否则无法区分用户。

Q3:GraphQL 接口限流有什么特殊之处?

GraphQL 单次请求可批量查询,仅按请求数限流会被绕过。应额外限制查询复杂度(深度 + 节点数),如限制深度不超过 5、单次查询节点不超过 100,并在网关计算 cost 累加值做配额控制。

总结

API 安全防护的关键是认证、授权、限流与日志四层协同。JWT 用短有效期与显式算法绑定规避无状态令牌的固有风险,Redis 滑动窗口与 Nginx limit_req 分别实现用户维度和 IP 维度的限流,密钥从环境变量/密钥管理服务读取并定期轮换。建议上线前用压测工具(如 ab、wrk)验证限流生效阈值,并将 401/429 的统计接入监控告警。