2013-12-20 70 views
12

我有一个需要上传文件到S3的播放应用程序。我们正在开发scala并使用Java AWS SDK。亚马逊S3签名不匹配 - AWS SDK Java

我在尝试上传文件时遇到问题,在使用预先登录的网址时,我不断收到403 SignatureDoesNotMatch。网址是正在使用AWS的Java SDK通过下面的代码genereated:

def generatePresignedPutRequest(filename: String) = { 
    val expiration = new java.util.Date(); 
    var msec = expiration.getTime() + 1000 * 60 * 60; // Add 1 hour. 
    expiration.setTime(msec); 

    s3 match { 
     case Some(s3) => s3.generatePresignedUrl(bucketname, filename, expiration, HttpMethod.PUT).toString 
     case None => { 
     Logger.warn("S3 is not availiable. Cannot generate PUT request.") 
     "URL not availiable" 
     } 
    } 
    } 

针对前端代码中,我们遵循ioncannon article

该上传文件(相同于本文中使用的一个)

function uploadToS3(file, url) 
    { 
     var xhr = createCORSRequest('PUT', url); 
     if (!xhr) 
     { 
     setProgress(0, 'CORS not supported'); 
     } 
     else 
     { 
     xhr.onload = function() 
     { 
      if(xhr.status == 200) 
      { 
      setProgress(100, 'Upload completed.'); 
      } 
      else 
      { 
      setProgress(0, 'Upload error: ' + xhr.status); 
      } 
     }; 

     xhr.onerror = function() 
     { 
      setProgress(0, 'XHR error.'); 
     }; 

     xhr.upload.onprogress = function(e) 
     { 
      if (e.lengthComputable) 
      { 
      var percentLoaded = Math.round((e.loaded/e.total) * 100); 
      setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.'); 
      } 
     }; 

     xhr.setRequestHeader('Content-Type', 'image/png'); 
     xhr.setRequestHeader('x-amz-acl', 'authenticated-read'); 

     xhr.send(file); 
     } 
    } 

服务器的响应是

<?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>50 55 bla bla bla...</StringToSignBytes> 
<RequestId>F7A8F1659DE5909C</RequestId> 
<HostId>q+r+2T5K6mWHLKTZw0R9/jm22LyIfZFBTY8GEDznfmJwRxvaVJwPiu/hzUfuJWbW</HostId> 
<StringToSign>PUT 

    image/png 
    1387565829 
    x-amz-acl:authenticated-read 
    /mybucketname/icons/f5430c16-32da-4315-837f-39a6cf9f47a1</StringToSign> 
<AWSAccessKeyId>myaccesskey</AWSAccessKeyId></Error> 

我已经配置CORS,双重检查AWS凭证和JS的功能尝试更改请求标头。我总是得到相同的结果。 亚马逊为什么告诉我签名不匹配?

回答

19

怀疑OP仍然有这个问题,但对于其他人谁运行到这一点,这里是答案:

制作签名的请求到S3时,AWS检查,以确保该签名完全匹配浏览器发送的HTTP头信息。这是不幸的是必读:http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html

然而,在这上面的代码是不实际的情况下,JavaScript是发送:

xhr.setRequestHeader('Content-Type', 'image/png'); 
xhr.setRequestHeader('x-amz-acl', 'authenticated-read'); 

但在Java/Scala中,s3.generatePresignedUrl被称为不及格在其中任何一个。所以得到的签名实际上是告诉S3拒绝任何具有Content-Type或x-ams-acl头部集合的东西。糟糕(我也为此而堕落)。

我见过浏览器会自动发送内容类型,所以即使它们没有明确添加到标题中,他们仍然可能会进入S3。所以问题是,我们如何将Content-Type和x-amz-acl头添加到签名中?

AWS SDK中有几个重载的generatePresignedUrl函数,但其​​中只有一个允许我们传入除bucket-name,filename,expiration-date和http-method之外的其他任何内容。

解决的办法是:

  1. 创建GeneratePresignedUrlRequest对象,用你的水桶和文件名。
  2. 调用setExpiration,setContentType等来设置它的所有标题信息。
  3. 将其作为唯一参数传递给s3.generatePresignedUrl。

这里的GeneratePresignedUrlRequest的正常功能定义中使用:

http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3Client.html#generatePresignedUrl(com.amazonaws.services.s3.model.GeneratePresignedUrlRequest)

函数的对AWS GitHub库代码也有助于我看到如何编写了解决方案。希望这可以帮助。

+0

好答案!注意:如果你想为S3的响应设置Content-Type头(即当AWS将响应发送回客户端时),你必须创建一个ResponseHeaderOverrides的新实例,在* that *上调用setContentType()然后在您的GeneratePresignedUrlRequest实例上调用setRequestHeaders(RequestHeaderOverrides)。在GeneratePresignedUrlRequest实例上调用setContentType会导致上述问题的另一个变体 - 由S3接收到的HTTP请求与预先登记的请求不同。 –

+0

更新的文档可在以下网址找到:http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html –

+0

我从未在一百万年内了解到这一点。谢谢 – user2465134

7

我刚刚使用NodeJs AWS SDK遇到了这个问题。 这是由于使用有效的凭证,但没有足够的权限。 更改为我的管理员密码解决了此问题,无需更改代码!

+0

我也拿到了无效凭证。曾使用错误的密钥。 – IanBussieres

+0

谢谢你的这篇文章。这可能会帮助我弄清楚为什么我正在做的不工作.... – Doug

0

有一个问题,Windows上的MIME类型是将fileType设置为空字符串,它不起作用。只需处理空字符串并添加一些文件类型。

0

我有同样的问题,但删除内容类型工作正常。在此共享完整的代码。

public class GeneratePresignedUrlAndUploadObject { 
    private static final String BUCKET_NAME = "<YOUR_AWS_BUCKET_NAME>"; 
    private static final String OBJECT_KEY = "<YOUR_AWS_KEY>"; 
    private static final String AWS_ACCESS_KEY = "<YOUR_AWS_ACCESS_KEY>"; 
    private static final String AWS_SECRET_KEY = "<YOUR_AWS_SECRET_KEY>"; 

    public static void main(String[] args) throws IOException { 
     BasicAWSCredentials awsCreds = new BasicAWSCredentials(AWS_ACCESS_KEY, AWS_SECRET_KEY); 

     AmazonS3 s3Client = AmazonS3ClientBuilder.standard().withRegion(Regions.US_EAST_1) 
       .withCredentials(new AWSStaticCredentialsProvider(awsCreds)).build(); 

     try { 
      System.out.println("Generating pre-signed URL."); 
      java.util.Date expiration = new java.util.Date(); 
      long milliSeconds = expiration.getTime(); 
      milliSeconds += 1000 * 60 * 60; 
      expiration.setTime(milliSeconds); 

      GeneratePresignedUrlRequest generatePresignedUrlRequest = 
        new GeneratePresignedUrlRequest(BUCKET_NAME, OBJECT_KEY); 
      generatePresignedUrlRequest.setMethod(HttpMethod.PUT); 
      generatePresignedUrlRequest.setExpiration(expiration); 
      URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest); 

      UploadObject(url); 

      System.out.println("Pre-Signed URL = " + url.toString()); 
     } catch (AmazonServiceException exception) { 
      System.out.println("Caught an AmazonServiceException, " + 
        "which means your request made it " + 
        "to Amazon S3, but was rejected with an error response " + 
      "for some reason."); 
      System.out.println("Error Message: " + exception.getMessage()); 
      System.out.println("HTTP Code: " + exception.getStatusCode()); 
      System.out.println("AWS Error Code:" + exception.getErrorCode()); 
      System.out.println("Error Type: " + exception.getErrorType()); 
      System.out.println("Request ID: " + exception.getRequestId()); 
     } catch (AmazonClientException ace) { 
      System.out.println("Caught an AmazonClientException, " + 
        "which means the client encountered " + 
        "an internal error while trying to communicate" + 
        " with S3, " + 
      "such as not being able to access the network."); 
      System.out.println("Error Message: " + ace.getMessage()); 
     } 
    } 

    public static void UploadObject(URL url) throws IOException 
    { 
     HttpURLConnection connection=(HttpURLConnection) url.openConnection(); 
     connection.setDoOutput(true); 
     connection.setRequestMethod("PUT"); 
     OutputStreamWriter out = new OutputStreamWriter(
       connection.getOutputStream()); 
     out.write("This text uploaded as object."); 
     out.close(); 
     int responseCode = connection.getResponseCode(); 
     System.out.println("Service returned response code " + responseCode); 

    } 
}