如何用PHP生成AWS SP-API签名?签名不匹配问题求助
AWS SP-API签名生成错误(SignatureDoesNotMatch)排查与修复
问题描述
用POSTMAN调用Amazon SP-API能正常获取数据,但PHP代码生成AWS签名时返回错误:
SignatureDoesNotMatch The computed request signature does not match the signature you gave. Examine your AWS Secret Access Key and signature technique. Details can be found in the service documentation.
已确认凭证准确,怀疑HASH计算或签名流程出错,使用SHA256算法,以下是签名生成代码:
------------------------- 1: Create a Canonical Request for Signature ---------------------------- echo $query_string = 'Action=AssumeRole&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=' . urlencode($access_key . '/' . $sDate . '/us-east-1/sts/aws4_request') . '&X-Amz-Date=' . $sTime . '&X-Amz-SignedHeaders=' . urlencode('content-type;host;x-amz-date'); $canonicalrequest = ""; $canonicalrequest .= "GET" . " "; $canonicalrequest .= "/" . " "; $canonicalrequest .= $query_string . " "; $canonicalrequest .= "Content-Type:application/x-www-form-urlencoded; charset=utf-8" . " "; $canonicalrequest .= "host:sts.amazonaws.com" . " "; $canonicalrequest .= "x-amz-date:" . $sTime . " "; $canonicalrequest .= " "; $canonicalrequest .= "content-type;host;x-amz-date" . " "; $canonicalrequest .= strtolower(bin2hex(hash('sha256', '', true))); ------------------------- 2: Create a String to Sign for Signature ------------------------------ $Algorithm = 'AWS4-HMAC-SHA256'; $RequestDateTime = $sTime; $CredentialScope = $sDate . '/us-east-1/sts/aws4_request'; $HashedCanonicalRequest = strtolower(bin2hex(hash('sha256', $canonicalrequest, true))); $StringToSign = $Algorithm . " " . $RequestDateTime . " " . $CredentialScope . " " . $HashedCanonicalRequest; -------------------------------- 3: Calculate the AWS Signature ----------------------------------- $kSecret = "AWS4" . $secret_access_key; $kDate = hash_hmac('sha256', $sDate, $kSecret, true); $kRegion = hash_hmac('sha256', 'us-east-1', $kDate, true); $kService = hash_hmac('sha256', 'sts', $kRegion, true); $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true); $notHexSignature = hash_hmac('sha256', $StringToSign, $kSigning, true); $signature = strtolower(bin2hex($notHexSignature)); -------------------------- 4: Add the AWS Signature to the Request -------------------------------- $session_token_url = 'https://sts.amazonaws.com?Version=2011-06-15&Action=AssumeRole&RoleSessionName=SessionToken&RoleArn=' . $role_ARN; echo $session_token_url; $session_token_header = ['X-Amz-Date: ' . $sTime, 'Authorization: AWS4-HMAC-SHA256 Credential=' . $access_key . '/' . $sDate . '/us-east-1/sts/aws4_request, SignedHeaders=host;x-amz-date, Signature=' . $signature]; $session_token = api_call_for_session_token($session_token_url, 'GET', '', $session_token_header); function api_call_for_session_token(string $url, string $method = 'GET', $body = null, $header) { $curl = curl_init($url); curl_setopt_array($curl, array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => $header, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false )); $response = curl_exec($curl); $rescode = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE); $err = curl_error($curl); curl_close($curl); $succesRescode = [100, 200, 201, 202, 205, 301]; if ($err || !in_array($rescode, $succesRescode)) { $res = ['status' => false, 'message' => 'api call failed ' . $err, 'rescode' => $rescode, 'data' => json_decode($response, true)]; } else { $res = ['status' => true, 'message' => 'api call success', 'rescode' => $rescode, 'data' => json_decode($response, true)]; } return $response; }
核心错误点分析
- 查询参数不匹配:生成签名时的
$query_string包含X-Amz-Algorithm等签名相关参数,但实际请求URL里没有这些参数,反而多了RoleSessionName,导致规范请求与实际请求结构不一致。 - SignedHeaders前后不一致:规范请求里的SignedHeaders是
content-type;host;x-amz-date,但Authorization头里只写了host;x-amz-date,缺少content-type,二者必须完全匹配。 - 换行符不规范:代码中用
" "添加换行,需确保是LF(\n)而非CRLF(\r\n),AWS SigV4要求所有换行必须用LF。 - 查询参数未排序:AWS要求查询参数按ASCII升序排列,原代码未做排序处理。
修正后的代码示例
// 1. 基础参数定义 $sDate = gmdate('Ymd'); $sTime = gmdate('Ymd\THis\Z'); $access_key = '你的AccessKey'; $secret_access_key = '你的SecretKey'; $role_ARN = '你的RoleARN'; $region = 'us-east-1'; $service = 'sts'; // 2. 构建实际请求的查询参数(与规范请求保持一致) $query_params = [ 'Action' => 'AssumeRole', 'Version' => '2011-06-15', 'RoleSessionName' => 'SessionToken', 'RoleArn' => $role_ARN ]; // 按ASCII升序排序查询参数(AWS强制要求) ksort($query_params); $query_string = http_build_query($query_params); // 3. 构建规范请求 $canonical_method = 'GET'; $canonical_uri = '/'; $canonical_headers = [ "content-type:application/x-www-form-urlencoded; charset=utf-8", "host:sts.amazonaws.com", "x-amz-date:$sTime" ]; $signed_headers = 'content-type;host;x-amz-date'; // 空请求体的SHA256哈希 $payload_hash = strtolower(bin2hex(hash('sha256', '', true))); // 拼接规范请求,严格使用LF换行 $canonical_request = implode("\n", [ $canonical_method, $canonical_uri, $query_string, implode("\n", $canonical_headers), "", // 空行分隔请求头与SignedHeaders $signed_headers, $payload_hash ]); // 4. 构建待签字符串 $algorithm = 'AWS4-HMAC-SHA256'; $credential_scope = "$sDate/$region/$service/aws4_request"; $hashed_canonical_request = strtolower(bin2hex(hash('sha256', $canonical_request, true))); $string_to_sign = implode("\n", [ $algorithm, $sTime, $credential_scope, $hashed_canonical_request ]); // 5. 计算签名 $k_secret = "AWS4$secret_access_key"; $k_date = hash_hmac('sha256', $sDate, $k_secret, true); $k_region = hash_hmac('sha256', $region, $k_date, true); $k_service = hash_hmac('sha256', $service, $k_region, true); $k_signing = hash_hmac('sha256', 'aws4_request', $k_service, true); $signature = strtolower(bin2hex(hash_hmac('sha256', $string_to_sign, $k_signing, true))); // 6. 构建请求头 $authorization_header = "$algorithm Credential=$access_key/$credential_scope, SignedHeaders=$signed_headers, Signature=$signature"; $headers = [ "X-Amz-Date: $sTime", "Content-Type: application/x-www-form-urlencoded; charset=utf-8", "Authorization: $authorization_header" ]; // 7. 发起请求 $session_token_url = "https://sts.amazonaws.com?$query_string"; $session_token = api_call_for_session_token($session_token_url, 'GET', '', $headers); // 请求函数保持不变 function api_call_for_session_token(string $url, string $method = 'GET', $body = null, $header) { $curl = curl_init($url); curl_setopt_array($curl, array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => $header, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false )); $response = curl_exec($curl); $rescode = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE); $err = curl_error($curl); curl_close($curl); $succesRescode = [100, 200, 201, 202, 205, 301]; if ($err || !in_array($rescode, $succesRescode)) { $res = ['status' => false, 'message' => 'api call failed ' . $err, 'rescode' => $rescode, 'data' => json_decode($response, true)]; } else { $res = ['status' => true, 'message' => 'api call success', 'rescode' => $rescode, 'data' => json_decode($response, true)]; } return $response; }
关键注意事项
- 所有参与签名的元素(请求方法、URI、查询参数、请求头等)必须与实际请求完全一致。
- 查询参数必须按ASCII升序排序,否则签名计算会出错。
- 全程使用LF(
\n)作为换行符,避免使用Windows系统默认的CRLF。 - SignedHeaders必须包含所有参与签名的请求头,且顺序与规范请求中的请求头顺序一致。
内容的提问来源于stack exchange,提问作者Testing Web
相关产品推荐
相关产品推荐

