签署和对 REST 请求进行身份验证 - Amazon Simple Storage Service
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

签署和对 REST 请求进行身份验证

注意

本主题说明使用签名版本 2 对请求进行身份验证。Amazon S3 现在支持最新的 Signature Version 4。所有区域都支持此最新签名版本,而 2014 年 1 月 30 日之后的所有新区域都只支持签名版本 4。有关更多信息,请转至《Amazon Simple Storage Service API 参考》中的验证请求 (Amazon Signature Version 4)

身份验证是指向系统证明身份的过程。身份对于 Amazon S3 访问控制决策非常重要。将根据请求者的身份允许请求或拒绝部分请求。例如,将为已注册的开发人员保留创建存储桶的权利,并且 (默认) 将为相关的存储桶拥有者保留在存储桶中创建对象的权利。作为开发人员,您需要提出请求来调用这些权限,因此您需要对您的请求进行身份验证以向系统证明您的身份。本节向您演示了应如何进行操作。

注意

本节中的内容不适用于 HTTP POST。有关更多信息,请参阅 使用 POST (Amazon Signature Version 2) 的基于浏览器的上传

Amazon S3 REST API 使用基于密钥 HMAC (Hash Message Authentication Code) 的自定义 HTTP 方案进行身份验证。要对请求进行身份验证,您首先需要合并请求的选定元素以形成一个字符串。然后,您可以使用 Amazon 秘密访问密钥来计算该字符串的 HMAC。通常我们将此过程称为“签署请求”并且我们将输出 HMAC 算法称为“签名”,因为它会模拟真实签名的安全属性。最后,您可以使用本部分中介绍的语法,作为请求的参数添加此签名。

系统收到经身份验证的请求时,将提取您申领的 Amazon 秘密访问密钥,并以相同的使用方式将它用于计算已收到的消息的签名。然后,它会将计算出的签名与请求者提供的签名进行对比。如果两个签名相匹配,则系统认为请求者必须拥有对 Amazon 秘密访问密钥的访问权限,因此充当向其颁发密钥的委托人的颁发机构。如果两个签名不匹配,那么请求将被丢弃,同时系统将返回错误消息。

例 经身份验证的 Amazon S3 REST 请求
GET /photos/puppy.jpg HTTP/1.1 Host: awsexamplebucket1.us-west-1.s3.amazonaws.com Date: Tue, 27 Mar 2007 19:36:42 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE: qgk2+6Sv9/oM7G3qLEjTH1a1l1g=

使用临时安全凭证

如果您使用临时安全凭证签署请求 (参阅 提出请求),您必须通过添加 x-amz-security-token 标头将相应的安全令牌包含在您的请求中。

使用 Amazon Security Token Service API 获取临时安全凭证时,响应包含临时安全凭证和会话令牌。向 Amazon S3 发送请求时,您可以在 x-amz-security-token 标头中提供会话令牌值。有关 IAM 提供的 Amazon Security Token Service API 的信息,请转到《Amazon Security Token Service API 参考指南》中的操作

身份验证标头

Amazon S3 REST API 使用标准的 HTTP Authorization 标头来传递身份验证信息。(标准标头的名称是不可取的,因为它承载的是身份验证信息,而不是授权。) 在 Amazon S3 身份验证方案下,授权标头具有以下形式:

Authorization: AWS AWSAccessKeyId:Signature

注册后,会向开发人员发放 Amazon 访问密钥 ID 和 Amazon 秘密访问密钥。对于请求身份验证,AWSAccessKeyId 元素将标识用于计算签名的访问密钥 ID 和进行请求的开发人员 (间接)。

Signature 元素是请求中选定元素的 RFC 2104HMAC-SHA1,因此授权标头的 Signature 部分会因请求的不同而各异。如果系统计算出的请求签名与请求随附的 Signature 相匹配,则请求者拥有经证明的 Amazon 秘密访问密钥。然后,在该身份下,借助获得此密钥的开发人员的授权来处理请求。

以下是说明 Authorization 请求标头结构的伪语法。(在该示例中,\n 表示 Unicode 码位 U+000A,这通常称为换行符)。

Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature; Signature = Base64( HMAC-SHA1( UTF-8-Encoding-Of(YourSecretAccessKey), UTF-8-Encoding-Of( StringToSign ) ) ); StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + CanonicalizedAmzHeaders + CanonicalizedResource; CanonicalizedResource = [ "/" + Bucket ] + <HTTP-Request-URI, from the protocol name up to the query string> + [ subresource, if present. For example "?acl", "?location", or "?logging"]; CanonicalizedAmzHeaders = <described below>

HMAC-SHA1 是由 RFC 2104(用于消息身份验证的哈希密钥)定义的算法。该算法要求输入两个字节字符串:一个密钥和一个消息。对于 Amazon S3 请求身份验证,请将您的 Amazon 秘密访问密钥 (YourSecretAccessKey) 用作密钥,并将 UTF-8 编码格式的 StringToSign 用作消息。HMAC-SHA1 的输出也是字节字符串,称为摘要。通过对此摘要进行 Base64 编码来构成 Signature 请求参数。

用于签名的请求规范

在系统收到经身份验证的请求时撤销,系统会将计算出的请求签名与 StringToSign 中提供的签名进行对比。基于此原因,您必须采用 Amazon S3 使用的相同方法计算签名。根据适用于签名的统一格式放置请求的过程称为标准化

构建 CanonicalizedResource 元素

CanonicalizedResource 表示请求的目标 Amazon S3 资源。为 REST 请求构建它,如下所示:

1

使用空字符串 ("") 启动。

2

如果请求使用 HTTP 主机标头 (虚拟托管类型) 来指定请求,请在 "/" (例如,“/bucketname”) 的后面附加存储桶名称。对于路径类型请求和不会寻址存储桶的请求,不执行任何操作。有关虚拟托管类型请求的更多信息,请参阅存储桶的虚拟托管

对于虚拟托管类型请求“https://awsexamplebucket1.s3.us-west-1.amazonaws.com/photos/puppy.jpg”,CanonicalizedResource 是“/awsexamplebucket1”。

对于路径类型请求“https://s3.us-west-1.amazonaws.com/awsexamplebucket1/photos/puppy.jpg”,CanonicalizedResource 为 ""。

3

附加未解码的 HTTP 请求-URI 的路径部分 (取决于但不包括查询字符串)。

对于虚拟托管类型请求“https://awsexamplebucket1.s3.us-west-1.amazonaws.com/photos/puppy.jpg”,CanonicalizedResource 是“/awsexamplebucket1/photos/puppy.jpg”。

对于路径类型的请求,“https://s3.us-west-1.amazonaws.com/awsexamplebucket1/photos/puppy.jpg”,CanonicalizedResource 是“/awsexamplebucket1/photos/puppy.jpg”。此时,虚拟托管类型和路径类型请求的 CanonicalizedResource 相同。

对于不寻址存储桶的请求(例如,GET Service),请附加“/”。

4

如果请求将寻址子资源(例如,?versioning?location?acl?lifecycle?versionid),请附加子资源、其值(如果有)和问号。请注意,如果存在多个子资源,子资源必须按子资源名称的字典顺序排序并使用“&”进行分隔 (例如,?acl&versionId=)。

构建 CanonicalizedResource 元素时必须包含的子资源为:acl、lifecycle、location、logging、notification、partNumber、policy、requestPayment、uploadId、uploads、versionId、versioning、versions 和 website。

如果请求指定覆盖响应标头值的查询字符串参数(请参阅 Get Object),请附加查询字符串参数及其值。签名时,请勿对这些值进行编码;但是进行请求时,您必须对这些参数值进行编码。GET 请求中的查询字符串参数包括 response-content-typeresponse-content-languageresponse-expiresresponse-cache-controlresponse-content-dispositionresponse-content-encoding

为多对象删除请求创建 CanonicalizedResource 时,必须包括 delete 查询字符串参数。

必须按照元素在 HTTP 请求中的显示方式,使用字母 (包括 URL 编码元字符) 签署来自 HTTP 请求-URI 的 CanonicalizedResource 元素。

CanonicalizedResource 可能与 HTTP 请求-URI 不同。尤其是,如果您的请求使用 HTTP Host 标头来指定存储桶,存储桶不会在 HTTP 请求 URI 中显示。但是,CanonicalizedResource 仍然会包含该存储桶。查询字符串参数可能也会在请求-URI 中显示,但是不会包括在 CanonicalizedResource 中。有关更多信息,请参阅 存储桶的虚拟托管

构建 CanonicalizedAmzHeaders 元素

要构建 StringToSign 的 CanonicalizedAmzHeaders 部分,请选择所有以“x-amz-”开头的 HTTP 请求标头 (使用不区分大小写的对比方式) 并遵循以下步骤。

1 将每个 HTTP 标头名称转换为小写。例如,“X-Amz-Date”改为“x-amz-date”。
2 根据标头名称按字典顺序排列标头集。
3 将相同名称的标头字段合并为一个“header-name:comma-separated-value-list”对,并按照 RFC 2616 中第 4.2 节中的规定,两个值之间不留空格。例如,可以将元数据标头“x-amz-meta-username: fred”和“x-amz-meta-username: barney”合并为单个标头“x-amz-meta-username: fred,barney”。
4 通过将折叠空格(包括新建行)替换为单个空格,“展开”跨多个行的长标头(按照 RFC 2616 中第 4.2 节允许的方式)。
5 删除标头中冒号周围的空格。例如,标头“x-amz-meta-username: fred,barney”改为“x-amz-meta-username:fred,barney”。
6 最后,请向生成的列表中的每个标准化标头附加换行字符 (U+000A)。通过将此列表中所有的标头规范化为单个字符串,构建 CanonicalizedResource 元素。

位置和命名 HTTP 标头 StringToSign 元素的对比

StringToSign 的头几个标头元素(Content-Type、Date 和 Content-MD5)属于位置标头。StringToSign 不包括这些标头的名称,仅包括它们在请求中的值。相反,“x-amz-”元素会进行命名。标头名称和标头值都会在 StringToSign 中显示。

如果在 StringToSign 定义中调用的位置标头不在请求中 (例如,Content-TypeContent-MD5 对于 PUT 请求是可选的,并且对于 GET 请求没有任何意义),请使用空字符串 ("") 替换该位置。

时间戳要求

有效的时间戳对于经身份验证的请求是必须的 (使用 HTTP Date 标头或 x-amz-date 替代项)。此外,经身份验证的请求随附的客户端时间戳必须处于收到请求时的 Amazon S3 系统时间的 15 分钟之内。否则,请求将失败并出现 RequestTimeTooSkewed 错误代码。施加这些限制的目的是为了防止对方重新使用已拦截的请求。要更好地防范窃听,请对经身份验证的请求使用 HTTPS 传输。

注意

对请求日期的验证限制仅适用于不使用查询字符串身份验证的经身份验证的请求。有关更多信息,请参阅 查询字符串请求身份验证替代项

某些 HTTP 客户端库不提供为请求设置 Date 标头的功能。如果您在标准化标头中包含“Date”标头的值时遇到困难,您可以改用“x-amz-date”标头为请求设置时间戳。x-amz-date 标头的值必须采用其中一种 RFC 2616 格式 (http://www.ietf.org/rfc/rfc2616.txt)。x-amz-date 标头位于请求中时,系统将在计算请求签名时忽略任何 Date 标头。因此,如果包含了 x-amz-date 标头,请在构建 Date 时使用 StringToSign 的空字符串。有关示例,请参阅下一节。

身份验证示例

本节中的示例使用下表中的 (非有效) 凭证。

参数
AWSAccessKeyId AKIAIOSFODNN7EXAMPLE
AWSSecretAccessKey wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

在示例 StringToSign 中,格式并不重要;\n 是指 Unicode 码位 U+000A,通常称为换行符。此外,该示例使用“+0000”指定时区。您可以改用“GMT”来指定时区,但是在示例中显示的签名将有所不同。

对象 GET

此示例将从 awsexamplebucket1 存储桶获取一个对象。

请求 StringToSign
GET /photos/puppy.jpg HTTP/1.1 Host: awsexamplebucket1.us-west-1.s3.amazonaws.com Date: Tue, 27 Mar 2007 19:36:42 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE: qgk2+6Sv9/oM7G3qLEjTH1a1l1g=
GET\n \n \n Tue, 27 Mar 2007 19:36:42 +0000\n /awsexamplebucket1/photos/puppy.jpg

请注意,CanonicalizedResource 包括存储桶名称,但 HTTP 请求 URI 不包括。(存储桶由主机标头指定)。

注意

以下 Python 脚本使用提供的参数来计算前面的签名。您可以使用此脚本来构建您自己的签名,并根据需要替换密钥和 StringToSign。

import base64 import hmac from hashlib import sha1 access_key = 'AKIAIOSFODNN7EXAMPLE'.encode("UTF-8") secret_key = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'.encode("UTF-8") string_to_sign = 'GET\n\n\nTue, 27 Mar 2007 19:36:42 +0000\n/awsexamplebucket1/photos/puppy.jpg'.encode("UTF-8") signature = base64.b64encode( hmac.new( secret_key, string_to_sign, sha1 ).digest() ).strip() print(f"AWS {access_key.decode()}:{signature.decode()}")

对象 PUT

此示例将向 awsexamplebucket1 存储桶放入一个对象。

请求 StringToSign
PUT /photos/puppy.jpg HTTP/1.1 Content-Type: image/jpeg Content-Length: 94328 Host: awsexamplebucket1.s3.us-west-1.amazonaws.com Date: Tue, 27 Mar 2007 21:15:45 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE: iqRzw+ileNPu1fhspnRs8nOjjIA=
PUT\n \n image/jpeg\n Tue, 27 Mar 2007 21:15:45 +0000\n /awsexamplebucket1/photos/puppy.jpg

请注意请求和 StringToSign 中的 Content-Type 标头。另请注意,由于 Content-MD5 不在请求中,它在 StringToSign 中将保留为空白。

列出

此示例将列出 awsexamplebucket1 存储桶的内容。

请求 StringToSign
GET /?prefix=photos&max-keys=50&marker=puppy HTTP/1.1 User-Agent: Mozilla/5.0 Host: awsexamplebucket1.s3.us-west-1.amazonaws.com Date: Tue, 27 Mar 2007 19:42:41 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE: m0WP8eCtspQl5Ahe6L1SozdX9YA=
GET\n \n \n Tue, 27 Mar 2007 19:42:41 +0000\n /awsexamplebucket1/

请注意 CanonicalizedResource 上的尾部斜杠以及查询字符串参数的缺失。

取回

本示例将为“awsexamplebucket1”存储桶提取访问控制策略子资源。

请求 StringToSign
GET /?acl HTTP/1.1 Host: awsexamplebucket1.s3.us-west-1.amazonaws.com Date: Tue, 27 Mar 2007 19:44:46 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE: 82ZHiFIjc+WbcwFKGUVEQspPn+0=
GET\n \n \n Tue, 27 Mar 2007 19:44:46 +0000\n /awsexamplebucket1/?acl

请注意子资源查询字符串参数包含在 CanonicalizedResource 中的方式。

删除

本示例使用路径类型和日期替代项从“awsexamplebucket1”存储桶删除对象。

请求 StringToSign
DELETE /awsexamplebucket1/photos/puppy.jpg HTTP/1.1 User-Agent: dotnet Host: s3.us-west-1.amazonaws.com Date: Tue, 27 Mar 2007 21:20:27 +0000 x-amz-date: Tue, 27 Mar 2007 21:20:26 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE:XbyTlbQdu9Xw5o8P4iMwPktxQd8=
DELETE\n \n \n Tue, 27 Mar 2007 21:20:26 +0000\n /awsexamplebucket1/photos/puppy.jpg

请注意我们如何使用“x-amz-date”替代方法来指定日期 (假设是因为我们的客户端库阻止我们设置日期)。在这种情况下,x-amz-date 优先于 Date 标头。因此,签名中的日期条目必须包含 x-amz-date 标头的值。

上传

本示例将对象上传到使用元数据的别名记录虚拟托管类型存储桶。

请求 StringToSign
PUT /db-backup.dat.gz HTTP/1.1 User-Agent: curl/7.15.5 Host: static.example.com:8080 Date: Tue, 27 Mar 2007 21:06:08 +0000 x-amz-acl: public-read content-type: application/x-download Content-MD5: 4gJE4saaMU4BqNR0kLY+lw== X-Amz-Meta-ReviewedBy: joe@example.com X-Amz-Meta-ReviewedBy: jane@example.com X-Amz-Meta-FileChecksum: 0x02661779 X-Amz-Meta-ChecksumAlgorithm: crc32 Content-Disposition: attachment; filename=database.dat Content-Encoding: gzip Content-Length: 5913339 Authorization: AWS AKIAIOSFODNN7EXAMPLE: jtBQa0Aq+DkULFI8qrpwIjGEx0E=
PUT\n 4gJE4saaMU4BqNR0kLY+lw==\n application/x-download\n Tue, 27 Mar 2007 21:06:08 +0000\n x-amz-acl:public-read\n x-amz-meta-checksumalgorithm:crc32\n x-amz-meta-filechecksum:0x02661779\n x-amz-meta-reviewedby: joe@example.com,jane@example.com\n /static.example.com/db-backup.dat.gz

请注意对“x-amz-”标头排序、删减多余空格和转换为小写的方式。另请注意,具有相同名称的多个标头使用逗号连接,以分隔值。

请注意,如何仅使 Content-TypeContent-MD5 HTTP 实体标头显示在 StringToSign 中。其他 Content-* 实体标头不显示。

同样请注意,CanonicalizedResource 包括存储桶名称,但 HTTP 请求 URI 不包括。(存储桶由主机标头指定)。

列出我的所有存储桶

请求 StringToSign
GET / HTTP/1.1 Host: s3.us-west-1.amazonaws.com Date: Wed, 28 Mar 2007 01:29:59 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE:qGdzdERIC03wnaRNKh6OqZehG9s=
GET\n \n \n Wed, 28 Mar 2007 01:29:59 +0000\n /

Unicode 密钥

请求 StringToSign
GET /dictionary/fran%C3%A7ais/pr%c3%a9f%c3%a8re HTTP/1.1 Host: s3.us-west-1.amazonaws.com Date: Wed, 28 Mar 2007 01:49:49 +0000 Authorization: AWS AKIAIOSFODNN7EXAMPLE:DNEZGsoieTZ92F3bUfSPQcbGmlM=
GET\n \n \n Wed, 28 Mar 2007 01:49:49 +0000\n /dictionary/fran%C3%A7ais/pr%c3%a9f%c3%a8re
注意

将逐字读取来自请求-URI 的 StringToSign 中的元素,包括 URL 编码和大写。

REST 请求签名问题

REST 请求身份验证失败后,系统将使用 XML 错误文档对请求做出响应。此错误文档中包含的信息将帮助开发人员诊断问题。特别是,StringToSign 错误文档的 SignatureDoesNotMatch 元素将明确告诉您系统使用的请求规范。

某些工具包可能会在您事先不知情的情况下自动插入标头,例如在 PUT 操作期间添加标头 Content-Type。在大部分情况下,插入标头的值保持不变,从而使您可以使用诸如 Ethereal 或 tcpmon 等工具来发现丢失的标头。

查询字符串请求身份验证替代项

您可以通过传递请求信息作为查询字符串参数,而不是使用 Authorization HTTP 标头来验证特定类型的请求。这在允许第三方浏览器直接访问您的私有 Amazon S3 数据,而无需代理请求时非常有用。其概念是构建一个“预签名”的请求并将其编码为最终用户浏览器可检索的 URL。此外,您还可以通过指定过期时间来限制预签名请求。

有关使用查询参数对请求进行身份验证的更多信息,请参阅《Amazon Simple Storage Service API 参考》中的对请求进行身份验证:使用查询参数 (Amazon Signature Version 4)。有关使用 Amazon 开发工具包生成预签名 URL 的示例,请参阅 使用预签名 URL 共享对象

创建签名

以下是查询字符串验证的 Amazon S3 REST 请求示例。

GET /photos/puppy.jpg ?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Expires=1141889120&Signature=vjbyPxybdZaNmGa%2ByT272YEAiv4%3D HTTP/1.1 Host: awsexamplebucket1.s3.us-west-1.amazonaws.com Date: Mon, 26 Mar 2007 19:37:58 +0000

查询字符串请求身份验证方法不要求任何特殊的 HTTP 标头,而是将要求的身份验证元素指定为查询字符串参数:

查询字符串参数名称 示例值 说明
AWSAccessKeyId AKIAIOSFODNN7EXAMPLE 您的 Amazon 访问密钥 ID。指定用于签署请求的 Amazon 秘密访问密钥以及(间接)进行请求的开发人员的身份。
Expires 1141889120 将签名过期时间指定为自 Epoch (1970 年 1 月 1 日 00:00:00 UTC) 以来的秒数。将拒绝在此时间 (根据服务器) 之后收到的请求。
Signature vjbyPxybdZaNmGa%2ByT272YEAiv4%3D 采用 StringToSign 的 HMAC-SHA1 Base64 编码的 URL 编码。

查询字符串请求身份验证方法与普通的方法稍有差异,不同之处仅在于 Signature 请求参数和 StringToSign 元素的格式。下面的伪语法演示了查询字符串请求身份验证方法。

Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKey, UTF-8-Encoding-Of( StringToSign ) ) ) ); StringToSign = HTTP-VERB + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Expires + "\n" + CanonicalizedAmzHeaders + CanonicalizedResource;

YourSecretAccessKey 是在您注册成为 Amazon Web Services 开发人员时,Amazon 分配给您的 Amazon 秘密访问密钥 ID。请注意如何对 Signature 进行 URL 编码,以使其适合放置在查询字符串中。另请注意,在 StringToSign 中,HTTP Date 位置元素已替换为 ExpiresCanonicalizedAmzHeadersCanonicalizedResource 是相同的。

注意

在查询字符串身份验证方法中,在计算要签署的字符串时,请勿使用 Datex-amz-date request 标头。

查询字符串请求身份验证

请求 StringToSign
GET /photos/puppy.jpg?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE& Signature=NpgCjnDzrM%2BWFzoENXmpNDUsSn8%3D& Expires=1175139620 HTTP/1.1 Host: awsexamplebucket1.s3.us-west-1.amazonaws.com
GET\n \n \n 1175139620\n /awsexamplebucket1/photos/puppy.jpg

我们假设,在浏览器创建 GET 请求时,它没有提供 Content-MD5 或 Content-Type 标头,也没有设置任何 x-amz- 标头,因此 StringToSign 的这些部分都保留为空白。

使用 Base64 编码

必须使用 Base64 编码 HMAC 请求签名。Base64 编码将签名转换为可附加到请求的简单 ASCII 字符串。如果要在 URI 中使用诸如加号 (+)、正斜杠 (/) 和等号 (=) 等可在签名中显示的字符,必须对它们进行编码。例如,如果身份验证代码包括一个加号 (+) 标志,请在请求中将其编码为 %2B。将正斜杠编码为 %2F,并将等号编码为 %3D。

有关 Base64 编码的示例,请参考 Amazon S3 身份验证示例