AWS SignatureV4请求签名偶尔不匹配问题排查咨询
问题现象
使用AWS SDK的SignatureV4对IAM授权的API Gateway发起请求时,签名仅在部分场景生效。相同凭证、相同请求头的情况下,仅URL中的userId[]参数组合不同会导致签名失败,且失败场景可稳定复现。
签名代码
$credentials = new Credentials(env('AWS_ACCESS_KEY_ID', ''), env('AWS_SECRET_ACCESS_KEY', '')); $signer = new SignatureV4('execute-api', env('AWS_DEFAULT_REGION', '')); $request = $signer->signRequest($request, $credentials);
请求示例(脱敏后)
- 成功请求URL:
https://[redacted].execute-api.eu-west-1.amazonaws.com/dev-ew1/api/v1/users?userId[]=34129&userId[]=2405&userType=student&oktaId=[redacted]
- 失败请求URL:
https://[redacted].execute-api.eu-west-1.amazonaws.com/dev-ew1/api/v1/users?userId[]=2405&userId[]=196624&userType=student&oktaId=[redacted]
失败错误信息
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method.
Consult the service documentation for details.The Canonical String for this request should have been
'GET
/dev-ew1/api/v1/users
oktaId=[redacted]&userId%5B%5D=196624&userId%5B%5D=2405&userType=student
host:[redacted].execute-api.eu-west-1.amazonaws.com
x-amz-date:20231218T101701Z
x-platform:messaging
x-platform-organisation:55host;x-amz-date;x-platform;x-platform-organisation
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'The String-to-Sign should have been
'AWS4-HMAC-SHA256
20231218T101701Z
20231218/eu-west-1/execute-api/aws4_request
3400b7c56695b319d050d264522b645bf19346449c2147768fc0ca10b36001cf'
请求头(成功/失败完全一致)
{ "Host": [ "[redacted].execute-api.eu-west-1.amazonaws.com" ], "X-Platform": [ "messaging" ], "X-Platform-Organisation": [ "55" ], }
场景复现规律
- 单个
userId(2405或196624):请求正常 userId=2405+196624组合:签名错误userId=2405+34129组合:请求正常
环境:PHP 8.1、AWS SDK 3.294.1
排查与解决步骤
1. 强制标准化查询参数排序
AWS SignatureV4要求Canonical Query String必须按参数名ASCII升序排列,数组参数的多个值也要按值升序排列。从错误信息的Canonical String可以看到,userId数组被排序为userId%5B%5D=196624&userId%5B%5D=2405,但请求URL中的顺序是userId[]=2405&userId[]=196624,这是签名不匹配的核心原因。
在构建请求前主动标准化参数:
// 假设$params是查询参数数组 ksort($params); // 按参数名升序排序 foreach ($params as $key => $value) { if (is_array($value)) { sort($value); // 数组参数内部按值升序排序 $params[$key] = $value; } } // 使用排序后的参数构建请求对象
2. 确认请求对象的构建方式
避免直接拼接URL字符串,必须通过AWS SDK或Guzzle的请求对象传递参数,确保SDK能自动处理参数排序和编码:
// 正确示例:用数组传递查询参数 $request = new \GuzzleHttp\Psr7\Request( 'GET', 'https://[redacted].execute-api.eu-west-1.amazonaws.com/dev-ew1/api/v1/users', $headers, null, ['query' => $params] // 传入排序后的参数数组 );
3. 验证签名前的参数状态
签名前打印请求对象的查询参数,确认是否已被正确排序:
// 签名前输出查询参数 var_dump($request->getQuery()->toArray());
4. 检查URL编码一致性
确保参数编码符合RFC 3986标准(如[]编码为%5B%5D,空格编码为%20而非+)。直接拼接URL容易出现编码不一致问题,依赖SDK处理编码更可靠。
5. 升级AWS SDK版本
当前使用的3.294.1版本可能存在数组参数排序的已知bug,尝试升级到最新稳定版,修复潜在的SDK逻辑问题。
6. 本地复现Canonical String生成
手动按照AWS SignatureV4规则生成Canonical String,与错误信息中的内容对比,定位具体差异点:
- 确定HTTP方法、Canonical URI
- 按规则生成Canonical Query String(参数名升序,数组值升序)
- 生成Canonical Headers(头名升序)
- 生成Signed Headers、Payload Hash
- 对比自己生成的字符串与错误信息中的内容,确认参数排序、编码是否一致
内容的提问来源于stack exchange,提问作者ComputerUser

