PHP 怎么PHP AWS Signature V4

wen PHP项目 2

本文目录导读:

PHP 怎么PHP AWS Signature V4

  1. 使用 AWS SDK(推荐)
  2. 手动实现 Signature V4
  3. 使用 Composer 包
  4. 注意事项
  5. 调试技巧

在 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

注意事项

  1. 时间同步:确保服务器时间与 AWS 服务同步(使用 NTP)
  2. 安全存储:永远不要硬编码凭证,使用环境变量或 AWS Secrets Manager
  3. 区域选择:根据实际服务区域配置 'region' 参数
  4. 服务端点:不同服务有不同的端点和签名规则

调试技巧

// 查看签名过程
echo "Canonical Request:\n$canonicalRequest\n\n";
echo "String to Sign:\n$stringToSign\n\n";
echo "Authorization Header:\n" . $headers['Authorization'] . "\n";

使用 AWS SDK 是最安全和可靠的方式,只有在特殊需求下才建议手动实现签名。

抱歉,评论功能暂时关闭!