APIG 返回 401/403 的三层排查:Token、签名、策略
APIG 返回 401/403 的三层排查:Token、签名、策略
> 实测环境:APIG 2.0 控制台发布 REST API,Python 3.10 + requests 调测;错误样本来自真实联调时客户端与服务端互推责任的场景。
场景:一次让前后端吵起来的 401/403
联调阶段客户端调 APIG 发布的接口,响应码在 401 和 403 之间跳:同一个接口,本地 curl 能通,服务端定时任务里就挂;刚配好 AppCode 能通,过一小时又 403。前后端各查各的,谁都没错,但接口就是调不通。
这类问题只要分三层排查,基本 10 分钟定位:<strong>第一层看认证(Token 有没有、过期没),第二层看签名(AK/SK 签名对不对),第三层看策略(这个调用者有没有权限、有没有触发流控)</strong>。401 和 403 的语义不同,先分清:401 表示"你谁?没带或带了无效凭证";403 表示"知道你是谁,但你不被允许"。
第一层:认证(Token)——报 401 先看这里
APIG 的 APP 认证下,调用方要么带 AppCode,要么用 AK/SK 签名;IAM 认证下则带 Token。401 的常见原因按概率排:
1. <strong>Token 过期</strong>:IAM Token 默认有效期 24 小时(可配置),定时任务如果缓存了 Token 而没做刷新逻辑,跨天后必然 401。 2. <strong>Token 带错位置</strong>:应放在 <code>Authorization: Bearer <token></code>,放成自定义 header 或在 URL 里拼,网关不认。 3. <strong>Token 作用域不匹配</strong>:用 A 项目下的 Token 调 B 项目的 API,网关验签域不一致直接 401。
复现命令:
# 带 Token 调 APIG 发布的 API
curl -i -X GET "https://{api-endpoint}/v1/hello" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
# 只看响应头,重点看 X-Request-Id,这是后续查网关日志的凭证
curl -si -X GET "https://{api-endpoint}/v1/hello" \
-H "Authorization: Bearer $TOKEN" 2>&1 | grep -iE "HTTP/|x-request-id"
如果换一个<strong>刚签发</strong>的 Token 立刻变通,结论就是 Token 过期/缓存问题,去查定时任务里的 Token 刷新逻辑,而不是网关配置。
第二层:签名(AK/SK)——curl 通、代码里挂的典型原因
用 AK/SK 做 APP 认证时,APIG 要求按规范对请求做 HMAC-SHA256 签名,参与签名的要素是:<strong>请求方法、URI、query、参与签名的 header(host、x-sdk-date、content-type 等)、请求体哈希、时间戳</strong>。签名错的经典表现:浏览器/简单工具里能通(因为它们不带签名或用的是 AppCode),而带签名的 SDK 调用 403——注意这里报 403 而非 401,因为凭证本身有效,是"签名与请求不一致"被网关判定为篡改。
排查时先做最小验证:把请求体 body 设为空、把自定义 header 全部去掉,只留 <code>host</code> 和 <code>x-sdk-date</code>,再生成签名。下面是一个可独立运行的最小签名示例(HMAC-SHA256 流程与华为云 API 签名指南一致):
import hashlib
import hmac
import datetime
import requests
AK, SK = "your-ak", "your-sk" # 从 APIG 应用凭据页获取
METHOD, HOST = "GET", "your-api.example.com"
URI, QUERY = "/v1/hello", ""
def sign(ak, sk, method, host, uri, query, body=""):
t = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
payload_hash = hashlib.sha256(body.encode()).hexdigest()
signed_headers = "host;x-sdk-date"
canonical_headers = f"host:{host}\nx-sdk-date:{t}\n"
canonical_request = "\n".join([method, uri, query,
canonical_headers, signed_headers, payload_hash])
string_to_sign = "\n".join(["HMAC-SHA256", t,
hashlib.sha256(canonical_request.encode()).hexdigest()])
sig = hmac.new(sk.encode(), string_to_sign.encode(), hashlib.sha256).hexdigest()
return t, sig
t, sig = sign(AK, SK, METHOD, HOST, URI, QUERY)
resp = requests.get(f"https://{HOST}{URI}", headers={
"Host": HOST,
"x-sdk-date": t,
"Authorization": f"SDK-HMAC-SHA256 Access={AK}, SignedHeaders=host;x-sdk-date, Signature={sig}",
})
print(resp.status_code, resp.text[:300])
这段代码把签名要素收敛到最小集合,如果它通了而你的业务代码不通,就逐步加回业务 header 和请求体,加一个测一个——大概率是<strong>某个自定义 header 忘了声明进 SignedHeaders</strong>,或<strong>请求体经过序列化后与签名时不一致</strong>(比如 <code>json.dumps</code> 的空白差异)。
第三层:策略(授权/访问控制)——403 的最后一站
签名和 Token 都没问题但仍 403,进第三层,按顺序查四项:
1. <strong>调用者授权</strong>:APP 认证下,这个 APP 是否已被授权调用该 API(未授权典型报错 <code>APIC.0302</code> 之类,以实际返回体为准)。去 APIG 控制台该 API 的"授权管理"里核对 APP 与 API 的绑定关系。 2. <strong>访问控制</strong>:API 级访问控制策略(IP 黑白名单 / 账号黑白名单)。定时任务跑在跳板机、本地 curl 跑在办公网,IP 不同,策略命中结果就不同——这解释了"本地通、服务器挂"。 3. <strong>流控</strong>:请求频次超过流控阈值时 APIG 会拒绝。特征是<strong>偶发</strong> 403/429,且集中在一段时间内高频调用之后。查流控策略的每秒/每分钟配额。 4. <strong>环境与发布</strong>:确认调的是<strong>已发布到对应环境(RELEASE)</strong>的 API,未发布的 API 会返回 404 或 403。
每步都留证据,用响应头和请求 ID 说话:
# 403 时记录完整响应头与 body,X-Request-Id 是查网关日志的关键
curl -si -X GET "https://{api-endpoint}/v1/hello" \
-H "Authorization: Bearer $TOKEN" -o resp.txt -D head.txt
grep -iE "x-request-id|HTTP/" head.txt
cat resp.txt
收尾:三层排查的顺序就是最优路径
遇到 APIG 401/403,按"认证 → 签名 → 策略"走,<strong>不要先动网关配置</strong>:换新 Token 测一次(排除认证层),再用上面的最小签名脚本测一次(排除签名层),最后核对授权、访问控制、流控(策略层)。三步都做一遍,80% 的问题在第二层就能定位——因为"curl 能通、代码挂"几乎都是签名细节问题,而"本地通、服务器挂"几乎都是访问控制或 Token 缓存问题。
小结
401/403 不是玄学:401 是"凭证无效",先查 Token 过期与携带位置;403 是"凭证有效但被拒",先查签名一致性,再查授权与策略。把排查动作收敛成可复现命令,每次留 <code>X-Request-Id</code>,问题就有据可查、有迹可循。
标签:APIG、API、认证
- 点赞
- 收藏
- 关注作者
评论(0)