我在一个网站上工作,用户应该能够将视频文件上传到 AWS。为了避免不必要的流量,我希望用户直接上传到 AWS(而不是通过 API 服务器)。为了不在 JavaScript 中暴露我的 key ,我试图在 API 中生成一个签名。但是,当我尝试上传时,它会告诉我签名不匹配。
对于签名生成,我一直在使用 http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html
我在后端运行 C#。
我使用
生成签名string policy = $@"{{""expiration"":""{expiration}"",""conditions"":[{{""bucket"":""dennisjakobsentestbucket""}},[""starts-with"",""$key"",""""],{{""acl"":""private""}},[""starts-with"",""$Content-Type"",""""],{{""x-amz-algorithm"":""AWS4-HMAC-SHA256""}}]}}";
生成以下内容
{"expiration":"2016-11-27T13:59:32Z","conditions":[{"bucket":"dennisjakobsentestbucket"},["starts-with","$key",""],{"acl":"private"},["starts-with","$Content-Type",""],{"x-amz-algorithm":"AWS4-HMAC-SHA256"}]}
基于 http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html (我对策略进行了 base64 编码)。作为起点,我尽量保持简单。
为了生成签名,我使用了在 AWS 网站上找到的代码。
static byte[] HmacSHA256(String data, byte[] key)
{
String algorithm = "HmacSHA256";
KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(algorithm);
kha.Key = key;
return kha.ComputeHash(Encoding.UTF8.GetBytes(data));
}
static byte[] GetSignatureKey(String key, String dateStamp, String regionName, String serviceName)
{
byte[] kSecret = Encoding.UTF8.GetBytes(("AWS4" + key).ToCharArray());
byte[] kDate = HmacSHA256(dateStamp, kSecret);
byte[] kRegion = HmacSHA256(regionName, kDate);
byte[] kService = HmacSHA256(serviceName, kRegion);
byte[] kSigning = HmacSHA256("aws4_request", kService);
return kSigning;
}
我是这样使用的:
byte[] signingKey = GetSignatureKey(appSettings["aws:SecretKey"], dateString, appSettings["aws:Region"], "s3");
byte[] signature = HmacSHA256(encodedPolicy, signingKey);
其中 dateString 的格式为 yyyymmdd
我使用 JavaScript 从 POST 信息
let xmlHttpRequest = new XMLHttpRequest();
let formData = new FormData();
formData.append("key", "<path-to-upload-location>");
formData.append("acl", signature.acl); // private
formData.append("Content-Type", "$Content-Type");
formData.append("AWSAccessKeyId", signature.accessKey);
formData.append("policy", signature.policy); //base64 of policy
formData.append("x-amz-credential", signature.credentials); // <accesskey>/20161126/eu-west-1/s3/aws4_request
formData.append("x-amz-date", signature.date);
formData.append("x-amz-algorithm", "AWS4-HMAC-SHA256");
formData.append("Signature", signature.signature);
formData.append("file", file);
xmlHttpRequest.open("post", "http://<bucketname>.s3-eu-west-1.amazonaws.com/");
xmlHttpRequest.send(formData);
我一直按照 AWS 的规定在所有地方使用 UTF8。在他们的示例中,签名采用十六进制格式,我也尝试过。 无论我尝试什么,我都会收到错误 403
The request signature we calculated does not match the signature you provided. Check your key and signing method.
我在 AWS 上的政策有“s3:Get*”、“s3:Put*”
我是不是遗漏了什么,或者它的工作方式与我的预期完全不同?
编辑:下面的答案是步骤之一。另一个是 AWS 区分大写和小写的十六进制字符串。 0xFF != AWS眼中的0xff。他们想要全小写的签名。
最佳答案
您正在使用 Signature Version 4 生成签名,但是您正在构建表单,就好像您在使用 Signature Version 2 一样......好吧,有点。
formData.append("AWSAccessKeyId", signature.accessKey);
这是 V2。它根本不应该出现在这里。
formData.append("x-amz-credential", signature.credentials); // <accesskey>/20161126/eu-west-1/s3/aws4_request
这是 V4。请注意此处和上方多余的 AWS 访问 key ID 提交。这一个可能是正确的,尽管示例有像 X-Amz-Credential
这样的大写。
formData.append("x-amz-algorithm", "AWS4-HMAC-SHA256");
这也是正确的,除了它可能需要是 X-Amz-Algorithm
。 (该示例似乎暗示忽略了大写)。
formData.append("Signature", signature.signature);
这个是不正确的。这应该是 X-Amz-Signature
。 V4 签名是十六进制的,所以这就是你应该在这里拥有的。 V2 签名是 base64。
有一个完整的 V4 示例 here ,它甚至为您提供了一个示例 aws key 和 secret 、日期、区域、存储桶名称等,您可以将其与您的代码一起使用以验证您确实获得了相同的响应。该表单实际上不会工作,但重要的问题是您的代码是否可以生成相同的表单、策略和签名。
对于任何给定的请求,永远只有一个正确的签名;然而,对于任何给定的策略,可能有不止一种有效的 JSON 编码(由于 JSON 对空格的灵 active )——但是对于任何给定的 JSON 编码,策略只有一种可能的有效 base64 编码。这意味着如果您的代码使用示例数据生成与示例中所示完全相同的表单和签名,则被证明可以正常工作——这意味着如果您的代码生成相同的表单和策略,则证明无效不同的签名——但还有第三种可能性:如果您的代码生成策略的不同 base64 编码,测试实际上无法证明您的代码有任何结论,因为这必然会将签名更改为不匹配,但可能仍然有效政策。
请注意,Signature V2 仅在较旧的 S3 区域受支持,而 Signature V4 受所有 S3 区域支持,因此,即使您可以通过使整个签名过程使用 V2 来交替解决此问题,不推荐这样做。
另请注意,我们计算出的请求签名与您提供的签名不匹配。检查您的 key 和签名方法
不会告诉您有关存储桶 策略或任何用户策略是允许还是拒绝该请求的任何信息。此错误不是权限错误。它将在权限检查之前抛出,仅基于签名的有效性,而不是 AWS 访问 key ID 是否被授权执行请求的操作,这是仅在验证签名后才进行测试的内容。
关于javascript - 使用 HTTP POST 的 AWS S3 浏览器上传提供无效签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40819360/