You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 05:31:10