Тема
Аутентификация и подписи
В системе живут несколько независимых способов доказать, что запрос пришёл от того, за кого себя выдают. Они применяются на разных границах, поэтому «почему не проходит авторизация» — вопрос без единого ответа, пока не понятно, о какой границе речь.
| Граница | Чем доказывается |
|---|---|
| Мерчант приходит к нам | Пара «ключ и секрет» либо подпись тела секретом мерчанта |
| Наш сервис приходит к нашему сервису | Подпись ключом AWS KMS и штамп времени |
| Служебная интеграция приходит к нам | Отдельный ключ, выданный именно ей |
| Мы приходим к мерчанту с колбеком | Подпись тела секретом этого мерчанта |
Как аутентифицируется мерчант
Способа два, и мерчант выбирает сам. Первый — обычная пара «ключ мерчанта и секрет», передаваемая как Basic. Второй — подпись: значения полей тела запроса сортируются по именам полей, склеиваются через вертикальную черту, и от этой строки считается HMAC секретом мерчанта.
Важно, кто именно проверяет. Шлюз — публичный или PCI — только достаёт учётные данные и определяет способ. Само сравнение делает сервис авторизации, к которому шлюз обращается по HTTP; секрет мерчанта хранится в Secrets Manager и кэшируется, чтобы не ходить туда на каждый платёж. Поэтому отказ авторизации — не всегда «мерчант прислал не тот секрет», это может быть и недоступность соседнего сервиса. Где физически живут эти ручки — пока открытый вопрос.
Ограничение по адресам мерчанта
Помимо учётных данных мерчанта можно ограничить по адресам, с которых он к нам ходит. Проверка работает на двух не связанных между собой уровнях.
На уровне платформы адреса собраны в именованные группы, группа назначается мерчанту, и у мерчанта есть отдельный признак, включающий фильтр. Адрес, с которого пришёл запрос, ядро узнаёт от шлюза: снаружи ядро недоступно, а подпись шлюза уже доказывает, что запрос прошёл через него, поэтому переданному адресу можно верить. Учтите, что в ядре эта проверка сейчас только пишет предупреждение в лог и не отклоняет запрос — фактическая блокировка на этом уровне выключена.
Второй уровень — правила Cloudflare перед публичными доменами со своим списком адресов. Поскольку проверка в ядре сейчас только наблюдает, фактически запрос может быть отклонён по адресу только здесь. Набор правил зависит от домена, на который пришёл мерчант, поэтому разбор блокировки начинается с выяснения домена.
Подписи между нашими сервисами
Внутренние вызовы не используют пароли — только подписи ключами AWS KMS, и схем здесь две. Первая: отправитель просит KMS сгенерировать одноразовый ключ, получает его в открытом и в зашифрованном виде, подписывает им тело запроса и отправляет вместе с зашифрованным ключом. Получатель расшифровывает ключ тем же ключом KMS и пересчитывает подпись. Так ходят шлюзы в ядро, ядро в сервис авторизации и служебные интеграции между собой.
Вторая: отправитель просит KMS посчитать код подлинности сообщения напрямую, а получатель просит KMS же его проверить. Так устроено обращение к сервису токенизации.
В обоих случаях в теле обязателен штамп времени: запрос с устаревшим штампом отклоняется, окно — около десяти секунд. Так перехваченный запрос не переиграть.
Отдельные ключи для служебных интеграций
Общего внутреннего ключа нет. У каждого потребителя ядра свой: один у шлюзов, свои — у сервиса агрегации, сервиса чеков, телеграм-бота транзакций, лямбды и оповещений о балансах. Ядро проверяет подпись именно ключом того источника, от которого пришёл запрос, поэтому право вызывать конкретную группу операций выдаётся выдачей ключа, а не настройкой ролей.
Из схемы выбиваются два случая: пакетная токенизация карт проверяется отдельным ключом во внешнем сервисе партнёра, а внешний антифрод приходит с общим секретом. Это частные договорённости, а не образец для новых интеграций.
Что открыто без проверки
Часть адресов намеренно доступна без учётных данных: страницы платёжной формы, перехода на 3DS и возврата с него, служебные проверки живости. Иначе и быть не может — их открывает браузер плательщика, у которого нет наших ключей.
Это не дыра: такие страницы работают по неугадываемому идентификатору транзакции и ничего не решают о деньгах — они показывают заранее подготовленный контент и фиксируют факт перехода, а решения принимает ядро по ответу банка. Для нового публичного адреса правило то же: показать страницу и отметить событие можно, менять баланс или отдавать чувствительные данные — нет.
Подпись исходящего колбека
Уведомление мерчанту подписывается тем же способом, которым мерчант подписывает запросы к нам: значения полей сортируются по именам, склеиваются через вертикальную черту, от строки считается HMAC секретом этого мерчанта. Считает подпись сервис авторизации — ядро секрета мерчанта не знает. Готовое тело ставится в очередь, а доставкой занимается сервис колбеков; детали — в Колбеке мерчанту.
Где обычно ошибаются
- Подпись считается по значениям, а не по парам. Сортировка идёт по именам полей, а в строку попадают только значения, поэтому добавление нового поля в тело меняет подпись, даже если мерчант это поле игнорирует.
- Тело для подписи не равно телу запроса. Пустые значения приводятся к строке, вложенное разворачивается не так, как видит глаз: сверяют не JSON, а склеенную строку.
- Внутренняя подпись считается по сериализованному телу. Переупорядочивание или переформатирование между подписанием и отправкой ломает проверку.
- Расхождение часов и перепутанный ключ выглядят одинаково. Устаревший штамп времени даёт тот же отказ, что и чужой ключ, а проверка не рассказывает, чего именно ждала: сверяйте и время, и ключ, настроенный для этого источника.