我有一个用于获取请求的预签名 URL,例如:
https://<my-bucket>.s3.amazonaws.com/<path/to/file.dat>?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<credential-string>%2F20241107%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20241107T202532Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-string>
我想使用
httr2
缓存,因此我想使用基于标头的授权重新构造此 URL,以便缓存不会仅仅因为日期和凭据更改而失效。但是,我无法使基于标头的请求正常工作。这是我正在尝试的,基于此文档:
aws_authorization = "AWS4-HMAC-SHA256Credential=<credential-string>%2F20241107%2Fus-east-1%2Fs3%2Faws4_request,SignedHeaders=host;x-amz-date=20241107T202532Z;x-amz-expires=900,Signature=<signature-string>"
为了清楚起见,这里再次显示带有换行符的授权标头:
aws_authorization = "AWS4-HMAC-SHA256
Credential=<credential-string>%2F20241107%2Fus-east-1%2Fs3%2Faws4_request,
SignedHeaders=host;x-amz-date=20241107T202532Z;x-amz-expires=900,
Signature=<signature-string>"
然后我将请求重构为
req = request("https://<my-bucket>.s3.amazonaws.com/<path/to/file.dat>") |>
req_headers(Authorization = aws_authorization)
req
#> <httr2_request>
#> GET
#> https://<my-bucket>.s3.amazonaws.com/<path/to/file.dat>
#> Headers:
#> • Authorization:
#> 'AWS4-HMAC-SHA256Credential=<credential-string>/20241107/us-east-1/s3/aws4_request,SignedHeaders=host;x-amz-date=20241107T202532Z;x-amz-expires=900,Signature=<signature-string>'
#> Body: empty
但是当我执行请求时,我收到 HTTP 400 Bad Request 错误:
req_perform(req)
#> HTTP 400 Bad Request.
我在构造请求时哪里出错了?
编辑
根据评论修改:
x-amz-expires
。=
,并定义引用的标头。所以现在我的授权字符串是
AWS4-HMAC-SHA256Credential=<credential-string>%2Fus-east-1%2Fs3%2Faws4_request,SignedHeaders=host;x-amz-date,Signature=<signature-string>
我已经添加了标题
X-Amz-Date = 20241107T202532Z
X-Amz-Expires = 900
产生请求
#> <httr2_request>
#> GET
#> <my-bucket>.s3.amazonaws.com/<path/to/file.dat>
#> Headers:
#> • Authorization:
#> 'AWS4-HMAC-SHA256Credential=<credential-string>%2F20241107%2Fus-east-1%2Fs3%2Faws4_request,SignedHeaders=host;x-amz-date,Signature=<signature-string>'
#> • X-Amz-Date: '20241107T220527Z'
#> • X-Amz-Expires: '900'
#> Body: empty
以下是使用以下方法完成的:
httr2 1.0.6包含一个函数req_auth_aws_v4(),它接近你想要的,但我无法让它与AWS s3一起使用。但是,如果您想创建自己的签名算法,req_auth_aws_v4() 代码很有用。
AWS 在对不成功的签名请求的响应中提供有用的调试信息。如果您看到 400 Bad Request 响应,我会尝试以下操作来阻止 httr2 抛出错误,以便您可以看到响应内容:
req = request("https://<my-bucket>.s3.amazonaws.com/<path/to/file.dat>") |> req_headers(Authorization = aws_authorization)
req <- req_verbose(
req,
header_req = TRUE,
header_resp = TRUE,
body_req = TRUE,
body_resp = TRUE,
info = TRUE,
redact_headers = FALSE
)
tryCatch(
resp <- req |> req_perform(),
httr2_http_400 = function(cnd) {
last_response() |> resp_raw()
}
我使用 Python requests 编写(借用)了以下代码,该代码使用标头身份验证成功返回了 AWS S3 对象:
import sys, os, base64, datetime, hashlib, hmac
import requests
method = 'GET'
service = 's3'
host = '<my-bucket>.s3.amazonaws.com'
region = 'us-east-1'
endpoint = 'https://<my-bucket>.s3.amazonaws.com'
request_parameters = ''
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def getSignatureKey(key, dateStamp, regionName, serviceName):
kDate = sign(('AWS4' + key).encode('utf-8'), dateStamp)
kRegion = sign(kDate, regionName)
kService = sign(kRegion, serviceName)
kSigning = sign(kService, 'aws4_request')
return kSigning
access_key = "AABBCCDDEEFF11223344"
secret_key = "SomeBigLongSecretKeyToUseForAWSDontShare"
if access_key is None or secret_key is None:
print('No access key is available.')
sys.exit()
t = datetime.datetime()
amzdate = t.strftime('%Y%m%dT%H%M%SZ')
datestamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
canonical_uri = '/<path/to/file.dat>'
canonical_querystring = request_parameters
canonical_headers = 'host:' + host + '\n' + 'x-amz-content-sha256:UNSIGNED-PAYLOAD' + '\n' + 'x-amz-date:' + amzdate + '\n'
signed_headers = 'host;x-amz-content-sha256;x-amz-date'
payload_hash = 'UNSIGNED-PAYLOAD'
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash
algorithm = 'AWS4-HMAC-SHA256'
credential_scope = datestamp + '/' + region + '/' + service + '/' + 'aws4_request'
print(canonical_request)
string_to_sign = algorithm + '\n' + amzdate + '\n' + credential_scope + '\n' + hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()
signing_key = getSignatureKey(secret_key, datestamp, region, service)
signature = hmac.new(signing_key, (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()
print(signature)
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature
headers = {'x-amz-date':amzdate, 'x-amz-content-sha256': 'UNSIGNED-PAYLOAD', 'Authorization':authorization_header}
request_url = endpoint + canonical_uri
print(authorization_header)
print('\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++')
print('Request URL = ' + request_url)
r = requests.get(request_url, headers=headers)
print('\nRESPONSE++++++++++++++++++++++++++++++++++++')
print('Response code: %d\n' % r.status_code)
print(r.text)
这里要注意的重要事项是它发送以下标头:
并且 Authorization 标头格式为:
AWS4-HMAC-SHA256 Credential=AKIASMI5W6NBQAMBZHR5/20241110/ap-southeast-2/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=2ad745df5f4680d42800865d775ca045651eb42819e18592b99c43c3c00b949f
注意
算法和凭证之间的空格
' '
分隔符:
AWS4-HMAC-SHA256 Credential
httr2 req_auth_aws_v4() 函数似乎生成一个授权标头,该标头在算法和凭证之间有 ','
,这可能是问题所在:
AWS4-HMAC-SHA256,Credential
如果您有有效的签名,则以下标头似乎可以使用 httr2:
library(httr2)
aws_date = "20241110"
aws_time = "170747"
sig = "64aa96f4dceaa3b782245ba9a070621190e1feab93c349bc6a5a83a8ce8ee4e3"
# Tested with AWS S3
#
# The Authorization header for AWSV4 appears to require a space " " after the Algorithm AWS4-HMAC-SHA256
#
aws_authorization = paste("AWS4-HMAC-SHA256 Credential=AABBCCDDEEFF11223344/", aws_date, "/us-east-1/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=", sig, sep = "")
req <- request("https://<my-bucket>.s3.amazonaws.com")
req <- req |> req_url_path(path = "<path/to/file.dat")
req <- req |> req_headers("Authorization" = aws_authorization, "host" = "<my-bucket>.s3.amazonaws.com", "x-amz-content-sha256" = "UNSIGNED-PAYLOAD", "x-amz-date" = paste(aws_date,"T",aws_time,"Z", sep = ""))
req <- req_verbose(
req,
header_req = TRUE,
header_resp = TRUE,
body_req = TRUE,
body_resp = TRUE,
info = TRUE,
redact_headers = FALSE
)
req
tryCatch(
resp <- req |> req_perform(),
httr2_http_400 = function(cnd) {
last_response() |> resp_raw()
}
)
resp |> resp_body_raw()
希望通过对 httr2 req_auth_aws_v4() 函数进行一些更改,将来可以将其简化为以下内容。
library(httr2)
req <- request("https:/<my-bucket>.s3.amazonaws.com/<path/to/file.dat>")
req <- req_auth_aws_v4(
req,
aws_access_key_id = "AABBCCDDEEFF11223344",
aws_secret_access_key = "SomeBigLongSecretKeyToUseForAWSDontShare",
aws_service = "s3",
aws_region = "us-east-1"
)
resp <- req_perform(req)
resp