4. 安全合规错误码分类:认证错误、授权错误、输入校验错误、资源冲突、限流熔断

在构建安全合规的后端系统时,错误码不仅仅是调试工具,更是安全边界的第一道防线。合理的错误码分类能够帮助客户端快速定位问题,同时避免泄露敏感信息。本章将深入剖析五大核心错误码类别:认证错误、授权错误、输入校验错误、资源冲突与限流熔断,并给出对应的HTTP状态码映射与安全合规断言策略。

核心原则:错误码设计应遵循“最小信息泄露”原则。对于认证与授权错误,返回信息应模糊化,避免攻击者通过错误信息推断用户是否存在或权限结构。

4.1 认证错误 (Authentication Errors)

认证错误发生在系统无法验证请求者身份的场景。常见原因包括:凭证缺失、凭证无效、凭证过期或会话失效。这类错误必须严格区分“未提供凭证”与“凭证错误”,但对外返回信息应保持统一。

错误场景 HTTP状态码 推荐错误码 安全断言要点
未提供认证凭证 401 Unauthorized AUTH_MISSING_TOKEN 响应体不得包含“请提供Bearer Token”等具体指引
凭证格式错误 401 Unauthorized AUTH_INVALID_TOKEN 不区分“Token格式错误”与“Token签名无效”
凭证已过期 401 Unauthorized AUTH_TOKEN_EXPIRED 可返回过期提示,但不得泄露Token剩余有效时间
会话已被撤销 401 Unauthorized AUTH_SESSION_REVOKED 返回信息应与“凭证无效”保持一致
合规提示:GDPR与等保2.0均要求认证失败日志不得记录明文密码或完整Token。建议在API网关层统一拦截并脱敏后再写入日志。

4.2 授权错误 (Authorization Errors)

授权错误发生在请求者身份已确认,但缺乏执行特定操作的权限。与认证错误不同,授权错误可以返回更明确的拒绝原因,但同样需要避免暴露资源层级细节。

  • 403 Forbidden:用户已认证但无权限。典型错误码:FORBIDDEN_INSUFFICIENT_ROLE、FORBIDDEN_RESOURCE_SCOPE。
  • 403 Forbidden (隐式):资源不存在与无权限应返回相同状态码,防止攻击者通过404/403差异枚举资源。
  • 401 vs 403 边界:若请求未携带任何身份信息,优先返回401;若携带了身份但权限不足,返回403。
// 授权错误响应示例 (JSON)
{
  "error": {
    "code": "FORBIDDEN_INSUFFICIENT_ROLE",
    "message": "您没有执行此操作的权限",
    "status": 403,
    "request_id": "req-abc123"
  }
}

4.3 输入校验错误 (Input Validation Errors)

输入校验错误是后端安全的第一道闸门。错误的校验信息可能泄露内部数据结构或SQL查询模式。安全合规要求校验错误信息必须经过“无害化”处理。

校验类型 HTTP状态码 安全风险 合规处理方式
字段格式错误 400 Bad Request 暴露正则表达式细节 统一返回“字段格式无效”
字段长度超限 400 Bad Request 暴露最大长度限制 返回“输入内容过长”
枚举值不在范围内 400 Bad Request 暴露允许的枚举列表 返回“参数值无效”
JSON结构解析失败 400 Bad Request 暴露解析器内部错误 返回“请求体格式错误”
避坑指南:不要在输入校验错误中返回“字段email格式应为xxx@xxx.com”或“密码长度需在8-20位之间”。这些信息会被攻击者用于构建精准的暴力破解字典。

4.4 资源冲突 (Resource Conflicts)

资源冲突通常发生在并发操作或状态变更时。这类错误码需要明确告知客户端操作无法完成,同时提供足够的上下文以便重试或回滚。

  • 409 Conflict:资源当前状态与请求冲突。典型场景:版本号不一致、唯一约束冲突、资源已被删除。
  • 412 Precondition Failed:请求中指定的条件不满足。典型场景:If-Match头与ETag不匹配。
  • 423 Locked:资源被临时锁定。典型场景:分布式锁、编辑锁定。
// 资源冲突错误响应示例 (409)
{
  "error": {
    "code": "CONFLICT_VERSION_MISMATCH",
    "message": "资源已被其他操作修改,请刷新后重试",
    "status": 409,
    "expected_version": 3,
    "current_version": 5
  }
}

4.5 限流熔断 (Rate Limiting & Circuit Breaking)

限流与熔断是保障系统稳定性的关键机制。错误码设计需要区分“临时限流”与“永久封禁”,并引导客户端进行合理的退避重试。

场景 HTTP状态码 推荐错误码 响应头要求
请求速率超限 429 Too Many Requests RATE_LIMIT_EXCEEDED 必须包含Retry-After头
并发请求超限 429 Too Many Requests RATE_LIMIT_CONCURRENT 建议包含X-RateLimit-Limit头
服务熔断降级 503 Service Unavailable CIRCUIT_BREAKER_OPEN 必须包含Retry-After头,建议包含X-Circuit-Status头
IP被临时封禁 429 Too Many Requests RATE_LIMIT_BLOCKED 不得透露封禁时长
最佳实践:限流响应中应使用标准的Retry-After头(单位:秒),而非自定义字段。客户端应优先读取该头进行退避,避免造成“限流风暴”。
安全合规错误码分类全景图 API 错误响应 认证错误 授权错误 输入校验错误 资源冲突 限流熔断 凭证缺失 凭证无效 凭证过期 角色不足 资源范围 操作拒绝 格式无效 长度超限 枚举越界 版本冲突 唯一约束 资源锁定 速率超限 并发超限 熔断降级 安全合规要求:认证/授权错误信息模糊化,输入校验不暴露内部规则 限流响应必须包含 Retry-After 头,资源冲突需提供版本号上下文

4.6 错误码断言策略与OpenAPI契约

在OpenAPI契约测试中,错误码断言需要覆盖以下维度:

  1. 状态码断言:验证响应状态码是否严格匹配规范(如认证错误必须为401,而非403)。
  2. 错误码字段断言:验证error.code字段是否在预定义的枚举范围内。
  3. 响应头断言:限流场景必须验证Retry-After头存在且为合法整数。
  4. 信息一致性断言:同一错误码在不同端点返回的message格式必须一致。
  5. 安全合规断言:认证/授权错误响应中不得包含stack_trace、debug_info等敏感字段。
// OpenAPI 3.0 错误码契约示例 (片段)
components:
  responses:
    UnauthorizedError:
      description: 认证失败
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                properties:
                  code:
                    type: string
                    enum: [AUTH_MISSING_TOKEN, AUTH_INVALID_TOKEN, AUTH_TOKEN_EXPIRED]
                  message:
                    type: string
                    maxLength: 100
                  status:
                    type: integer
                    enum: [401]
              request_id:
                type: string
                format: uuid
            required: [error, request_id]
      headers:
        WWW-Authenticate:
          schema:
            type: string
测试断言示例 (Python + pytest):
def test_unauthorized_response_should_not_leak_info(api_client):
    response = api_client.get("/api/protected/resource")
    assert response.status_code == 401
    body = response.json()
    assert "error" in body
    assert body["error"]["status"] == 401
    # 安全合规断言:响应中不得包含敏感字段
    assert "stack_trace" not in body
    assert "debug" not in body
    assert "sql" not in body
    # 错误码必须在枚举范围内
    assert body["error"]["code"] in [
        "AUTH_MISSING_TOKEN",
        "AUTH_INVALID_TOKEN",
        "AUTH_TOKEN_EXPIRED"
    ]

4.7 分类对比与选择矩阵

错误类别 HTTP状态码 是否可重试 信息模糊化要求 典型安全风险
认证错误 401 是(重新获取凭证) 高 用户枚举、凭证猜测
授权错误 403 否 中 权限提升、资源枚举
输入校验错误 400 是(修正请求后) 高 注入攻击、内部结构泄露
资源冲突 409/412/423 是(刷新后重试) 低 并发竞态、数据不一致
限流熔断 429/503 是(等待Retry-After) 中 DDoS、资源耗尽
实战建议:在OpenAPI契约中为每个端点显式声明所有可能的错误响应。使用oneOf或discriminator区分不同错误类型,确保客户端SDK能够正确解析并处理各类错误。