Rails,Paperclip,直接上传到S3 - 经过身份验证的URL返回SignatureDoesNotMatch响应

时间:2014-01-22 22:54:40

标签: ruby-on-rails-3.2 amazon-s3 paperclip

rails(〜> 3.2.13)

回形针(2.7.4)

aws-sdk(1.8.0)

在使用Paperclip的Rails应用程序中,我有一个名为“Asset”的模型,其附件名为“upload”(s3_permissions:“private”)

此附件首先直接上传到Amazon S3,然后保存资产。为了直接上传,我使用了以下gem 按照教程https://github.com/waynehoover/s3_direct_upload

中的说明操作http://www.blitztheory.com/direct-upload-with-s3_direct_upload/ gem

注意

我从教程中忽略的一件事是我没有使用类方法copy_and_delete(paperclip_file_path,raw_source)在所需路径中重新创建存储桶中的资产。

我首先将视图“Mars.gif”从我的视图上传到Amazon S3并将其与我的Asset对象关联。图像已成功上传到存储桶,资产也已成功保存。但是在保存资产时我正在查看其详细信息,并尝试使用图像的源URL打开附加的“Mars.gif”:

http://s3.amazonaws.com/mm_tom_test/uploads%2F1390421005820-yff5taw7kgf-da8b488d45d747deb206977d29e11005%2FMars.gif?AWSAccessKeyId=AKIAIMKHM4EATOHUAQ3Q&Expires=1390433963&Signature=ehSbrI2bKE4jqQNHyPJKWDySMyU%3D&response-content-disposition=inline&response-content-type=image%2Fgif

我遇到 SignatureDoesNotMatch 错误。请在下面找到Amazon S3返回的XML:

    <?xml version="1.0" encoding="UTF-8"?>
    <Error>
       <Code>SignatureDoesNotMatch</Code>
       <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
       <StringToSignBytes>47 45 54 0a 0a 0a 31 33 39 30 34 33 33 39 36 33 0a 2f 6d 6d 5f 74 6f 6d 5f 74 65 73 74 2f 75 70 6c 6f 61 64 73 25 32 46 31 33 39 30 34 32 31 30 30 35 38 32 30 2d 79 66 66 35 74 61 77 37 6b 67 66 2d 64 61 38 62 34 38 38 64 34 35 64 37 34 37 64 65 62 32 30 36 39 37 37 64 32 39 65 31 31 30 30 35 25 32 46 4d 61 72 73 2e 67 69 66 3f 72 65 73 70 6f 6e 73 65 2d 63 6f 6e 74 65 6e 74 2d 64 69 73 70 6f 73 69 74 69 6f 6e 3d 69 6e 6c 69 6e 65 26 72 65 73 70 6f 6e 73 65 2d 63 6f 6e 74 65 6e 74 2d 74 79 70 65 3d 69 6d 61 67 65 2f 67 69 66</StringToSignBytes>
       <RequestId>9E297018ADD4D5AD</RequestId>
       <HostId>EtMgiHpNfywzw7cNxAoCBFW5fY80LY3E5nTUuP182NfjzYqFTizIgjS+bgqPKM33</HostId>
       <SignatureProvided>ehSbrI2bKE4jqQNHyPJKWDySMyU=</SignatureProvided>
       <StringToSign>GET
    1390433963
    /mm_tom_test/uploads%2F1390421005820-yff5taw7kgf-da8b488d45d747deb206977d29e11005%2FMars.gif?response-content-disposition=inline&amp;response-content-type=image/gif</StringToSign>
       <AWSAccessKeyId>AKIAIMKHM4EATOHUAQ3Q</AWSAccessKeyId>
    </Error>

我已经花了足够的时间来解决这个问题而没有成功。如果社区中的任何人能够帮助我弄清楚导致此签名不匹配问题的原因以及如何解决这个问题,我真的很感激吗?

在我使用的代码下面列出:

/config/initializers/paperclip.rb

    Paperclip::ASSET_EXPIRATION_TIME = 3600.seconds

    Paperclip.interpolates(:key) do |attachment, style|
      attachment.instance.key
    end

    Paperclip.interpolates(:s3_conditional_url) do |attachment, style|
      attachment.expiring_url(Paperclip::ASSET_EXPIRATION_TIME, style)
    end

    module Paperclip::Storage::S3
      def public_url(style_name = default_style)
        if path
          "http://#{s3_host_name}/#{bucket_name}/#{path(style_name)}"
        end
      end

      def expiring_url(time = Paperclip::ASSET_EXPIRATION_TIME, style_name = default_style)
        if path
          s3_object(style_name).url_for(:read, :expires => time, :secure => use_secure_protocol?(style_name), :response_content_disposition => "inline",
        end
      end
    end

/config/amazon_s3.yml

development:
  access_key_id: 'AKIAIMKHM4EATOHUAQ3Q'
  secret_access_key: '<SECRET ACCESS KEY>'
  bucket: 'mm_tom_test'

/lib/s3_decider.rb

  module S3Decider
    def self.included(model)
      model.class_eval do
        if Paperclip::FILESYSTEM_ENVS.include?(Rails.env)
          @s3_decider = {
            :path => ":rails_root/public/assets/:class/:attachment/:id/:style/:basename.:extension",
            :url => "/assets/:class/:attachment/:id/:style/:basename.:extension"
          }
        else
          @s3_decider = {
            :storage        => :s3,
            :s3_credentials => "#{Rails.root}/config/amazon_s3.yml",
            :s3_permissions => "private",
            :s3_protocol    => Rails.env == "development" ? "http" : "https",
            :path           => ":class/:attachment/:id/:style.:extension",
            :url            => ":s3_conditional_url"
          }
        end
      end
    end
  end

/app/models/asset.rb

  class Asset < ActiveRecord::Base
    include S3Decider

    attr_accessible :upload, :upload_file_name, :upload_content_type, upload_file_size, :upload_updated_at

    paperclip_options_for_upload = @s3_decider.merge(
      path: ":key"
    )

    has_attached_file :upload, paperclip_options_for_upload

  end

感谢。

1 个答案:

答案 0 :(得分:2)

我自己解决了这个问题并回答了我自己的问题,以防其他人最终解决上述问题

uploadComplete回调中的

S3DirectUpload gem将Amazon S3存储桶对象的密钥作为编码值接收,并以正斜杠为前缀“/”。 /uploads%2F1390554749261-nuflsns5cn-0de4e0f6e495e02bc5ee0c853d56b95f%2Fflower-3.jpeg

如果保存在应用程序的数据库中,然后使用upload.expiring_url(对于名为upload的附件)访问生成的过期URL,则显示 SignatureDoesNotMatch 从Amazon S3返回。

但是,如果我们在保存到应用程序的数据库之前清理最初收到的密钥:

  1. 解码密钥中的编码值
  2. 从密钥
  3. 中删除前缀正斜杠(“/”)

    然后访问时生成的到期URL允许成功访问资源。

    如果有人遇到此问题,我将在此处提供该模型的工作代码段。其他代码段可以在我的上述评论中找到。

    <强> /app/models/asset.rb

      class Asset < ActiveRecord::Base
        include S3Decider
    
        attr_accessible :upload, :upload_file_name, :upload_content_type, upload_file_size, :upload_updated_at
        attr_accessible :key
    
        paperclip_options_for_upload = paperclip_options_for_upload.merge(
          path: ":key",
          url: ":s3_conditional_url",
          s3_url_options: lambda { |model|
            {
               response_content_type: model.upload_content_type,
               response_content_disposition: "inline"
            }
          }
        )
    
        has_attached_file :upload, paperclip_options_for_upload
    
        def key=(key)
          return if key.blank?
          # S3DirectUpload receives the Amazon S3 bucket object's key
          # as encoded and prefixed by a forward slash.For e.g.
          # /uploads%2F1390554749261-nuflsns5cn-0de4e0f6e495e02bc5ee0c853d56b95f%2Fflower-3.jpeg
          # Sanitizing it here else programmatically accessing the bucket object
          # corresponding to the key prefixed with "/" shall throw a No such key
          # exception.
          sanitized_key = key.sub(%r{^/},'')
          decoded_key = CGI.unescape(sanitized_key)
          write_attribute(:key, decoded_key)
        end
    
      end
    

    在搜索解决方案时,我遇到了拉取请求#769,它提到了我正面临的问题。

    当我使用paperclip(2.7.4)时,它的S3存储不包含在pull请求中完成的更改。我通过检查rvm目录下本地安装的gem中的源代码来验证。因此我更改了上面提到的代码以下内容:

    <强> /config/initializers/paperclip.rb

    Paperclip::ASSET_EXPIRATION_TIME = 3600.seconds
    
    Paperclip.interpolates(:key) do |attachment, style|
      attachment.instance.key
    end
    
    Paperclip.interpolates(:s3_conditional_url) do |attachment, style|
      attachment.expiring_url(Paperclip::ASSET_EXPIRATION_TIME, style)
    end
    
    module Paperclip::Storage::S3
      def public_url(style_name = default_style)
        if path
          "http://#{s3_host_name}/#{bucket_name}/#{path(style_name)}"
        end
      end
    
      def expiring_url(time = Paperclip::ASSET_EXPIRATION_TIME, style_name = default_style)
         if path
         # Reference: https://github.com/thoughtbot/paperclip/pull/769
         base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
         s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
         end
      end
    
      # Reference: https://github.com/thoughtbot/paperclip/pull/769
      def s3_url_options
        s3_url_options = @options[:s3_url_options] || {}
        s3_url_options = s3_url_options.call(instance) if s3_url_options.is_a?(Proc)
        s3_url_options
      end
    end
    

    谢谢,

    Jignesh