AWS S3预先签名的URL以及其他查询参数

时间:2017-12-06 01:53:57

标签: amazon-web-services amazon-s3

我创建了一个预先签名的网址并返回类似

的内容
https://s3.amazonaws.com/MyBucket/MyItem/
?X-Amz-Security-Token=TOKEN
&X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Date=20171206T014837Z
&X-Amz-SignedHeaders=host
&X-Amz-Expires=3600
&X-Amz-Credential=CREDENTIAL
&X-Amz-Signature=SIGNATURE

我现在可以curl这没问题。但是,如果我现在添加另一个查询参数,我将返回403,即

https://s3.amazonaws.com/MyBucket/MyItem/
?X-Amz-Security-Token=TOKEN
&X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Date=20171206T014837Z
&X-Amz-SignedHeaders=host
&X-Amz-Expires=3600
&X-Amz-Credential=CREDENTIAL
&X-Amz-Signature=SIGNATURE
&Foo=123

为什么?是否可以生成支持自定义查询的预签名网址?

3 个答案:

答案 0 :(得分:4)

如果您更改其中一个标题或添加/减去,则必须重新设置该网址。

这是AWS签名设计的一部分,此流程旨在提高安全级别。从签署版本2更改为签署版本4的AWS原因之一。

签名设计不知道哪些标题重要,哪些标题不重要。这会造成一场噩梦,试图跟踪所有AWS服务。

答案 1 :(得分:2)

将自定义查询参数插入到v4预先签名的URL 之前进行签名似乎在技术上是可行的,但并非所有AWS SDK都提供了执行此操作的方法。

以下是使用AWS JavaScript SDK执行此操作的迂回方式示例:

%eip

我已尝试使用以X-开头且没有它的自定义查询参数。两者似乎都很好。我尝试了多个查询参数(const AWS = require('aws-sdk'); var s3 = new AWS.S3({region: 'us-east-1', signatureVersion: 'v4'}); var req = s3.getObject({Bucket: 'mybucket', Key: 'mykey'}); req.on('build', () => { req.httpRequest.path += '?session=ABC123'; }); console.log(req.presign()); ),这也有效。

自定义的预签名URL可以正常工作(我可以使用它们来获取S3对象),查询参数可以将其用于CloudWatch Logs,因此可用于关联目的。

请注意,如果您想提供自定义过期时间,请按以下步骤操作:

?a=1&b=2

我不知道其他(非JavaScript)SDK允许您将查询参数插入到URL构造过程中,因此在其他语言中执行此操作可能是一个挑战。我建议使用一个小的JavaScript Lambda函数(或API Gateway和Lambda函数),只需创建并返回自定义的预签名URL。

自定义查询参数也是防篡改的。它们包含在URL的签名中,因此,如果您篡改它们,则URL将变为无效,从而产生403 Forbidden。

我使用此代码生成预签名的网址。结果是:

const Expires = 120;
const url = req.presign(Expires);

这当然不能保证这种技术能够继续发挥作用,当然,如果AWS改变了覆盖范围内的东西,但是现在它似乎有效并且肯定是有用的。

归因:此发现的来源为ECMA specification

答案 2 :(得分:0)

我为 Ruby SDK 创建了这个解决方案。这有点像 hack,但它按预期工作:

require 'aws-sdk-s3'
require 'active_support/core_ext/object/to_query.rb'

# Modified S3 pre signer class that can inject query params to the URL
#
# Usage example:
#
#    bucket_name = "bucket_name"
#    key = "path/to/file.json"
#    filename = "download_file_name.json"
#    duration = 3600
#
#    params = {
#      bucket: bucket_name,
#      key: key,
#      response_content_disposition: "attachment; filename=#{filename}",
#      expires_in: duration
#    }
#
#    signer = S3PreSignerWithQueryParams.new({'x-your-custom-field': "banana", 'x-some-other-field': 1234})
#    url = signer.presigned_url(:get_object, params)
#
#    puts "url = #{url}"
#
class S3PreSignerWithQueryParams < Aws::S3::Presigner
  def initialize(query_params = {}, options = {})
    @query_params = query_params
    super(options)
  end

  def build_signer(cfg)
    signer = super(cfg)
    my_params = @query_params.to_h.to_query()
    signer.define_singleton_method(:presign_url,
                                   lambda do |options|
                                     options[:url].query += "&" + my_params
                                     super(options)
                                   end)
    signer
  end
end