创建已签名的 AWS API 请求 - AWS Identity and Access Management

创建已签名的 AWS API 请求

重要

如果您使用 AWS SDK(请参阅示例代码和库)或 AWS Command Line Interface(AWS CLI)工具向 AWS 发送 API 请求,则可以跳过本部分,因为 SDK 和 CLI 客户端会使用您提供的访问密钥来验证您的请求。除非您有充分的理由不这样做,否则我们建议您始终使用 SDK 或 CLI。

在支持多个签名版本的区域中,手动签名请求意味着您必须指定使用的签名版本。当您向多区域访问点提供请求时,SDK 和 CLI 会自动切换为使用签名版本 4A,而无需进行其他配置。

您可以使用 AWS SigV4 签名协议为 AWS API 请求创建签名的请求。

  1. 根据请求详细信息创建规范请求。

  2. 使用 AWS 凭证计算签名。

  3. 将此签名作为授权标头添加到请求。

然后 AWS 复制此过程并验证签名,相应地授予或拒绝访问权限。

要了解如何使用 AWS SigV4 对 API 请求进行签名,请参阅请求签名示例

下图演示了 SigV4 签名过程,包括您为签名而创建的字符串的各个组成部分。

规范请求、待签字符串、签名密钥和签名各部分的图像。

下表介绍了图中显示的函数。您需要为这些函数实现代码。如需了解更多信息,请参阅 AWS 软件开发工具包中的代码示例

函数 描述

Lowercase()

将字符串转换为小写。

Hex()

base-16 编码的小写形式。

SHA256Hash()

安全哈希算法(SHA)加密哈希函数。

HMAC-SHA256()

使用 SHA256 算法和提供的签名密钥计算 HMAC。这是最终的签名。

Trim()

删除所有前导空格或尾随空格。

UriEncode()

URI 对每个字节进行编码。UriEncode() 必须强制执行以下规则:

  • URI 对除非预留字符之外的所有字节进行编码:“A”-“Z”、“a”-“z”、“0”-“9”、“-”、“.”、“_”和“~”。

  • 空格字符是预留字符,必须编码为“%20”(而不是“+”)。

  • 每个 URI 编码字节由“%”和该字节的两位十六进制值组成。

  • 十六进制值中的字母必须为大写,例如“%1A”。

  • 对除对象键名称之外的所有位置的正斜杠字符“/”进行编码。例如,如果对象键名称是 photos/Jan/sample.jpg,则不会对键名称中的正斜杠进行编码。

重要

由于底层 RFC 中的实现差异和相关歧义,您开发平台提供的标准 UrienCode 函数可能无法正常工作。建议您编写自己的自定义 UrienCode 函数,以确保编码能够正常工作。

要查看 Java 中的 UriEncode 函数示例,请参阅 GitHub 网站上的 Java Utilities

注意

签署请求时,您可以使用 AWS 签名版本 4 或 AWS 签名版本 4A。两者之间的关键区别取决于签名的计算方式。对于 AWS 签名版本 4A,签名不包含区域特定的信息,并且使用 AWS4-ECDSA-P256-SHA256 算法计算得出。

使用临时安全凭证签名请求

您可使用 AWS Security Token Service(AWS STS)提供的临时安全凭证来签署请求,而不是使用长期凭证。

使用临时安全凭证时,必须将 X-Amz-Security-Token 添加至授权标头或将其包含在查询字符串中以保存会话令牌。某些服务会要求您将 X-Amz-Security-Token 添加至规范请求。其他服务仅会要求您在计算出签名后在末尾添加 X-Amz-Security-Token。有关具体要求,请查看每个 AWS 服务的文档。

签名步骤摘要

创建规范请求:

将请求的内容(主机、操作、标头等)组织为标准规范格式。规范请求是用于创建待签字符串的输入之一。有关创建规范请求的详细信息,请参阅AWS API 请求签名的元素

创建规范请求的哈希值

使用创建负载的哈希时所使用的相同算法来哈希规范请求。经过哈希处理的规范请求必须以小写十六进制字符串形式表示。

创建待签字符串

使用规范请求和额外信息(例如算法、请求日期、凭证范围和规范请求的哈希)创建待签字符串。

派生签名密钥

使用 AWS 秘密访问密钥作为初始哈希操作的密钥,对请求日期、区域和服务执行一系列加密哈希操作(HMAC)。

计算签名

使用派生的签名密钥作为哈希密钥,对待签字符串执行加密哈希操作(HMAC)。

将签名添加至请求

将计算的签名添加到请求的 HTTP 标头或查询字符串中。

创建规范请求

要创建规范请求,请串联由换行符分隔的以下字符串。这有助于确保您计算出的签名能够与 AWS 计算出的签名相匹配。

<HTTPMethod>\n <CanonicalURI>\n <CanonicalQueryString>\n <CanonicalHeaders>\n <SignedHeaders>\n <HashedPayload>
  • HTTPMethod – HTTP 方法,例如 GETPUTHEAD 和 DELETE

  • CanonicalUri:绝对路径组件 URI 的 URI 编码版本,以域名后面的 / 开头,直至字符串结尾处,或者如果包含查询字符串参数,则直至问号字符(?)。如果绝对路径为空,则使用正斜杠字符(/)。以下示例中的 URI /amzn-s3-demo-bucket/myphoto.jpg 是绝对路径,并且您无需在绝对路径中对 / 进行编码:

    http://s3.amazonaws.com/amzn-s3-demo-bucket/myphoto.jpg
  • CanonicalQueryString – URI 编码的查询字符串参数。您可以单独对每个名称和值进行 URI 编码。您还必须按键名称的字母顺序对规范查询字符串中的参数进行排序。编码后进行排序。以下 URI 示例中的查询字符串是:

    http://s3.amazonaws.com/amzn-s3-demo-bucket?prefix=somePrefix&marker=someMarker&max-keys=2

    规范查询字符串如下所示(为便于阅读,此示例中添加了换行符:):

    UriEncode("marker")+"="+UriEncode("someMarker")+"&"+ UriEncode("max-keys")+"="+UriEncode("20") + "&" + UriEncode("prefix")+"="+UriEncode("somePrefix")

    当请求针对子资源时,相应的查询参数值将为空字符串("")。例如,以下 URI 标识了 amzn-s3-demo-bucket 存储桶上的 ACL 子资源:

    http://s3.amazonaws.com/amzn-s3-demo-bucket?acl

    在这种情况下,CanonicalQueryString 将为:

    UriEncode("acl") + "=" + ""

    如果 URI 不包含 ?,则请求中没有查询字符串,并且您需要将规范查询字符串设置为空字符串("")。您仍然需要包含换行符 ("\n")。

  • CanonicalHeaders:请求标头及其值的列表。各个标头名称和值对用换行符("\n")分隔。以下是 CanonicalHeader 的示例:

    Lowercase(<HeaderName1>)+":"+Trim(<value>)+"\n" Lowercase(<HeaderName2>)+":"+Trim(<value>)+"\n" ... Lowercase(<HeaderNameN>)+":"+Trim(<value>)+"\n"

    CanonicalHeaders 列表必须包含以下内容:

    • HTTP host 标头。

    • 如果请求中存在 Content-Type 标头,则必须将其添加到 CanonicalHeaders 列表中。

    • 此外,还必须添加计划在请求中包含的所有 x-amz-* 标头。例如,如果您使用临时安全凭证,则请求中必须包含 x-amz-security-token。您必须将此标头添加到 CanonicalHeaders 列表中。

    注意

    x-amz-content-sha256 标题是 Amazon S3 AWS 请求所必需的。它将提供请求负载的哈希。如果不包含有效负载,则必须提供空字符串的哈希值。

    每个标头名称必须:

    • 使用小写字符。

    • 按字母顺序显示。

    • 后跟冒号(:)。

    对于值,您必须:

    • 去除任何前导空格或尾随空格。

    • 将连续空格转换为单个空格。

    • 使用逗号分隔多值标头的值。

    • 签名中必须包含 host 标头(HTTP/1.1)或 :authority 标头(HTTP/2)以及任何 x-amz-* 标头。签名中也可以包含其他标准标头,例如 content-type。

    上一部分介绍了本示例中使用的 Lowercase() 和 Trim() 函数。

    以下是示例 CanonicalHeaders 字符串。标头名称为小写且已排序。

    host:s3.amazonaws.com x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 x-amz-date:20130708T220855Z

    注意

    为了计算授权签名,只有主机以及任何 x-amz-* 标头为必填项;但是,为了防止数据篡改,您应该考虑在签名计算中包含所有标头。

  • SignedHeaders:按字母顺序排序、以分号分隔的小写请求标头名称列表。列表中的请求标头与您在 CanonicalHeaders 字符串中包含的标头相同。对于前面的示例,SignedHeaders 的值如下:

    host;x-amz-content-sha256;x-amz-date
  • HashedPayload – 使用 HTTP 请求正文中的负载作为哈希函数的输入创建的字符串。此字符串使用小写十六进制字符。

    Hex(SHA256Hash(<payload>>))

    如果请求中不包含有效负载,则计算空字符串的哈希值,例如,当使用 GET 请求检索对象时,有效负载中没有任何内容。

    Hex(SHA256Hash(""))
    注意

    对于 Amazon S3,请在构造规范请求时包含文字字符串 UNSIGNED-PAYLOAD,并在发送请求时设置与 x-amz-content-sha256 标头值相同的值。

    Hex(SHA256Hash("UNSIGNED-PAYLOAD"))

创建规范请求的哈希值

使用创建负载的哈希时所使用的相同算法来创建规范请求的哈希(摘要)。经过哈希处理的规范请求必须以小写十六进制字符串形式表示。

创建待签字符串

要创建待签字符串,请串联以下由换行符分隔的以下字符串。请勿使用换行符作为此字符串的结尾。

Algorithm \n RequestDateTime \n CredentialScope \n HashedCanonicalRequest
  • Algorithm – 用于创建规范请求的哈希的算法。对于 SHA-256,算法是 AWS4-HMAC-SHA256

  • RequestDateTime – 在凭证范围内使用的日期和时间。该值是采用 ISO 8601 格式的当前 UTC 时间(例如 20130524T000000Z)。

  • CredentialScope:凭证范围,将生成的签名限制在指定的区域和服务范围内。该字符串采用以下格式:YYYYMMDD/region/service/aws4_request。

  • HashedCanonicalRequest:上一步中计算出的规范请求的哈希。

以下是要签名的字符串的示例。

"AWS4-HMAC-SHA256" + "\n" + timeStampISO8601Format + "\n" + <Scope> + "\n" + Hex(SHA256Hash(<CanonicalRequest>))

派生签名密钥

要派生签名密钥,请使用 AWS 秘密访问密钥作为初始哈希操作的密钥,对请求日期、区域和服务执行一系列加密哈希操作(HMAC)。

对于每个步骤,使用所需的密匙和数据调用哈希函数。每次调用哈希函数的结果都会变成下一次调用哈希函数的输入。

以下示例说明了如何派生本过程下一部分中使用的 SigningKey,并说明了输入的串联和哈希顺序。HMAC-SHA256 是用于对数据进行哈希处理的哈希功能,如下所示。

DateKey = HMAC-SHA256("AWS4"+"<SecretAccessKey>", "<YYYYMMDD>") DateRegionKey = HMAC-SHA256(<DateKey>, "<aws-region>") DateRegionServiceKey = HMAC-SHA256(<DateRegionKey>, "<aws-service>") SigningKey = HMAC-SHA256(<DateRegionServiceKey>, "aws4_request")
必填项
  • Key,包含您的秘密访问密钥的字符串。

  • Date,包含在凭证范围中使用的日期的字符串,格式为 YYYYMMDD

  • Region,包含区域代码的字符串(例如,us-east-1)。

    有关区域字符串的列表,请参阅 AWS 一般参考 中的 Regional Endpoints

  • Service,包含服务代码的字符串(例如,ec2)。

  • 在上一步中创建的要签名的字符串。

派生签名密钥
  1. 串联 "AWS4" 和秘密访问密钥。使用密钥和数据调用哈希函数,并将连接的字符串作为密钥,而日期字符串作为数据。

    DateKey = hash("AWS4" + Key, Date)
  2. 使用密钥和数据调用哈希函数,并将上一次调用的结果作为密钥,而区域字符串作为数据。

    DateRegionKey = hash(kDate, Region)
  3. 使用密钥和数据调用哈希函数,并将上一次调用的结果作为密钥,而服务字符串作为数据。

    服务代码由服务定义。您可以在 AWS Pricing CLI 中使用 get-products 返回服务的服务代码。

    DateRegionServiceKey = hash(kRegion, Service)
  4. 使用密钥和数据调用哈希函数,并将上一次调用的结果作为密钥,而“aws4_request”作为数据。

    SigningKey = hash(kService, "aws4_request")

计算签名

派生签名密钥后,通过对待签字符串执行加密哈希操作来计算签名。使用派生的签名密钥作为此操作的哈希密钥。

计算签名
  1. 使用密钥和数据调用哈希函数,并将上一次调用的结果作为密钥,而要签名的字符串作为数据。结果是作为二进制值的签名。

    signature = hash(SigningKey, string-to-sign)
  2. 将签名从二进制转换为十六进制表示形式,使用小写字符。

将签名添加至请求

将计算出的签名添加到您的请求中。

例 示例:授权标头

以下示例显示了 DescribeInstances 操作的 Authorization 标头。为便于阅读,此示例已使用换行符编排过格式。在您的代码中,这必须是连续的字符串。算法和 Credential 之间没有逗号。但是,必须使用逗号分隔其他元素。

Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220830/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-date, Signature=calculated-signature
例 示例:请求在查询字符串中使用身份验证参数

以下示例显示了对包含身份验证信息的 DescribeInstances 操作的查询。为便于阅读,此示例已使用换行符编排过格式,而非 URL 编码。在您的代码中,查询字符串必须是采用 URL 编码的连续字符串。

https://ec2.amazonaws.com/? Action=DescribeInstances& Version=2016-11-15& X-Amz-Algorithm=AWS4-HMAC-SHA256& X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20220830/us-east-1/ec2/aws4_request& X-Amz-Date=20220830T123600Z& X-Amz-SignedHeaders=host;x-amz-date& X-Amz-Signature=calculated-signature