Инструменты разработчика · Генератор UUID
Ключи идемпотентности: использование сгенерированного клиентом UUID для обеспечения безопасности повторных попыток
· Почему это важно
uuid криптография API-интерфейс браузера
Тайм-аут запроса платежа оставляет вас неуверенным, прошел ли он. Ключи идемпотентности позволяют безопасно повторить попытку, а UUID, сгенерированный CSPRNG, является естественным ключом. В этом посте объясняется весь шаблон.
Тайм-аут, из-за которого с клиента могла дважды взиматься плата — для исправления существуют ключи идемпотентности режима отказа.
Таймаут во время запроса платежа создает настоящую неопределенность для клиента и системы. Ваш клиент HTTP перестал ждать ответа, но платежный сервер мог обработать транзакцию до закрытия соединения или истечения времени ожидания. Если вы повторите тот же запрос, вы можете списать с клиента плату дважды. Если вы не повторите попытку, платеж никогда не будет завершен. Платежная система терпит неудачу в выборе золотой середины: деньги клиента могут исчезнуть, могут прийти завтра, могут застрять в очереди обработки или вообще не покинуть счет. Такая двусмысленность неприемлема для финансовых систем.
Как работают ключи идемпотентности — сервер сохраняет первый ответ под ключом и воспроизводит его для повторов.
Ключи идемпотентности элегантно решают эту проблему, делая повторные попытки безопасными и детерминированными. Клиент генерирует уникальный ключ для каждого намерения — платежа, перевода, списания — и включает его в каждый запрос. Сервер обрабатывает платеж, кэширует ответ под этим ключом и сохраняет ключ и результат. Если тот же ключ снова поступает в течение периода хранения, сервер воспроизводит кэшированный ответ, не обрабатывая платеж повторно. Клиент может с уверенностью повторить попытку, зная, что один и тот же ключ всегда будет давать один и тот же результат, независимо от того, сколько раз он будет отправлен. Этот шаблон устраняет неоднозначность и делает логику повторных попыток безопасной.
Генерировать перед первой попыткой — почему ключ должен существовать до того, как запрос будет отправлен, и повторно использоваться дословно при повторной попытке.
Этот шаблон старше современных спецификаций HTTP, но приобрел известность в платежах после массовых финансовых потерь и жалоб клиентов из-за дублирования платежей. Каждый платеж API и многие API веб-сервисов теперь поддерживают ключи идемпотентности. UUID, сгенерированный CSPRNG, является естественным выбором для ключа, поскольку он не угадывается, уникален без какой-либо координации между клиентами и не требует выделения на стороне сервера или центральных полномочий. Клиент генерирует его перед первой попыткой, повторно использует его дословно при каждой повторной попытке и каждый раз получает один и тот же ответ. Для координации генерации ключей не требуется никакого состояния на стороне сервера.
Почему случайный UUID, а не счетчик или хеш полезной нагрузки — уникальность без координации и отсутствие случайного повторного использования в разных целях
Ключ должен существовать до того, как запрос покинет клиент, поскольку генерировать его при повторной попытке слишком поздно для обеспечения идемпотентности. Если первый запрос был успешным и с клиента взималась плата, создание нового ключа при повторной попытке замаскирует проблему и взимает плату снова. Клиент должен зафиксировать ключ перед первой попыткой, сохранить его в памяти или постоянном хранилище и повторно использовать тот же ключ, если потребуется тайм-аут или повторная попытка. Для ручного тестирования API генератор ToolAcre создает ключи, которые вы можете вставить в Curl или клиент REST, скопировать и повторно использовать в нескольких запросах для проверки поведения идемпотентности.
Область действия и срок действия — ключи для каждой операции, для каждой учетной записи и как долго сервер должен их помнить.
Почему UUID, а не хэш или последовательный счетчик ключей идемпотентности? Хэш полезных данных запроса кажется интуитивно понятным — идентичные полезные данные получают одинаковые хэши и, следовательно, идентичные ключи. Но хэши слабы для этого варианта использования, поскольку два почти идентичных запроса с разными суммами, разными получателями или разными параметрами создают совершенно разные хэши и, таким образом, генерируют отдельные расходы, что правильно, но не обеспечивает всей необходимой защиты. Последовательный счетчик требует координации и распределенного состояния: если два клиента генерируют ключи на основе счетчиков в вашей инфраструктуре, их счетчики могут столкнуться. UUID не требует каких-либо центральных полномочий, его невозможно угадать, и его случайное столкновение по всему Интернету за все время крайне маловероятно.
Рабочий пример — последовательность повторов с тем же ключом, показывающая, что каждый раз отправляет клиент и что возвращает сервер.
Реализация на стороне сервера хранит ответы по ключам и возвращает кэшированные ответы при повторах. Сложность заключается в решении практических оперативных вопросов: срок хранения, как долго запоминать ключ, размер кэша, сколько ключей запоминать, блокировка, как не допустить, чтобы два одновременных запроса с одним и тем же ключом дважды обрабатывали платеж, и очистка, когда забыть ключ. Это вопросы хранения и надежности, выходящие за рамки генератора UUID. Задача клиента — сгенерировать хороший ключ и повторно использовать его при повторных попытках; задача сервера — правильно и надежно реализовать кеш.
Что сюда не входит — хранение и блокировка на стороне сервера, необходимые для реализации шаблона, который является отдельной разработкой.
Проработанный пример показывает типичную последовательность действий на практике. Мобильное приложение должно перевести деньги другу с помощью API, поддерживающего идемпотентность. Перед отправкой запроса приложение генерирует UUID, используя свою локальную криптографическую библиотеку, или извлекает его из генератора ToolAcre для целей тестирования: 3fa85f64-5717-4562-b3fc-2c963f66afa6. Приложение отправляет запрос POST на /transfers с телом JSON и заголовком HTTP Idempotency-Key: 3fa85f64-5717-4562-b3fc-2c963f66afa6. Сервер обрабатывает передачу, сохраняет 3fa85f64-5717-4562-b3fc-2c963f66afa6 → {status: "success", TransferId: "xfer-12345"} в своем кеше и возвращает ответ 200 с результатом.
Вывод: одно намерение, один ключ — генератор ToolAcre предоставляет вам UUID с поддержкой CSPRNG, который можно использовать в качестве ключа при тестировании интеграции вручную.
Время ожидания сети истекло, и клиент не видит ответа с первой попытки. Приложение повторяет тот же запрос с тем же ключом идемпотентности, не создавая новый UUID. Сервер распознает ключ в своем кеше, находит закешированный ответ и немедленно возвращает {status: "success", TransferId: "xfer-12345"}, не обрабатывая новый перевод и не взимая повторной оплаты с клиента. Операция идемпотентна: повторная попытка каждый раз приводит к одному и тому же наблюдаемому результату. Для работающего теста генератор ToolAcre может предоставить ключ; сгенерируйте UUID, включите его в заголовок, просмотрите ответ и повторите отправку с тем же ключом, чтобы убедиться, что сервер правильно реализует кеширование.