本文目录导读:

在 PHP 中实现 AWS Signature V4 签名,主要有两种方式:
使用 AWS SDK(推荐)
最简单可靠的方式是使用 AWS SDK for PHP:
<?php
require 'vendor/autoload.php';
use Aws\Signature\SignatureV4;
use Aws\Credentials\Credentials;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
// 创建凭证
$credentials = new Credentials(
'YOUR_ACCESS_KEY',
'YOUR_SECRET_KEY'
);
// 创建签名器
$signature = new SignatureV4('s3', 'us-east-1');
// 创建请求
$request = new Request(
'GET',
'https://s3.us-east-1.amazonaws.com/my-bucket/my-object'
);
// 签名请求
$signedRequest = $signature->signRequest($request, $credentials);
// 发送请求
$client = new Client();
$response = $client->send($signedRequest);
echo $response->getBody();
手动实现 Signature V4
如果需要手动实现,以下是核心步骤:
<?php
class AwsSignatureV4
{
private string $accessKey;
private string $secretKey;
private string $region;
private string $service;
public function __construct(
string $accessKey,
string $secretKey,
string $region,
string $service
) {
$this->accessKey = $accessKey;
$this->secretKey = $secretKey;
$this->region = $region;
$this->service = $service;
}
public function sign(
string $method,
string $url,
array $headers = [],
string $body = ''
): array {
$parsedUrl = parse_url($url);
$timestamp = gmdate('Ymd\THis\Z');
$date = substr($timestamp, 0, 8);
// 1. 创建规范请求
$canonicalRequest = $this->createCanonicalRequest(
$method,
$parsedUrl['path'] ?? '/',
$parsedUrl['query'] ?? '',
$headers,
$body
);
// 2. 创建签名字符串
$credentialScope = $date . '/' . $this->region . '/' . $this->service . '/aws4_request';
$stringToSign = "AWS4-HMAC-SHA256\n{$timestamp}\n{$credentialScope}\n" .
hash('sha256', $canonicalRequest);
// 3. 计算签名
$signingKey = $this->getSignatureKey(
$this->secretKey,
$date,
$this->region,
$this->service
);
$signature = hash_hmac('sha256', $stringToSign, $signingKey);
// 4. 添加 Authorization 头
$headers['Authorization'] = sprintf(
'AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, ' .
'SignedHeaders=%s, Signature=%s',
$this->accessKey,
$date,
$this->region,
$this->service,
$this->getSignedHeaders($headers),
$signature
);
$headers['x-amz-date'] = $timestamp;
return $headers;
}
private function createCanonicalRequest(
string $method,
string $path,
string $query,
array $headers,
string $body
): string {
// 规范 URI
$canonicalUri = $this->normalizePath($path);
// 规范查询字符串
$canonicalQueryString = $this->normalizeQueryString($query);
// 规范头
$canonicalHeaders = $this->getCanonicalHeaders($headers);
$signedHeaders = $this->getSignedHeaders($headers);
// 请求体哈希
$payloadHash = hash('sha256', $body);
return "{$method}\n{$canonicalUri}\n{$canonicalQueryString}\n" .
"{$canonicalHeaders}\n{$signedHeaders}\n{$payloadHash}";
}
private function normalizePath(string $path): string
{
// 对路径进行 URI 编码,但保留正斜杠
$parts = explode('/', $path);
$normalized = [];
foreach ($parts as $part) {
if ($part !== '') {
$normalized[] = rawurlencode(rawurldecode($part));
}
}
return '/' . implode('/', $normalized);
}
private function normalizeQueryString(string $query): string
{
if (empty($query)) {
return '';
}
parse_str($query, $params);
ksort($params);
$pairs = [];
foreach ($params as $key => $value) {
$pairs[] = rawurlencode($key) . '=' . rawurlencode($value);
}
return implode('&', $pairs);
}
private function getCanonicalHeaders(array $headers): string
{
$canonicalHeaders = [];
foreach ($headers as $key => $value) {
$lowerKey = strtolower($key);
$canonicalHeaders[$lowerKey] = $lowerKey . ':' . trim($value);
}
ksort($canonicalHeaders);
return implode("\n", $canonicalHeaders) . "\n";
}
private function getSignedHeaders(array $headers): string
{
$signedHeaders = [];
foreach ($headers as $key => $value) {
$signedHeaders[] = strtolower($key);
}
sort($signedHeaders);
return implode(';', $signedHeaders);
}
private function getSignatureKey(
string $key,
string $dateStamp,
string $regionName,
string $serviceName
): string {
$kDate = hash_hmac('sha256', $dateStamp, 'AWS4' . $key, true);
$kRegion = hash_hmac('sha256', $regionName, $kDate, true);
$kService = hash_hmac('sha256', $serviceName, $kRegion, true);
$kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
return $kSigning;
}
}
// 使用示例
$signer = new AwsSignatureV4(
'YOUR_ACCESS_KEY',
'YOUR_SECRET_KEY',
'us-east-1',
's3'
);
$headers = $signer->sign(
'GET',
'https://s3.us-east-1.amazonaws.com/my-bucket/my-object?versionId=123',
[
'Host' => 's3.us-east-1.amazonaws.com',
]
);
// 使用 cURL 发送请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://s3.us-east-1.amazonaws.com/my-bucket/my-object');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
使用 Composer 包
也可以使用专门的签名包:
composer require aws/aws-sdk-php # 或者轻量级方案 composer require async-aws/core
注意事项
- 时间同步:确保服务器时间与 AWS 服务同步(使用 NTP)
- 安全存储:永远不要硬编码凭证,使用环境变量或 AWS Secrets Manager
- 区域选择:根据实际服务区域配置 'region' 参数
- 服务端点:不同服务有不同的端点和签名规则
调试技巧
// 查看签名过程 echo "Canonical Request:\n$canonicalRequest\n\n"; echo "String to Sign:\n$stringToSign\n\n"; echo "Authorization Header:\n" . $headers['Authorization'] . "\n";
使用 AWS SDK 是最安全和可靠的方式,只有在特殊需求下才建议手动实现签名。