Подпись Amazon S3 не соответствует - AWS SDK Java

У меня есть приложение для воспроизведения, которое должно загружать файлы на S3. Мы развиваемся в scala и используя Java AWS SDK.

У меня возникли проблемы с загрузкой файлов, я продолжаю получать 403 SignatureDoesNotMatch при использовании назначенных URL-адресов. URL-адрес создается с использованием AWS Java SDK с помощью следующего кода:

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"
      }
    }
  }

Для кода frontend мы выполнили статью о ионном оружии.

Функция js, которая загружает файл (тот же, что используется в статье)

 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 и попытался изменить заголовки запросов. Я всегда получаю тот же результат. Почему Amazon говорит мне, что подписи не совпадают?

Ответ 1

Сомневаюсь, что у 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. Упс (я тоже за это напал).

Я видел, что браузеры автоматически отправляют Content-Types, поэтому, даже если они явно не добавлены в заголовок, они все равно могут попасть в S3. Итак, вопрос в том, как добавить в подпись заголовки Content-Type и x-amz-acl?

В AWS SDK есть несколько перегруженных функций generatePresignedUrl, но только один из них позволяет нам передавать что-либо еще, кроме имени ведра, имени файла, даты истечения срока действия и http-метода.

Решение:

  • Создайте объект GeneratePresignedUrlRequest с вашим ведром и именем файла.
  • Вызов setExpiration, setContentType и т.д., чтобы установить на нем всю информацию заголовка.
  • Передайте это значение в 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 также помог мне понять, как кодировать решение. Надеюсь, это поможет.

Ответ 2

Я просто столкнулся с этой проблемой, используя NodeJs AWS SDK. Это связано с использованием учетных данных, которые были действительными, но без достаточных разрешений. Переход на мой ключ администратора исправил это без изменений кода!

Ответ 3

Я столкнулся с аналогичной проблемой, и настройка config signatureVersion: 'v4' помогла решить ее в моем случае -

В JavaScript:

var s3 = new AWS.S3({
  signatureVersion: 'v4'
});

Адаптировано с https://github.com/aws/aws-sdk-js/issues/902#issuecomment-184872976

Ответ 4

У меня была одна и та же проблема, но удаление контента контента прекрасное. Таким образом, общий код.

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);

    }
}

Ответ 5

Появилась проблема: тип mime в окнах устанавливал fileType для пустых строк, и это не сработало. Просто обрабатывайте пустые строки и добавляйте некоторый тип файла.

Ответ 6

Я столкнулся с ошибкой SignatureDoesNotMatch при использовании Java AWS SDK. В моем случае ошибка SignatureDoesNotMatch произошла после обновления зависимостей maven без изменений в моем коде (поэтому учетные данные верны и не были изменены). После обновления зависимостей org.apache.httpcomponents:httpclient с версии 4.5.6 до 4.5.7 (фактически это было обновление Spring Boot с 2.1.2 до 2.1.3, и там bom указал версию httpclient), код стал генерировать исключения, пока выполнение некоторых запросов AWS SDK S3, таких как AmazonS3.getObject.

После httpclient основной причины я обнаружил, что библиотека httpclient изменения с помощью нормализованного URI, что затронуло Java AWS SDK S3. Пожалуйста, обратите внимание на открытый билет GitHub org.apache.httpcomponents: httpclient: 4.5.7 прерывает выборку объектов S3 для получения более подробной информации.

Ответ 7

Та же проблема для меня, но другая причина. Я использовал POST вместо PUT