Pull to refresh

Comments 32

А вы часто встречаете реализацию защиты от MITM-атаки при обработке коллбека?
во всех вменяемых платёжных сервисах (и большинстве невменяемых, поскольку они подражают вменяемым)
Подписать контент коллбека — более чем уместно. Передавать в ответе свою подпись этого контента, на мой взгляд, несколько избыточно. Если злоумышленник смог скомпрометировать подпись отправителя, модифицировал контент и заставил получателя считать, что коллбек достоверный, то он вполне сможет при взаимодействии с отправителем эмулировать, например, отвал по таймауту — это даст получателю время на обработку подмененных данных. Я не встречал сервисов, где факт отвала по таймауту при получении коллбека был бы достаточным основанием, например, для блокировки транзакции, о которой передается уведомление.

Автор не раскрыл полный кейс. Но не обязательно это защита от атаки злоумышленника. Я бы это воспиринимал как своего рода роспись в получении. Наученные горьким опытом провайдеры когда им выставляли претензию что не было подтверждения там чего-то или неверная скажем сумма оплаты могут сказать как же не было если было и вы своим же хэшем это подписали.

В принципе, интересная теория. ;)

Сначала убедимся, что разработчик смог правильно распарсить наш особенный формат данных.
Потом, мы понимаем, что он умеет сортировать поля.
Потом, он должен сдать 1й разряд по байтоукладчеству и посчитать хеш.
Потом, он должен правильно кроме кода еще и сформировать ответ.

Где-то забыл сохранить данные? Ну это мы твоей компании потом припомним!

Когда пишу интеграции с платежными шлюзами я просто логирую в БД весь трафик в текстовом виде.
И входящий и исходящий.
Спится спокойнее…

Генерировать подпись кэлбэка все се равно приходится так как входные данные от например платежных систем приходят с подписью обычно чтобы клиент мог убедиться что это действительно платежная система прислала данные. Так что весь стек технологий уже используется при работе с кэлбеком.то что хэш нужно вернуть конечно более редкий кейс.


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

Платежные шлюзы все еще отдают коллбеки на HTTP?
Если нет, то кажется, это уже достаточная защита от MITM.

Так вот в этом и вопрос: Зачем еще усложнять жизнь интегратору?

У меня по поводу api в подарок был клубок вопросов на которые которые я так и не нашел ответ. Пустые значения. Можем не передавать пустые свойства, передавать null и передавать для сороковых пустую строку. Я склоняюсь к вам снова не передавать. Но фронтенду часто проще если это будет пустая строка. И в запросах и в ответах. А как быть если клиент хочет удалить свойство? Значит null?
Хорошо. Пусть я набрался терпения и начал требовать чтобы или было значение непустое или не было свойства вообще. Ну тут дело доходит до массивов и весь скоуп вопросов нужно решать заново. Так как у массива есть еще пустой масив и все вопросы заново. Как правильно пустой массив или null, или полностью удалить свойство?
Дальше связанные объекты. Скажем блог у поста есть автор authorId. Вы возвращаетесь объект со связанным полем author в котором данные из связанной таблицы и есть поле id равное authorId. Что делать с authorId. Его возвращать в ответе или достаточно только author? Вроде бы достаточно. Но как быть если нужно обновить новым значением это поле? Присылать в authorId или в author.id?

При полном удалении свойства или вводя в ответе Null нужно менять версию API, а если оставаться на той-же API, То возвращать пустую строку

Для удаления свойства или Delete resource/property или полноценный json patch. Правда фронт его не любит почему-то.

Это совершенно 3 разные ситуации:


  • поле имеет допустимое для своего типа значение;
  • поле имеет значение null;
  • поля вообще нет в запросе или ответе.

Не надо пытаться экономить на спичках и интерпретировать одно через другое.
Пустая строка — это всё ещё строка, а не null и тем более не отсутствие поля. Тоже самое и с пустым массивом.
Null не тоже самое, что отсутствие поля. Например, в запросе на обновление сущности, передав в поле expires_time значение null мы сообщим что ресурс больше не имеет срока жизни. А если вообще не передать поле — значит его не надо менять, а оставить текущее значение.

С Вашим комментом а все 100 согласен. Но тут со всех сторон начинают лезть подробности. Например чем семантические пустой массив отличается от значения null. Например у статьи нет комментома это null скорее всего если исходить из логики Вашего комментария. Но что тогда пустой массив когда его применять? Или не применять никогда?
Или такой вопрос. Если идет создание сущности с фронтенду что обычно делают запросом POST. На бэк нужно прислать явно значения null или все же null возникнет при сохранении в базу данных? Или null не нужно хранить в базе данных если она документоориентированная?

Из моего опыта null нужен обычно только в тех случаях, когда он уместен в конкретной ситуации и не может быть иначе выражен конкретным типом данных. Например у типа datetime нет ни какого подходящего значения, которое бы можно было во всех ситуациях использовать в качестве "нет значения". Тут только null подходит. Для целых чисел иногда можно использовать 0, если бизнес-логика не допускает использование нуля в качестве нормального значения. С пустыми строками аналогично.
В случае коментов пустой массив это нормальная абстракция для "ещё нет коментов". Хотя бы потому что коменты надо куда-то добавлять. В null их не добавишь, а потому это не очень подходящее значение. Только если не использовать его как признак того, что коменты запрещены и их нельзя добавлять вообще.
Я лично стараюсь избегать использовать null без необходимости, т.к. он добавляет проверок в программе.
Что и как надо посылать при создании сущности — зависит от того нужено этоили нет. Если null допустимое значение для поля, то почему бы его не послать в POST запросе. Но можно так же сделать null как дефолтное значение поля при создании сущности. И тогда это поле можно вообще не посылать.

Я время от времени размышляю над этими вопросами так как хочется сделать типа "руководства", чтобы в пределах проекта была один подход и как я понимаю что за пределами TODOAPP сталкиваешься со сложными вопросами. Вернее их сложность заключается в том что нужно соблюдать согласованность на бэке и фронте. И при этом не превратить работу в бег с препятствиями Например когда любая отправка данных с фронтенда будет проходить через какой то сложный маппинг. По идее пустой строки быть не должно. Но если у фронтенда есть пустой input то он часто сериализируется в пустую строку это проще всего. Иначе на фронте нужно давать полный анализ и преобразование всех поле ввода.
Про пустой массив я склоняюсь к тому что значение null и пустой массив инвариантны. И проще использовать пустой массив, так как если нужно добавлять значение то проще сделать это без проверко к пустому массиву. Только в этом случае неободимо чтобы на уровне системы всегда соблюдалось что поле типа массив не может иметь значение null. Чтобы уйти от проверок в стиле "если пусто создать массив и добавить значение, иначе добавить значение" или "если пусто или значение типа массив и количство элементов массива равно нулю".

Тут обязательно надо разделять "схему" входных параметров, и "схему" того что возвращает API (о чём говорится в статье). Входные параметры могут иметь более свободную форму. Например можно сделать так, что отсутствие в запросе поля или если оно равно null, будет преобразованно десериализатором-валидатором в "значение по умолчанию" (пустой массив).
А вот в схеме возвращаемого результата лучше избегать вольностей. Если поле должно быть всегда массивом — всегда возвращать массив, даже если по каким то причинам этого массива нет в хранилище данных.

Я согласен. Так например делают в graphql. Но в традициях restfullapi в котором я не сильно верю как раз то что объект на вход и выход подается один и тот же. Более того многие считают что в отахвете нужно вернуть только ко измененные на бэк поля например сгенерированный идентификатор или время изменения объекта. На этом собственно есть много готовых тулзов. Тот же backbone хотя он уже не актуален.

Про restfull вы несколько заблуждаетесь. Нет такого "правила" что возвращать надо то же что получил. Можно вообще получать в json, а отдавать в xml с другим набором полей. И в rest передают в обе стороны не сущности, а их некую репрезентацию. Какая при этом будет репрезентация — дело ваше, лишь бы её было достаточно для решения задач.
Но естественно часто просто удобнее если набор входных параметров является подмножеством результирующих свойств созданной сущности.
Возвращать только то что изменилось — это не удобно, нарушает схему ответа (разработчики клиентов вам точно спасибо не скажут), и накладно — если сущность была изменена другим клиентом, то придётся запрашивать её заново полностью, т.к. ваш запрос на изменение вернёт только то что он изменил.

Я же говорил что в restfull я не верю. Так как его не существует в отличие от rest. Потому что кроме tutorial todoapp по rest null нет никакой информации что и порождает вот те самые вопросы которые я озвучил. Даже неясно кто его первый придумал. Вроде бы это так было в ruby-on-rails. Но если посмотреть на реализации то наверное чаще всего будет именно такая реализация. Когда объект в том виде как он представлен на фронтенде и какой он был получен с бэка после изменений сохраняется в той же структуре как и пришел с бэка. Хотя тут как всегда возникают подробности если в объекте есть связанные сущности

Насчёт RESTfull я с вами согласен — это просто абстрактный термин, которым принято обозначать архитектуру приложений, якобы сделанную по принципам REST. Особой конкретики в этом нет, т.к. следуя принципам REST можно сделать разные архитектуры. По моему опыту 95% разработчиков, к сожалению, вообще не понимают REST, но при этом пытаются делать приложения и писать статьи про всё это. В результате интернет заполнен кодом и текстами не соответствующими принципам REST. И это только ещё больше запутывает, особенно новичков.
Но тема null-ов и отсутствующих полей не имеет отношения к REST как таковому. Просто есть 3 разных состояний поля в сущности: поле есть и имеет значение, оно null или его вообще нет. И эти состояния можно использовать в разных целях, в зависимости от контекста. Какие из них использовать и где — обычно понять не сложно, просто не забывая, что это действительно 3 разных состояния, которые лучше не смешивать. А потом задаться вопросами: какие из этих состояний допустимы, что каждое их них означает в конкретном случае. И если получается, что два разных состояния означают одно и то-же, то это повод хорошо подумать, действительно ли именно это вы хотели.

Ну вот для меня как раз и не просто понять семантику чем пустой массив отличается о значение null и полного отсутсвия значения (отсутствия поля с таким именем) Но возможно это только моя личная проблема.

Пустой массив — комментариев пока нет, но можете добавить свой
Null — в теории могли бы быть, но автор запретил
нет значения — комментарии к этому материалу вообще не предусмотрены.


В любом случае, семантика определяется автором API

Самая частая проблема — Id ресурса генерирует сервер в ответ на запрос Post, если говорить о http apu. External ID — костыль для обхода этого факта. Есть uuid, есть составные ключи, например clientid+externalid, нет, обязательно надо весь мир заставлять использовать свои id. Причём, блин, целочисленные unsigned int, а в некоторых (большинстве?) языках unsigned типов нет скалярных. Делайте свой id строковым, если уж решили его экспозить на весь мир, хотя бы на этапе сериализации — ваши клиенты будут вам благодарны. Да и вы себе когда-нибудь спасибо скажете, возможно, решив перейти на uuid например

Был такой случай с системой оплат. Нужно было генерировать уникальный номер заказа. Я его сделал числами, но естественно что-то было уже использовано мною же в процессе тестировнаия. И и потом пошли заказы с ошибкми дубликатов. Пришлось на ходу переделывать. Но вот GUID тоже не всегда хоршо. В том случае идентиыикатор заказа мало того что должен был быть униальным он еще должен быть человекопонятным так как по нему шли обращения в службу поддержки

В любом (почти) случае текстовый ид лучше числового в API и его генерациию лучше делать на клиенте

Самая частая проблема — Id ресурса генерирует сервер в ответ на запрос Post, если говорить о http apu. External ID — костыль для обхода этого факта.

Не совсем согласен с такой постановкой вопроса — internal_id и external_id решают разные задачи. Internal_id должен больше удовлетворять потребностям самой системы, где создается, external_id — потребителям API. У нас была интеграция, где сервис вынуждал нас генерировать internal_id на нашей стороне, предоставив в документации алгоритм генерации — это неудобно + это распыляет бизнес логику системы на внешних потребителей. Если представить, что internal_id используется, например, для шардинга данных в базе, то мы не сможем сменить логику генерации\шардинга не затронув потребителей API, которые поддержали генерацию id на своей стороне. Текстовый external_id — лучший выбор для потребителя, захочет — положит туда uuid в строковом представлении, а захочет — человекочитаемую альтернативу.

Я о ситуации, когда вы отправляете POST /orders, возможно с вашим id 45477 в теле, а в ответе получаете Location: /orders/123 или иным способом internal_id сервиса как "первичный ключ" ресурса. В терминах HTTP/"Rest" именно 123, internal_id основной идентификатор, а ваш external_id — так, для справки, может для контроля уникальности. Это заставляет клиента поддерживать постоянную связь его id с internal_id сервиса

По-моему первый пункт, это про тех, кто документацию не прочитал. И никогда тут проблем не возникало. Так же за уши можно притянуть и idempotency key.

Бывает возня так же с запросами, результаты которых появляются позднее текущего соединения.

Пункт 2… ну, можно и согласиться.

Пункт 3. Про защиту https сертификатом, это не совсем понял, кто и что защищает, когда методы открыты наружу. Так же не понял: по тексту вы то на стороне клиента, то на стороне сервера. От себя могу добавить, что встречал реализации, когда сервер ничего не отвечает, если не проходит формальная проверка (mime не тот, авторизация не прошла и т.п.). Просто игнорирует. Основание — не выполнение требований документации. А если авторизация пройдена, то сервер уже может пояснить что именно ему не понравилось, иначе — лесом.

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

Проблемы возникают тогда, когда у нас нет возможности однозначно идентифицировать созданный нами объект во внешней системе, но есть возможность создать такой же. В таких кейсах, если external_id обеспечивает уникальность только определенное время, мы вынуждены сохранять на своей стороне timestamp первой попытки создания (перед отправкой запроса), а в случае отвала, при последующих попытках проверять находимся ли мы в интервале, когда external_id еще не превратился в тыкву. При должном невезении и проблемах в сети с такой интеграцией легко отхватить множество объектов, которые нужно обрабатывать руками через тех-поддержку.
Про защиту https сертификатом, это не совсем понял, кто и что защищает, когда методы открыты наружу.

Отправитель колбека вынуждает получателя сформировать свою подпись полученной модели и вернуть её отправителю. Если это форма защиты — то мне не понятен механизм атаки. Кажется, сейчас уже подавляющее большинство платежных систем интегрируются исключительно по https, на ваш взгляд, этого не достаточно, чтобы идентифицировать получателя колбека?
сервер ничего не отвечает, если не проходит формальная проверка (mime не тот, авторизация не прошла и т.п.). Просто игнорирует

А можете чуть подробнее? Как именно «игнорирует»? 404, 408?
по-моему любые платежные данные запрещено сохранять в транзитной системе в любом виде, аудит не пройдет

Да, интересный вопрос, как эта неназванная платежная система проходила аудит.
А если такая необходимость сохранять подобные данные все таки есть, то система не должна хранить ключ для дешифровки данных

В этом ведь и суть: платежная система отдавала интегратору хэш номера карты как её идентификатор для дальнейшего использования, ну и маскированный номер, до кучи.

Кажется, сейчас уже подавляющее большинство платежных систем интегрируются исключительно по https, на ваш взгляд, этого не достаточно, чтобы идентифицировать получателя колбека?

Я не так вас понял. Да, вполне достаточно.
Еще в копилку: за рубежом встречал просто фильтр по IP, даже без авторизаций.

А можете чуть подробнее? Как именно «игнорирует»? 404, 408?

Да, 404. Правда это раздражает разработчиков, которые привыкли работать с API у которого ноги раздвинуты, зато усложняет исследование API злоумышленниками, если такой доступ возможен.

На стороне сервера проверка примерно такая: если совпало наименование метода, присутствуют обязательные параметры и заголовки в запросе, то запрос передается в обработчик, если что-то не совпало, то сервер говорит 404, до скриптов дело даже не доходит, формальные проверки на уровне веб-сервера.

Он хэш номера карты это кажется обычная практика чтобы привязать карту к клиенту и таким образом дать возможность оплачивать не вводя номер карты что должно защитить клиента. Обычно процессинг дает разрешение на использование такого api после аудита информационной системы. Хотя наверное есть такие которые дают всем подряд. С учетом того что все равно транзакция требует подтверждения клиентом это не выглядит слишком не секьюрно.

Точно есть (ну или были года три назад) такие, которые всем давали. Но вот про хэши они ничего не говорили, давали токен для списаний. Без подтверждений, кстати.

Для подписок ака реккурентные платежи это обычное дело — один раз подтверждаешь и всё.

Sign up to leave a comment.

Articles