3. 安全合规错误码分类
在集成测试环境下,后端接口返回的 HTTP 状态码与业务错误码是安全合规断言的核心依据。错误码的准确分类不仅决定了测试用例的断言逻辑,还直接关联到安全审计与合规扫描的通过率。本章将错误码划分为四大类:认证类、授权类、输入校验类与资源不存在类,并逐一分析其语义、断言策略与常见陷阱。
3.1 认证类错误码(401 / 403)
认证类错误码是安全合规中最基础也最容易混淆的一类。HTTP 规范明确区分了 401 Unauthorized 与 403 Forbidden,但在实际项目中常被误用或混用。集成测试断言必须严格遵循语义差异。
| 状态码 | 语义 | 典型场景 | 断言要点 |
|---|---|---|---|
| 401 | 未认证(Unauthenticated) | 缺少 Token、Token 过期、Token 格式错误 | 响应体应包含 error_code 如 AUTH_TOKEN_MISSING;WWW-Authenticate 头必须存在 |
| 403 | 已认证但无权限(Forbidden) | 用户角色不足、IP 白名单拒绝、请求被 WAF 拦截 | 响应体应包含 error_code 如 FORBIDDEN_ROLE;不应返回 WWW-Authenticate 头 |
合规提示: 安全审计标准(如 OWASP ASVS)要求 401 与 403 的响应体结构必须一致,避免攻击者通过响应差异推断资源是否存在。集成测试应断言两个状态码返回的 JSON 字段数量与格式相同。
集成测试断言示例(Python + requests):
def test_401_response_structure():
resp = client.get("/api/secure", headers={})
assert resp.status_code == 401
assert "error_code" in resp.json()
assert "message" in resp.json()
assert "WWW-Authenticate" in resp.headers
def test_403_response_structure():
resp = client.get("/api/admin", headers={"Authorization": "Bearer user_token"})
assert resp.status_code == 403
assert "error_code" in resp.json()
assert "message" in resp.json()
assert "WWW-Authenticate" not in resp.headers
3.2 授权类错误码
授权类错误码与 403 有重叠,但授权错误更聚焦于业务权限而非通用访问控制。常见授权错误码包括 403(通用无权限)、451(法律原因拒绝)以及自定义业务错误码如 INSUFFICIENT_PERMISSION、ROLE_NOT_ALLOWED。集成测试需要区分“功能级授权”与“数据级授权”。
- 功能级授权: 用户角色是否允许执行某个操作(如删除用户)。断言:返回 403 + 业务错误码
FUNCTION_FORBIDDEN。 - 数据级授权: 用户是否只能操作自己拥有的数据(如只能查看自己的订单)。断言:返回 403 或 404(防止枚举),取决于安全策略。
- 跨域授权: OAuth2 Scope 不足时返回
403或insufficient_scope错误。
| 授权类型 | HTTP 状态码 | 业务错误码示例 | 断言策略 |
|---|---|---|---|
| 功能级授权 | 403 | FUNCTION_FORBIDDEN |
断言 status_code == 403,error_code 匹配预期 |
| 数据级授权 | 403 / 404 | DATA_ACCESS_DENIED |
根据安全策略断言是否返回 404 隐藏资源存在性 |
| OAuth2 Scope | 403 | insufficient_scope |
断言响应包含 error 字段且值为 insufficient_scope |
常见陷阱: 某些框架在数据级授权失败时返回
404 Not Found 而非 403,这是为了防止攻击者通过错误码差异推断资源是否存在。集成测试必须与安全策略文档对齐,不能硬编码为 403。
3.3 输入校验类错误码
输入校验错误码覆盖参数缺失、格式错误、类型不匹配、长度超限、SQL 注入特征检测等场景。安全合规要求输入校验错误不能泄露内部实现细节(如数据库字段名、堆栈信息)。
- 标准状态码:
400 Bad Request是输入校验错误的主状态码;422 Unprocessable Entity用于语义错误(如 JSON 结构正确但业务逻辑无效)。 - 业务错误码规范: 推荐使用
VALIDATION_ERROR作为通用错误码,配合field与reason字段定位具体问题。 - 安全约束: 错误信息不得包含正则表达式细节、数据库列名、文件路径或 SQL 片段。
输入校验错误响应体规范示例:
{
"error_code": "VALIDATION_ERROR",
"message": "请求参数校验失败",
"details": [
{
"field": "email",
"reason": "格式不正确",
"code": "INVALID_FORMAT"
},
{
"field": "age",
"reason": "必须为整数且大于0",
"code": "OUT_OF_RANGE"
}
]
}
| 校验类型 | HTTP 状态码 | 业务错误码 | 安全合规要求 |
|---|---|---|---|
| 必填参数缺失 | 400 | MISSING_REQUIRED_PARAM |
不暴露参数名以外的信息 |
| 格式校验失败 | 400 | INVALID_FORMAT |
不输出正则表达式 |
| 业务逻辑校验 | 422 | BUSINESS_VALIDATION_ERROR |
不输出数据库查询细节 |
| 安全特征检测 | 400 | MALICIOUS_INPUT_DETECTED |
不输出触发规则的具体关键词 |
3.4 资源不存在类错误码
资源不存在类错误码的核心安全考量是信息泄露防护。标准做法是统一返回 404 Not Found,无论资源是不存在还是无权限访问。集成测试需要验证响应体的一致性,避免通过错误码差异泄露资源存在性。
- 标准状态码:
404 Not Found是唯一推荐的状态码。 - 业务错误码: 推荐使用
RESOURCE_NOT_FOUND或NOT_FOUND,不区分“用户不存在”与“订单不存在”。 - 特殊场景: 某些 RESTful 设计在删除资源时返回
204 No Content,但若资源已不存在则仍应返回404。
集成测试最佳实践: 编写一个通用测试用例,遍历所有 GET/PUT/DELETE 接口,使用不存在的 ID 请求,断言所有接口返回相同的状态码(404)和相同的错误码结构。这能有效防止信息泄露漏洞。
3.5 错误码分类总结与断言矩阵
下表汇总了四类错误码的 HTTP 状态码、业务错误码前缀、安全合规要点与集成测试断言策略,供测试用例设计时直接参考。
| 错误码分类 | HTTP 状态码 | 业务错误码前缀 | 安全合规要点 | 断言策略 |
|---|---|---|---|---|
| 认证类 | 401, 403 | AUTH_, TOKEN_ |
401 必须带 WWW-Authenticate 头;401 与 403 响应体结构一致 | 断言状态码、头字段、错误码前缀 |
| 授权类 | 403, 451 | FORBIDDEN_, SCOPE_ |
区分功能级与数据级授权;数据级授权可能返回 404 | 断言状态码 + 业务错误码 + 权限上下文 |
| 输入校验类 | 400, 422 | VALIDATION_, INVALID_ |
错误信息不泄露内部实现;使用统一错误码结构 | 断言 details 数组结构与字段名 |
| 资源不存在类 | 404 | NOT_FOUND, RESOURCE_ |
统一错误码,不区分资源类型;无权限也返回 404 | 断言所有接口返回相同 404 结构 |
集成测试通用断言模板:
def assert_error_response(resp, expected_status, expected_error_prefix):
assert resp.status_code == expected_status
body = resp.json()
assert "error_code" in body
assert body["error_code"].startswith(expected_error_prefix)
assert "message" in body
# 安全合规:响应体不应包含堆栈或 SQL
assert "Traceback" not in resp.text
assert "SELECT" not in resp.text
assert "INSERT" not in resp.text
通过以上分类与断言策略,集成测试可以在安全合规层面建立统一的错误码断言基线,有效降低因错误码差异导致的信息泄露风险,同时提升测试用例的可维护性与可审计性。