跳转到内容

接口鉴权规范

Token、权限、签名等鉴权机制


[Standard] 面向终端用户的接口使用 JWT Bearer Token;服务间调用使用 AppKey + AppSecret 签名;内部管理接口使用 mTLS 或内网白名单。

场景认证方式传输位置
终端用户(Web/App)JWT Access TokenAuthorization: Bearer <token>
服务间调用(开放平台)AppKey + 签名X-App-Key + X-Signature + X-Timestamp
内部服务调用mTLS 或内网 IP 白名单TLS 证书层 / 网关层
回调接口(第三方通知)HMAC 签名请求头签名

[Requirement] 禁止在 URL 查询参数中传递 Token 或密钥(会被日志、Referer、浏览器历史记录泄漏)。

[Requirement] JWT 使用 Authorization: Bearer <token> 头传输,Token 存储于 HttpOnly Secure Cookie 或客户端内存,禁止存 localStorage 长期持有。

[Standard] JWT Payload 只存必要信息(user_id、exp、iat),禁止存入密码、权限列表等敏感或易变数据。

// JWT Payload 示例
{
  "sub": "1001",           // user_id
  "iat": 1728894600,
  "exp": 1728898200,       // 1 小时过期
  "jti": "a1b2c3d4"        // token 唯一 ID,用于吊销
}

[Caution] JWT 签名算法必须使用 HS256 以上或 RS256,禁止使用 none 算法。


[Standard] 采用双 Token 机制:Access Token 短有效期(15 分钟2 小时),Refresh Token 长有效期(730 天),仅用于换取新 Access Token。

Token有效期存储用途
Access Token15~120 分钟内存 / 短期 Cookie接口调用
Refresh Token7~30 天HttpOnly Secure Cookie刷新 Access Token

[Requirement] Refresh Token 必须支持轮换(每次刷新生成新的 Refresh Token,旧的立即失效),防止重放。

[Standard] Access Token 过期返回 401 + 错误码 410002,客户端调用 /auth/refresh 获取新 Token。

[Requirement] Token 吊销通过黑名单(Redis)实现,以 jti 为 key 存储至自然过期时间,登出/改密时加入黑名单。

# 吊销 Token
def revoke_token(jti: str, exp: int):
    ttl = exp - int(time.time())
    if ttl > 0:
        redis.setex(f"token:blacklist:{jti}", ttl, "1")

[Caution] 禁止 Access Token 有效期超过 2 小时;高敏接口(支付、改密)要求 Access Token 有效期 ≤ 15 分钟,且需二次验证。


[Standard] 权限模型采用 RBAC(基于角色)+ ABAC(基于属性) 混合:角色控制功能权限,属性(部门、数据归属)控制数据权限。

用户 ──多对多── 角色 ──多对多── 权限点(如 user:read, order:create)
└── 数据权限规则(如 own_dept, all)

[Requirement] 所有需鉴权接口必须在网关或 Controller 入口处统一校验,禁止在业务代码中散落权限判断。

[Standard] 权限点命名:{资源}:{动作},如 user:read、order:create、payment:refund。

# 装饰器声明所需权限
@require_permission("order:read")
@router.get("/orders/{order_id}")
async def get_order(order_id: int, current_user: User = Depends(get_current_user)):
    ...

[Standard] 数据权限通过在查询层注入过滤条件实现(如 WHERE owner_id = current_user.id 或 WHERE dept_id IN (...)),禁止在返回后过滤。

[Caution] 水平越权(A 用户访问 B 用户数据)是高频漏洞,必须通过参数校验当前用户对资源的归属权。


[Requirement] 服务间调用与开放平台接口使用 HMAC-SHA256 签名,密钥为 AppSecret,永不传输。

[Standard] 签名串由 请求方法 + 路径 + 时间戳 + 非随机串 + 排序后查询串 + 请求体哈希 拼接。

signature = HMAC-SHA256(
secret=AppSecret,
message="POST\n/api/v1/orders\n1728894600\nabc123\namount=100&item=book\nsha256(body)"
)

[Requirement] 签名接口必须携带以下请求头:

头说明
X-App-Key调用方标识
X-Timestamp请求时间戳(秒),服务端校验±5 分钟内
X-Nonce随机串,防重放(缓存 10 分钟)
X-SignatureHMAC-SHA256 签名(十六进制)

[Requirement] 服务端校验:① 时间戳在 ±5 分钟内;② Nonce 在缓存中不存在;③ 签名正确。三者均通过才放行。

def verify_signature(app_key, timestamp, nonce, signature, method, path, body):
    # 1. 时间戳校验
    if abs(int(time.time()) - int(timestamp)) > 300:
        raise InvalidSignatureError("timestamp out of range")
    # 2. Nonce 防重放
    if redis.exists(f"nonce:{nonce}"):
        raise ReplayAttackError("nonce already used")
    redis.setex(f"nonce:{nonce}", 600, "1")
    # 3. 签名校验
    secret = get_secret_by_app_key(app_key)
    expected = hmac.new(secret.encode(), build_string(...), sha256).hexdigest()
    if not hmac.compare_digest(expected, signature):
        raise InvalidSignatureError("signature mismatch")

[Caution] 签名比较必须使用 hmac.compare_digest(恒定时间),禁止 ==,防止时序攻击。


[Requirement] 生产环境强制 HTTPS(TLS 1.2+),禁用 HTTP 明文传输;敏感接口要求 TLS 1.3。

[Standard] 设置安全响应头:Strict-Transport-Security、X-Content-Type-Options: nosniff、Content-Security-Policy。

[Requirement] 所有对外接口必须在网关层做限流(按 IP、用户、AppKey 维度),默认 100 次/分钟,超限返回 429 + Retry-After 头。

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1728894660

[Standard] 敏感操作(改密、支付、删除)增加二次验证(短信验证码、支付密码),并记录操作日志。

[Caution] 登录、改密、验证码接口必须加图形验证码/行为验证,防止暴力破解。

[Requirement] 日志中禁止打印 Authorization 头、Cookie、AppSecret、密码原文;Authorization 头在日志中替换为 ***。