Создание подписей для запросов REST и их аутентификация

Этот раздел описывает процесс аутентификации, осуществляемый с помощью предыдущей версии сервиса подписи. На данный момент сервис поддерживает последнюю версию сервиса подписи.

Аутентификация — это процесс подтверждения своей личности в системе. Идентификация личности является важным фактором при принятии решений по управлению доступом в сервисе. Разрешение и запрет запросов частично зависит от личности запрашивающего. Например, право создавать бакеты закреплено за зарегистрированными разработчиками, а право создавать объекты в бакете по умолчанию закреплено за его владельцем. Если вы являетесь разработчиком, то вы будете создавать запросы, использующие данные привилегии, и вам будет необходимо подтверждать свою личность в системе путем аутентификации ваших запросов. В этом разделе описывается данный процесс.

API REST сервиса использует свою собственную схему HTTP, которая основывается на предназначенном для аутентификации коде HMAC с ключом. Для аутентификации запроса необходимо сначала объединить избранные элементы запроса, создав строку, а затем использовать секретный ключ доступа сервиса для вычисления кода HMAC для этой строки. Неофициально этот процесс называется «созданием подписи для запроса», а выходные данные алгоритма HMAC — «подписью», так как она обладает свойствами настоящей подписи. В последнюю очередь вы добавляете эту подпись в качестве параметра запроса, используя синтаксическую конструкцию, описанную в этом разделе.

При поступлении прошедшего аутентификацию запроса система извлекает секретный ключ доступа сервиса, о котором вы заявляете ей, и использует этот ключ указанным ранее способом, вычисляя подпись для полученного сообщения. Затем система сравнивает вычисленную ей подпись с представленной запрашивающим. Если эти две подписи совпадают, то система делает вывод, что запрашивающий имеет доступ к секретному ключу доступа сервиса и, следовательно, может осуществлять действия, разрешенные для сущности, которой был выдан ключ. Если же подписи не совпадают, то запрос отбрасывается и система выводит сообщение об ошибке.

 GET /photos/puppy.jpg HTTP/1.1
 Host: my.hb.bizmrg.com
 Date: Mon, 26 Mar 2007 19:37:58 +0000
 Authorization: AWS AKIAIOSFODNN7EXAMPLE:frJIUN8DYpKDtOLCwo//yllqDzg=

Заголовок аутентификации

API REST сервиса использует стандартный HTTP-заголовок Authorization для передачи информации об аутентификации (неудачное имя для этого стандартного заголовка, так как оно содержит информацию об аутентификации, а не об авторизации). В схеме аутентификации сервиса заголовок Authorization имеет следующий формат:

 Authorization: AWS AWSAccessKeyId:Signature

Разработчикам при регистрации выдается идентификатор ключа доступа сервиса и секретный ключ доступа сервиса. Для аутентификации запроса элемент AWSAccessKeyId определяет идентификатор ключа доступа, использованный для вычисления подписи. Этот элемент также опосредованно определяет создающего запрос разработчика.

Элементом подписи Signature является значение HMAC-SHA1 (RFC 2104) для выбранных из запроса элементов, и, следовательно, часть Signature заголовка Authorization будет различной для каждого запроса. Если подпись запроса, вычисленная системой, совпадает с подписью, включенной в запрос, то это будет означать, что у запрашивающего действительно есть секретный ключ доступа сервиса. Затем запрос обработается — система будет использовать личность и полномочия разработчика, которому был выдан этот ключ.

Ниже следует псевдокод, иллюстрирующий конструкцию заголовка запроса на авторизацию (в примере «\n» означает кодовую точку Юникода U+000A, более известную как «новая строка»).

 Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;

 Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );

 StringToSign = HTTP-Verb + "\n" +
        Content-MD5 + "\n" +
        Content-Type + "\n" +
        Date + "\n" +
        CanonicalizedAmzHeaders +
        CanonicalizedResource;
 
 CanonicalizedResource = [ "/" + Бакет ] +
        <URI-Запрос-HTTP, от имени протокола до строки запроса> +
        [ подресурс, при его наличии, например, "?acl", ];
 CanonicalizedAmzHeaders = <описание идет ниже>

Алгоритм HMAC-SHA1 описан в документе «RFC 2104 — Keyed-Hashing for Message Authentication» («RFC 2104 — код аутентификации сообщений, использующий хеш-функции с ключом»). В качестве входных данных алгоритм принимает две строки байтов — ключ и сообщение. Для аутентификации запроса сервиса в качестве ключа воспользуйтесь своим секретным ключом доступа сервиса (YourSecretAccessKeyID), а в качестве сообщения — кодировкой UTF-8 подписываемой строки (StringToSign). Выходными данными HMAC-SHA1 также является строка байтов, называемая сверткой. Параметр запроса подписи Signature сформирован шифрованием этой свертки с помощью алгоритма Base64.

Каноникализация запроса для создания подписи

Когда система получает аутентифицированный запрос, она сравнивает вычисленную подпись запроса с подписью, предоставленной в запросе в StringToSign. По этой причине вы должны вычислять подпись с помощью того же метода, который используется сервисом. Процесс выражения запроса в согласованном для подписи форматом называется каноникализацией.

Формирование элемента CanonicalizedResource

CanonicalizedResource («канонизированный ресурс») представляет собой ресурс сервиса, на который ориентируется запрос. CanonicalizedResource формируется для запроса REST нижеследующим образом.

Процесс запуска

1 Начните с пустой строки («»).
2

Если запрос указывает бакет при помощи заголовка HTTP Host (virtual-hosted-style), то добавьте перед именем бакета «/» (например, «/bucketname»). Для запросов path-style и запросов без обращения к бакету ничего делать не нужно.

CanonicalizedResource для запроса virtual-hosted-style «https://my.hb.bizmrg.com/photos/puppy.jpg» — «/my».

CanonicalizedResource для запроса path-style «https://hb.bizmrg.com/my/photos/puppy.jpg» — «».
3

Добавьте часть пути недекодированного URI-запроса HTTP вплоть до строки запроса, но не включая ее.

CanonicalizedResource для запроса virtual-hosted-style «https://my.hb.bizmrg.com/photos/puppy.jpg» — «/my/photos/puppy.jpg».

CanonicalizedResource для запроса path-style «https://hb.bizmrg.com/my/photos/puppy.jpg» — «/my/photos/puppy.jpg». На этой стадии CanonicalizedResource одинаков для запросов virtual-hosted-style и path-style.

Для запросов, которые не обращается к бакету, таких как сервис GET, добавьте «/».
4

Если запрос обращается к подресурсу, такому как ?versioning, ?location, ?acl, ?torrent, ?lifecycle или ?versionid, то добавьте сам подресурс, его значение (при его наличии) и вопросительный знак. Обратите внимание, что в случае обращения к нескольким подресурсам, эти подресурсы должны быть отсортированы в лексикографическом порядке по имени подресурса и разделены символом «&», например «?acl&versionId=значение».

Подресурсами, которыми необходимо воспользоваться при формировании элемента CanonicalResource, являются acl, lifecycle, location, logging, notification, partNumber, policy, requestPayment, torrent, uploadId, uploads, versionId, versioning, versions и website.

Если запрос указывает параметры строки запроса, которые подменяют значения заголовка ответа, то добавьте параметры строки запроса и их значения. При создании подписи вы не кодируете эти значения. Однако при создании запроса вы должны закодировать эти значения параметра. В запросе GET параметры строки запроса содержат следующие элементы:

response-content-type, response-content-language, response-expires,response-cache-control, response-content-disposition и response-content-encoding.

При создании подписи элементы CanonicalizedResource из URI-запроса HTTP должны выглядеть точно так же, как в HTTP-запросе, включая метасимволы URL-кодировки.

CanonicalizedResource может отличатся от URI-запроса HTTP. В частности, если ваш запрос использует заголовок HTTP Host для указания бакета, то этот бакет не появляется в URI-запросе HTTP. Однако CanonicalizedResource продолжает включать в себя бакет. Параметры строки запроса могут также появляться в URI-запросе, но при этом они не включаются в CanonicalizedResource.

Формирование элемента CanonicalizedAmzHeaders

Для формирования части CanonicalizedAmzHeaders («канонизированные заголовки сервиса») подписываемой строки StringToSign выберите все заголовки запроса HTTP, которые начинаются с x-amz- (без учета регистра) и используют нижеуказанный процесс.

Процесс CanonicalizedAmzHeaders

1 Конвертируйте каждое имя заголовка HTTP в нижний регистр. Например, X-Amz-Date должно стать x-amz-date.
2 Отсортируйте коллекцию заголовков в лексикографическом порядке по имени заголовка.
3 Объедините поля заголовка с одинаковыми именами в одну пару «имя-заголовка: список-значений-разделенный-запятой» без пробельных символов между значениями, как это установлено в разделе 4.2 документа RFC 2616. Например, два заголовка метаданных x-amz-meta-username: fred и x-amz-meta-username: barney объединяются в один заголовок x-amz-meta-username: fred,barney.
4 Уберите переносы в длинных заголовках, которые занимают несколько строк (как это допускается в разделе 4.2 документа RFC 2616), путем замещения пробельных символов с переносом (включая символ новой строки) одним пробелом.
5 Удалите любые пробельные символы рядом с двоеточием в заголовке. Напримерзаголовок x-amz-meta-username: fred,barney станет x-amz-meta-username:fred,barney.
6 В заключении добавьте символ новый строки (U+000A) к каждому канонизированному заголовку в результирующем списке. Сформируйте элемент CanonicalizedResource путем объединения всех заголовков в этом списке в одну строку.

Позиционные и именованные элементы StringToSign HTTP-заголовка


Первые несколько элементов-заголовков StringToSign (Content-Type, Date и Content-MD5) являются позиционными по своей природе. StringToSign не включает в себя имена этих заголовков — включены только их значения из запроса. В то же время элементы x-amz- являются именованными. Как имена заголовков, так и значения заголовков появляются в StringToSign.


Если позиционный заголовок, запрашиваемый в элементе StringToSign не присутствует в вашем запросе (например, Content-Type или Content-MD5 являются необязательными для запросов PUT и ненужными для запросов GET), то замените его пустой строкой («»).

Требование к временной метке


Допустимая временная метка (использующая заголовок HTTP Date или вариант x-amz-date) обязательна для аутентифицированных запросов. Кроме того, при получении запроса временная метка клиента, которая включается в аутентифицированный запрос, должна расходится с системным временем сервиса не более, чем на 15 минут. Если расхождение превышает это значение, то запрос не будет обработан и будет возвращен код ошибки «RequestTimeTooSkewed». Цель этих действий — ограничить любую возможность того, что перехваченные запросы могут быть повторно использованы злоумышленниками. Для повышенной защиты от прослушивания используйте передачу данных по HTTPS для аутентифицированных запросов.

Проверка по дате запроса применима только к аутентифицированным запросам, которые не используют аутентификацию по строке запроса. Для получения дополнительной информации смотрите раздел «Вариант аутентификации запроса по строке запроса».

Некоторые библиотеки HTTP-клиента не дают возможности устанавливать заголовок даты (Date) для запроса. Если у вас возникли проблемы при использовании значения заголовка Date в канонизированных заголовках, то вы можете установить метку времени для запроса, воспользовавшись вместо этого заголовком x-amz-date. Значение заголовка x-amz-date должно иметь один из форматов, описанных в документе RFC 2616 (http://www.ietf.org/rfc/rfc2616.txt). Если заголовок x-amz-date присутствует в запросе, то система проигнорирует любой заголовок даты Date при вычислении подписи запроса. Следовательно, если вы включаете заголовок x-amz-date, то используйте пустую строку для даты Date при формировании подписываемой строки StringToSign. Пример рассмотрен в следующем разделе.

Примеры аутентификации

В таблице ниже приведены в качестве примера данные учетной записи (учетная запись приведена для примера и не является действительной).


Параметр Значение
AWSAccessKeyId AKIAIOSFODNN7EXAMPLE
AWSSecretAccessKey wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

В примере с элементом StringToSign форматирование не имеет значения, а строка «\n» обозначает кодовую точку Юникода U+000A, более известную как «новая строка». В примерах указана строка «+0000» для обозначения часового пояса. Как вариант, вы можете воспользоваться строкой «GMT» для обозначения часового пояса, но тогда подпись, указанная в примерах ниже, будет отличаться.

Операция GET для объекта

Данный пример получает объект из бакета «my».


Запрос Подписываемая строка StringToSign
 GET /photos/puppy.jpg HTTP/1.1
 Host: my.hb.bizmrg.com
 Date: Tue, 27 Mar 2007 19:36:42 +0000
 Authorization: AWS AKIAIOSFODNN7EXAMPLE: 
 bWq2s1WEIj+Ydj0vQ697zp+IXMU=
 GET\n
 \n
 \n
 Tue, 27 Mar 2007 19:36:42 +0000\n
 /my/photos/puppy.jpg

Необходимо отметить, что CanonicalizedResource включает в себя имя бакета, а URI-запрос HTTP — не включает его (бакет указывается заголовком Host).

Операция PUT для объекта

Данный пример помещает объект в бакет «my».


Запрос Подписываемая строка StringToSign
 PUT /photos/puppy.jpg HTTP/1.1
 Content-Type: image/jpeg
 Content-Length: 94328
 Host: my.hb.bizmrg.com
 Date: Tue, 27 Mar 2007 21:15:12 +0000
 Authorization: AWS AKIAIOSFODNN7EXAMPLE: 
 MyyxeRY7whkBe+bq8fHCL/2kKUg=
 PUT\n
 \n
 image/jpeg\n
 Tue, 27 Mar 2007 21:15:12 +0000\n
 /my/photos/puppy.jpg

Необходимо отметить, что заголовок Content-Type находится как в запросе, так и в StringToSign. Также важно, что Content-MD5 остается пустым в StringToSign, так как он не присутствует в запросе.

Просмотр списка

Данный пример приводит список содержимого бакета «my».


Запрос Подписываемая строка StringToSign
 GET /?prefix=photos&max-keys=50&marker=puppy HTTP/1.1
 User-Agent: Mozilla/5.0
 Host: my.hb.bizmrg.com
 Date: Tue, 27 Mar 2007 21:15:12 +0000
 Authorization: AWS AKIAIOSFODNN7EXAMPLE: 
 htDYFYduRNen8P9ZfE/s9SuKy0U=
 GET\n
 \n
 \n
 Tue, 27 Mar 2007 21:15:12 +0000\n
 /my/

Необходимо отметить, что после элемента CanonicalizedResource ставится косая черта, а строка запроса не имеет параметров строки запроса.

Извлечение

Данный пример извлекает подресурс политики управления доступом для бакета «my».


Запрос Подписываемая строка StringToSign
 GET /?acl HTTP/1.1
 Host: my.hb.bizmrg.com
 Date: Tue, 27 Mar 2007 21:15:12 +0000
 Authorization: AWS AKIAIOSFODNN7EXAMPLE: 
 c2WLPFtWHVgbEmeEG93a4cG37dM=
 GET\n
 \n
 \n
 Tue, 27 Mar 2007 21:15:12 +0000\n
 /my/?acl

Важен тот факт, что параметр строки запроса подресурса включен в элемент CanonicalizedResource.

Удаление

Данный пример удаляет объект из бакета «my» при помощи path-style и альтернативного указания даты.


Запрос Подписываемая строка StringToSign
 DELETE /my/photos/puppy.jpg HTTP/1.1
 User-Agent: dotnet
 Host: my.hb.bizmrg.com
 Date: Tue, 27 Mar 2007 21:15:12 +0000
 Authorization: AWS AKIAIOSFODNN7EXAMPLE: 
 lx3byBScXR6KzyMaifNkardMwNk=
 GET\n
 \n
 \n
 Tue, 27 Mar 2007 21:15:12 +0000\n
 /my/photos/puppy.jpg

Здесь важно отметить то, что использовался x-amz-date в качестве альтернативного способа указания даты (например, из-за того, что библиотека клиента не позволила установить дату). В данном случае x-amz-date является приоритетной над заголовком Date. Следовательно, запись с датой в подписи должна содержать значение заголовка x-amz-date.

Загрузка

Данный пример загружает объект в содержащий метаданные бакет со стилем виртуального хостинга и каноническим именем.


Запрос Подписываемая строка StringToSign
 PUT /db-backup.dat.gz HTTP/1.1
 User-Agent: curl/7.15.5
 Host: static.my.net:8080
 Date: Tue, 27 Mar 2007 21:15:12 +0000
 
 x-amz-acl: public-read
 content-type: application/x-download
 Content-MD5: 4gJE4saaMU4BqNR0kLY+lw==
 filename=database.dat
 Content-Length: 5913339
 
 Authorization: AWS AKIAIOSFODNN7EXAMPLE: 
 ilyl83RwaSoYIEdixDQcA4OnAnc=
 GET\n
 4gJE4saaMU4BqNR0kLY+lw==\n
 application/x-download\n
 Tue, 27 Mar 2007 21:15:12 +0000\n
 
 x-amz-acl:public-read\n

Здесь важно отметить, что заголовки x-amz- отсортированы, конвертированы в нижний регистр, а пробельные символы в них удалены. Также несколько заголовков с одним и тем же именем объединены с помощью запятых, разделяющих значения, при этом только заголовки сущности HTTP Content-Type и Content-MD5 появляются в StringToSign.

 Прочие заголовки сущности, начинающиеся с Content-, там не появляются.

Необходимо также отметить, что CanonicalizedResource включает в себя имя бакета, а URI-запрос HTTP — не включает его (бакет указывается заголовком Host).

Просмотр списка всех своих бакетов

Запрос Подписываемая строка StringToSign
 GET / HTTP/1.1
 Host: hb.bizmrg.com
 Date: Tue, 27 Mar 2007 21:15:12 +0000
 Authorization: AWS AKIAIOSFODNN7EXAMPLE: 
 qGdzdERIC03wnaRNKh6OqZehG9s=
 GET\n
 \n
 \n 
 Tue, 27 Mar 2007 21:15:12 +0000\n
 /

Ключи Юникода

Запрос Подписываемая строка StringToSign
 GET /dictionary/fran%C3%A7ais/pr%c3%a9f%c3%a8re HTTP/1.1
 Host: hb.bizmrg.com
 Date: Wed, 28 Mar 2007 01:49:49 +0000
 Authorization: AWS AKIAIOSFODNN7EXAMPLE: 
 DNEZGsoieTZ92F3bUfSPQcbGmlM=
 GET\n
 \n
 \n 
 Wed, 28 Mar 2007 01:49:49 +0000\n
 /dictionary/fran%C3%A7ais/pr%c3%a9f%c3%a8re

Элементы в подписываемой строке StringToSign, полученные из URI-запроса, обрабатываются так, как они выглядят, включая их кодировку URL и символьный регистр.

Проблемы при создании подписей для запросов REST

При ошибках аутентификации запроса REST, система отвечает на запрос документом в формате XML с описанием ошибок. Информация, содержащаяся в этом документе, помогает разработчикам диагностировать возникающие проблемы. В частности, элемент StringToSign документа с описанием ошибки «SignatureDoesNotMatch» четко указывает, какую каноникализацию запроса использует система.

Некоторые инструментальные средства незаметно для разработчика вставляют заголовки, например, заголовок Content-Type, во время создания операции PUT. В большинстве таких случаев значение вставляемого заголовка неизменно, что позволяет вам обнаружить отсутствующие заголовки с помощью таких инструментальных средств, как Etheral или tcpmon.

Вариант аутентификации запроса по строке запроса

Вы можете производить аутентификацию некоторых типов запроса путем передачи требуемой информации в качестве параметров строки запроса, не прибегая к помощи HTTP-заголовка Authorization. Это полезно в случае разрешения предоставления доступа сторонним браузерам к вашим личным данным в сервисе без проксирования запроса. Идея заключается в том, чтобы сформировать «заранее подписанный» запрос и закодировать его в URL-строке, которую может обработать браузер конечного пользователя. Кроме того, вы можете устанавливать ограничение на «заранее подписанный» запрос, указав дату истечения его срока действия.

Создание подписи

Ниже приведен пример запроса REST сервиса, аутентифицированного по строке запроса.

 GET /photos/puppy.jpg
 ?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Expires=1141889120&Signature=vjbyPxybdZaNmGa%2ByT272YEAiv4%3D HTTP/1.1
 Host: my.hb.bizmrg.com
 Date: Mon, 26 Mar 2007 19:37:58 +0000

Способ аутентификации запроса по строке запроса не требует каких-либо особенных HTTP-заголовков. Вместо этого требуемые элементы аутентификации указываются в качестве параметров строки запроса.


Имя параметра строки запроса Пример значения Описание
AWSAccessKeyId AKIAIOSFODNN7EXAMPLE Ваш идентификатор ключа доступа сервиса, указывающий секретный ключ доступа сервиса. Ключ используется для создания подписи для запроса и опосредованно определяет личность создающего запрос разработчика.
Expires 1141889120 Время истечения срока действия подписи, указываемое в количествах секунд от опорной даты (00:00:00 UTC, 1 января 1970 г.). Запросы получаемые после этого времени (согласно времени на сервере) будут отклонены.
Signature vjbyPxybdZaNmGa%2ByT272YEAiv4%3D URL-кодировка зашифрованного алгоритмом Base64 кода HMAC-SHA1 подписываемой строки StringToSign.

Способ аутентификации запроса по строке запроса немного отличается от обыкновенного, но только в плане формата параметра запроса записи и элемента StringToSign. Ниже следует псевдокод, иллюстрирующий способ аутентификации запроса по строке запроса.

 Signature = URL-Кодировка( Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Кодировка( StringToSign ) ) ) );
 
 StringToSign = HTTP-ГЛАГОЛ + "\n" +
    Content-MD5 + "\n" +
    Content-Type + "\n" +
    Expires + "\n" +
    CanonicalizedAmzHeaders +
    CanonicalizedResource; 

YourSecretAccessKeyID является идентификатором секретного ключа доступа. Этот идентификатор предоставляется вам сервисом во время вашей регистрации в качестве разработчика веб-сервиса. Важно, чтобы в подписи применялась URL-кодировка, что позволит разместить эту подпись в строке запроса. Также необходимо отметить, что в StringToSign, позиционный HTTP-элемент Date был замещен элементом Expires. Элементы CanonicalizedAmzHeaders и CanonicalizedResource одинаковы.

В способе аутентификации по строке запроса вы не используете заголовок запроса Date или x-amz-date при вычислении подписываемой строки.

Аутентификация запроса по строке запроса

Запрос Подписываемая строка StringToSign
 GET /photos/puppy.jpg?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&
    Signature=NpgCjnDzrM%2BWFzoENXmpNDUsSn8%3D&
    Expires=1175139620 HTTP/1.1
 Host: my.hb.bizmrg.com
 GET\n
 \n
 \n 
 1175139620\n
 
 /my/photos/puppy.jpg

Предполагается, что браузер, создавая запрос GET, не предоставит заголовок Content-MD5 или Content-Type и не укажет какие-либо заголовки x-amz-, так что эти части элемента StringToSign остаются пустыми.

Шифрование алгоритмом Base64

Подписи запроса HMAC должны быть зашифрованы алгоритмом Base64. Шифрование алгоритмом Base64 конвертирует подпись в простую строку формата ASCII, которую можно будет прикрепить к запросу. Символы, которые могут появиться в строке подписи, такие как плюс («+»), косая черта («/») и знак равенства («=»), при использовании в URI должны быть зашифрованы. Например, если код аутентификации включает в себя знак («+»), то в запросе данный знак будет закодирован как «%2B». Косая черта будет закодирована как «%2F», а знак равенства — как «%3D».