REST API Best Practices
Привет, Хабр! Представляю вашему вниманию перевод статьи «REST API Best Practices» автора Krishna Srinivasan.
REST становится общим подходом для представления сервисов окружающему миру. Причина его популярности заключается в его простоте, легкости использования, доступе через HTTP и другие. Существует неправильное представление о том, что все данные, доступные через сеть, считаются REST, но это не так. В этой статье я собираюсь объяснить вам некоторые best practices, которые вы должны всегда помнить при реализации собственного REST приложения. Я бы хотел услышать ваш опыт в REST приложениях, поэтому если вы знаете best practies, которые не упомянуты в этой статье, пожалуйста, поделитесь с нами в комментариях.
Disclamer: все best practies основаны на моем личном опыте. Если вы имеете другое мнение, не стесняйтесь отправлять его мне на email, и мы обсудим его.
Здесь представлен список best practices, которые будут обсуждаться в этой статье:
1. Конечные точки в URL – имя существительное, не глагол
2. Множественное число
3. Документация
4. Версия вашего приложения
5. Пагинация
6. Использование SSL
7. HTTP методы
8. Эффективное использование кодов ответов HTTP
1. Конечные точки в URL – имя существительное, не глагол
Одна из самых распространённых ошибок, которую делают разработчики REST приложений, — использование глаголов при именовании конечных точек. Однако, это не лучшая практика. Вы должны всегда использовать существительные вместо глаголов.
Мы имеем заказ на разработку REST веб сервисов, которые предоставляют информацию об Индийских фермерах. Сервис также должен реализовывать функционал, предоставляющий такую информацию как доход фермера, названия культур, адреса ферм и другую информацию, относящуюся к каждому фермеру. Каждый фермер имеет уникальный id.
Таким же образом должны быть реализованы сервисы, предоставляющие информацию о культурах и какой фермер владеет ими.
Имеем единственную конечную точку, которая отвечает за все действия. В примере ниже представлена только одна конечная точка /farmers для всех операций таких как добавление, обновление, удаление. Базовые реализации имеют различные HTTP методы, которые правильно маршрутизируются для разных операций.
Постарайтесь избегать использования глаголов. Рекомендуется представлять операции внутри таких форматах как JSON, XML, RAML или использовать HTTP методы. Не используйте представленные ниже обозначения:
• /getFarmers
• /updateFarmers
• /deleteFarmers
• /getCrops
• /updateCrops
• /deleteCrops
2. Множественное число
Используйте множественное число для названия своих REST сервисов. Это еще одна горячая тема для обсуждений среди REST дизайнеров – выбор между единственными или множественными формами существительных для обозначения сервисов.
Примечание:
Хотя я упоминаю, что использование множественного числа является best practice, по какой-то причине, если вы придерживаетесь единственного числа, то придерживайтесь этого во всех своих сервисах. Не смешивайте использование множественного и единственного чисел. Поэтому я и не говорю здесь про bad practice, а просто говорю, что это не рекомендуется. Пожалуйста, решайте сами, что лучше подходит для вашего приложения.
3. Документация
Документирование программного обеспечения является общей практикой для всех разработчиков. Этой практики стоит придерживаться и при реализации REST приложений. Если писать полезную документацию, то она поможет другим разработчикам понять ваш код.
Наиболее распространенным способом документирования REST приложений – это документация с перечисленными в ней конечными точками, и описывающая список операций для каждой из них. Есть множество инструментов, которые позволяют сделать это автоматически.
Ниже представлены приложения, которые помогают документировать REST сервисы:
Пожалуйста, поделитесь своим опытом документирования ваших приложений в комментариях.
4. Версия вашего приложения
Любое программное обеспечение развивается с течением времени. Это может потребовать различных версий для всех существенных изменений в приложении. Когда дело доходит до версии REST приложения, то оно становится одной из самых обсуждаемых тем среди сообщества разработчиков REST.
Существует два общих способа для управления версиями REST приложений:
1. URI версии.
2. Мультимедиа версии.
Ниже приведены основные недостатки способа создания версий с использованием URI:
- Разбиваются существующие URIs, все клиенты должны обновить до нового URI.
- Увеличивается количество URI версий для управления, что в свою очередь увеличивает размер HTTP кэша для хранения нескольких версий URI. Добавление большого числа дубликатов URI может повлиять на количество обращений к кэшу и тем самым может снизить производительность вашего приложения.
- Он крайне негибкий, мы не можем просто изменить ресурс или небольшой их набор.
Пример информации в заголовке:
GET /account/5555 HTTP/1.1
Accept: application/vnd.farmers.v1+json
HTTP/1.1 200 OK
Content-Type: application/vnd.farmers.v1+json
В мультимедийном подходе управления версиями клиент имеет возможность выбрать, какую версию запрашивать с сервера. Этот способ выглядит предпочтительней, чем подход с URI, но сложность возникает при кэшировании запросов с различными версиями, которые передаются через заголовок. Говоря простыми словами, когда клиент кэширует на основе URI, это просто, но, кэширование с ключом в качестве мультимедийного типа добавляет сложности.
5. Пагинация
Отправка большого объема данных через HTTP не очень хорошая идея. Безусловно, возникнут проблемы с производительностью, поскольку сериализация больших объектов JSON станет дорогостоящей. Best practice является разбиение результатов на части, а не отправка всех записей сразу. Предоставьте возможность разбивать результаты на странице с помощью предыдущих или следующих ссылок.
Если вы используете пагинацию в вашем приложении, одним из хороших способов указать ссылку на пагинацию является использование опции Link HTTP заголовка.
Следующая ссылка будет полезной для вас.
6. Использование SSL
SSL должен быть! Вы всегда должны применять SSL для своего REST приложения. Доступ к вашему приложения будет осуществляется из любой точки мира, и нет никакой гарантии, что к нему будет обеспечен безопасный доступ. С ростом числа инцидентов с киберпреступностью мы обязательно должны обеспечить безопасность своему приложению.
Стандартные протоколы проверки аутентификации облегчают работу по защите вашего приложения. Не используйте базовый механизм аутентификации. Используйте Oauth1.Oa или Oaurh2 для лучшей безопасности ваших сервисов. Я бы рекомендовал Oauth2 лично из-за его новейших функций.
7. HTTP методы
Проектирование операций на HTTP методы становится легче, когда вы знаете характеристики всех методов HTTP. В одном из предыдущих разделов этой статьи я настаивал на использовании HTTP методов для операций вместо написания различных наименований сервисов для каждой операции. В этом разделе в основном рассматривается поведение каждого HTTP метода.
Ниже представлены две характеристики, которые должны быть определены перед использованием HTTP метода:
- Безопасность: HTTP метод считается безопасным, когда вызов этого метода не изменяет состояние данных. Например, когда вы извлекаете данные с помощью метода GET, это безопасно, потому что этот метод не обновляет данные на стороне сервера.
- Идемпотентность: когда вы получаете один и тот же ответ, сколько раз вы вызываете один и тот же ресурс, он известен как идемпотентный. Например, когда вы пытаетесь обновить одни и те же данные на сервере, ответ будет таким же для каждого запроса, сделанного с одинаковыми данными.
REST HTTP методы
Ниже приведен краткий обзор каждого метода и рекомендации по их использованию:
- GET: этот метод является безопасным и идемпотентным. Обычно используется для извлечения информации и не имеет побочных эффектов.
- POST: этот метод не является ни безопасным, ни идемпотентным. Этот метод наиболее широко используется для создания ресурсов.
- PUT: этот метод является идемпотентным. Вот почему лучше использовать этот метод вместо POST для обновления ресурсов. Избегайте использования POST для обновления ресурсов.
- DELETE: как следует из названия, этот метод используется для удаления ресурсов. Но этот метод не является идемпотентным для всех запросов.
- OPTIONS: этот метод не используется для каких-либо манипуляций с ресурсами. Но он полезен, когда клиент не знает других методов, поддерживаемых для ресурса, и используя этот метод, клиент может получить различное представление ресурса.
- HEAD: этот метод используется для запроса ресурса c сервера. Он очень похож на метод GET, но HEAD должен отправлять запрос и получать ответ только в заголовке. Согласно спецификации HTTP, этот метод не должен использовать тело для запроса и ответа.
8. Эффективное использование кодов ответов HTTP
HTTP определяет различные коды ответов для указания клиенту различной информации об операциях. Ваше REST приложение могло бы эффективно использовать все доступные HTTP-коды, чтобы помочь клиенту правильно настроить ответ. Далее представлен список кодов ответов HTTP:
- 200 OK — это ответ на успешные GET, PUT, PATCH или DELETE. Этот код также используется для POST, который не приводит к созданию.
- 201 Created — этот код состояния является ответом на POST, который приводит к созданию.
- 204 Нет содержимого. Это ответ на успешный запрос, который не будет возвращать тело (например, запрос DELETE)
- 304 Not Modified — используйте этот код состояния, когда заголовки HTTP-кеширования находятся в работе
- 400 Bad Request — этот код состояния указывает, что запрос искажен, например, если тело не может быть проанализировано
- 401 Unauthorized — Если не указаны или недействительны данные аутентификации. Также полезно активировать всплывающее окно auth, если приложение используется из браузера
- 403 Forbidden — когда аутентификация прошла успешно, но аутентифицированный пользователь не имеет доступа к ресурсу
- 404 Not found — если запрашивается несуществующий ресурс
- 405 Method Not Allowed — когда запрашивается HTTP-метод, который не разрешен для аутентифицированного пользователя
- 410 Gone — этот код состояния указывает, что ресурс в этой конечной точке больше не доступен. Полезно в качестве защитного ответа для старых версий API
- 415 Unsupported Media Type. Если в качестве части запроса был указан неправильный тип содержимого
- 422 Unprocessable Entity — используется для проверки ошибок
- 429 Too Many Requests — когда запрос отклоняется из-за ограничения скорости
Резюме
Надеюсь, эта статья будет полезна для понимания того, как создать свой REST API. Здесь представлены best practices, собранные на основе моего опыта и обсуждения с друзьями, которые работали над приложениями веб-служб REST.
Если вы много работали над дизайном REST API, и, если вы чувствуете, что эта статья не имеет для вас никакого смысла, я рад услышать ваши отзывы. Я хотел бы продолжить обновление этого обсуждения с помощью более проверенных методов разработки лучшего API для вашего приложения.
Кто нибудь использовал апи сбербанка на систему быстрых платежей?
Authorization: Bearer f4084e24-3d2c-468c-b464-20e0b90d70aa
Content-Type: application/x-www-form-urlencoded
RqUID: 25Ec70328e2CE4DF39e828E1dF75EFa0
Accept: application/json
<"rq_uid": "24f75b719b460385cdcd97f3688a81",
"rq_tm": "2022-03-11T19:59:36Z",
"idQR":"24123536",
"startPeriod":"2022-01-11T19:59:36Z",
"endPeriod":"2023-03-11T19:59:36Z",
"registryType":"REGISTRY">
400
Авторизация и аутентификация для всех и каждого, часть 2
В первой части мы рассмотрели основные термины, а также подробно остановились на теме делегированного доступа. Продолжаем разбираться.

Проблема входа в систему
После того как OAuth 2.0 обеспечил возможность доступа к сторонним API, создатели приложений также захотели, чтобы их пользователи могли логиниться при помощи других аккаунтов. Возьмем пример. Скажем, приложение HireMe123 хочет, чтобы пользователь MyCalApp мог логиниться в HireMe123, используя аккаунт MyCalApp — несмотря на то, что в самом HireMe123 у него нет аккаунта.
Но, как уже говорилось, OAuth 2.0 — это про делегированный доступ. Это НЕ протокол аутентификации. Тем не менее, это не остановило людей в их попытках использовать OAuth 2.0 именно с целью аутентификации, и это породило проблемы.
Проблемы, связанные с использованием токенов доступа для аутентификации
Если HireMe123 полагает, что успешный вызов API MyCalApp при помощи токена доступа означает, что пользователь может считаться аутентифицированным в HireMe123, мы сталкиваемся с проблемами. Дело в том, что у нас нет возможности проверить, был ли токен доступа выпущен для данного человека.
- Кто-нибудь мог украсть токен доступа другого пользователя.
- Токен доступа мог быть получен от другого клиента (не HireMe123) и вставлен в HireMe123.
Эта проблема получила название confused deputy. HireMe123 не знает, откуда пришел токен и для кого он был выпущен. Давайте припомним: аутентификация — это проверка, действительно ли пользователь является тем, за кого себя выдает. То, что HireMe123 может использовать токен доступа для доступа к API, не дает ему оснований полагать, что пользователь действительно является тем, кем назвался.
Как уже говорилось, это никого не остановило и люди продолжали использовать OAuth 2.0 и токены доступа не по назначению (т. е., для аутентификации). В связи с этим быстро стала очевидной необходимость формализовать аутентификацию поверх OAuth 2.0, чтобы позволить логиниться при помощи сторонних приложений и при этом не допускать пробоев в безопасности.
OpenID Connect
Это привело нас к спецификации под названием OpenID Connect (OIDC).
OIDC это спецификация поверх OAuth 2.0, которая говорит, как аутентифицировать пользователей. Стандарты OIDC разрабатываются OpenID Foundation (OIDF).
OIDC — это слой идентификации для аутентификации пользователей при помощи сервера авторизации.
Вы помните, что сервер авторизации выпускает токены. Токены — это закодированные кусочки данных для передачи информации между разными сторонами (такими как сервер авторизации, приложение или API). В случае OIDC и аутентификации сервер авторизации выпускает ID-токены.
ID-токены
ID-токены предоставляют информацию о событии аутентификации и идентифицируют пользователя. ID-токены предназначены для клиента. Они имеют фиксированный формат, понятный для клиента: клиент может извлечь идентифицирующую информацию из токена и таким образом аутентифицировать пользователя.
OIDC декларирует фиксированный формат для ID-токенов —
JSON Web Token (JWT)
JSON Web Tokens (JWT, иногда произносится как «джот») составляются из трех URL-безопасных строковых сегментов, соединенных точками.
Заголовок JWT
Первый сегмент токена это заголовок. Он может выглядеть примерно так:
Заголовок токена это JSON-объект, содержащий алгоритм подписи и тип токена. Он зашифрован при помощи алгоритма base64Url (бинарные данные, представленные в виде текста).
В расшифрованном виде это выглядит примерно так:
Полезная нагрузка JWT
Второй сегмент токена — полезная нагрузка. Выглядеть этот сегмент может так:
Это объект JSON, содержащий заявления (claims) — предложения о пользователе и событии аутентификации. Например:
Этот сегмент тоже base64Url-зашифрован.
Крипто-сегмент
Последний сегмент — это подпись или данные шифрования. JWT подписываются, чтобы их нельзя было модифицировать при пересылке. Когда сервер авторизации выпускает токен, он подписывает его при помощи ключа.
Получая ID-токен, клиент проверяет подпись (тоже при помощи ключа).
(При использовании асимметричного алгоритма для подписи и проверки используются разные ключи. В таком случае только у сервера авторизации есть возможность подписывать токены).
Не переживайте, если все это кажется запутанным. Детали того, как все работает, не должны вас беспокоить или мешать эффективно использовать сервер авторизации с аутентификацией на основе токенов.
Заявления
Теперь, когда мы знаем анатомию JWT, давайте остановимся на заявлениях (claims) — тех самых предложениях из сегмента полезной нагрузки токена. Как следует из самого их названия, ID-токены предоставляют идентифицирующую информацию, а содержится она в заявлениях.

Заявления аутентификации
Начнем с предложений, касающихся события аутентификации. Вот несколько примеров таких заявлений:
Среди необходимых заявлений об аутентификации в ID-токенах можно назвать:
- iss (issuer): сторона, генерирующая JWT, т. е., сервер авторизации;
- aud (audience): список получателей JWT. Для ID-токенов это клиентский идентификатор приложения, получающего токен;
- exp (expiration time): время в формате Unix Time, определяющее момент, когда токен станет не валидным (expiration);
- iat (issued at time): время выпуска ID-токена (тоже в формате Unix Time).
nonce привязывает запрос авторизации клиента к получаемому токену. По сути это криптографически случайная строка, которую клиент создает и отсылает вместе с запросом авторизации. Сервер авторизации помещает nonce в токен, который отсылает назад приложению. Приложение проверяет, совпадает ли nonce в токене с тем, который был послан с запросом авторизации. Таким образом приложение может проверить, что токен поступил именно из того места, откуда запрашивался.
Заявления идентичности
Заявления также включают предложения о конечном пользователе. Вот несколько примеров таких заявлений:
Среди стандартных заявлений профиля в ID-токенах можно назвать:
- sub (subject): уникальный идентификатор пользователя (указывается обязательно);
- email ;
- email_verified ;
- birthdate (дата рождения).
Итак, мы прошли краткий курс по важным спецификациям (OAuth 2.0 и OpenID Connect). Теперь давайте посмотрим, как можно применить эти знания в работе.
Аутентификация при помощи ID-токенов
Давайте посмотрим, как происходит OIDC-аутентификация.
Примечание. Учитывайте, что это упрощенная схема. В зависимости от архитектуры вашей системы у вас будет несколько других потоков.
Рассмтариваемые нами сущности: браузер, приложение, запущенное в браузере, и сервер авторизации. Когда пользователь хочет войти в систему (залогиниться в приложении), это приложение отсылает запрос авторизации на сервер авторизации. Пользовательские логин и пароль проверяются сервером авторизации. Если все сходится, сервер выпускает ID-токен для приложения.
Затем клиентское приложение расшифровывает ID-токен (JWT) и проверяет его. В проверку входит проверка подписи, а также заявлений:
- issuer ( iss ): выпущен ли токен именно тем сервером авторизации, от которого мы ждали ответа?
- audience ( aud ): является ли наше приложение тем получателем, для которого был выпущен токен?
- expiration ( exp ): не истек ли срок годности этого токена?
- nonce ( nonce ): можем ли мы привязать этот токен к запросу авторизации, который мы посылали?
Когда мы установили аутентичность ID-токена, пользователь считается аутентифицированным. Также у нас есть доступ к заявлениям идентичности, благодаря чему мы знаем, кем является наш пользователь.
Итак, пользователь аутентифицирован. Время взаимодействовать с API.
Доступ к API при помощи токенов доступа
Выше мы уже немного поговорили о токенах доступа — когда рассматривали, как работает делегированный доступ с использованием OAuth 2.0 и серверов авторизации. Теперь давайте разберем все это подробнее. Для этого вернемся к нашему сценарию с HireMe123 и MyCalApp.
Токены доступа
Токены доступа используются для предоставления доступа к ресурсам. Благодаря токену доступа, выпущенному сервером авторизации MyCalApp, HireMe123 может получить доступ к API MyCalApp.
В отличие от ID-токенов, которые OIDC определяет как JSON веб-токены, токены доступа не имеют четко определенного формата. Они не обязательно являются JWT. Тем не менее, во многих решениях для токенов доступа все же используются JWT, потому что этот формат позволяет валидацию.
Токены доступа непрозрачны для клиента
Токены доступа предназначаются для API ресурса и важно, чтобы они были непрозрачны для клиента. Почему?
Токены доступа могут измениться в любой момент. У них должно быть короткое время устаревания, чтобы пользователь мог часто получать новые. Также они могут выпускаться повторно, чтобы дать возможность доступа к другим API или с другими правами. В клиентском приложении ни в коем случае не должно быть кода, полагающегося на содержимое токена доступа. Подобный код будет слишком хрупким и практически гарантированно сломается.
Доступ к API ресурса
Допустим, мы хотим использовать токен доступа для вызова API одностраничного приложения. Как это происходит?
Выше мы рассматривали процесс аутентификации. Предположим, что пользователь залогинился в наше JS-приложение в браузере. Это приложение отсылает запрос авторизации к серверу авторизации, запрашивая токен доступа для вызова API.
Затем, когда наше приложение хочет вступить во взаимодействие с этим API, мы прилагаем токен доступа к заголовку запроса, вот так:
Авторизованный запрос отсылается к API, который проверяет токен при помощи промежуточного ПО. Если все сходится, API возвращает данные (например, JSON) приложению, запущенному в браузере.
Это все хорошо, но выше мы упоминали, что OAuth решает проблему чрезмерных прав доступа. Как это происходит?
Делегирование с областью видимости
Как API узнает, какой уровень доступа нужно дать приложению? Это определяется путем установления области видимости (scopes).
Область видимости «ограничивает, что именно приложение может делать в интересах пользователя». Она не позволяет выдать права, которых у пользователя уже нет. Например, если пользователь MyCalApp не имеет права создавать новые корпоративные аккаунты, область видимости гарантирует, что HireMe123 тоже не позволит пользователю создавать новые корпоративные аккаунты.
Области видимости делегируют контроль доступа самому API или ресурсу. За соответствие областей видимости правам пользователя отвечает API.
Давайте рассмотрим это на примере.
Я использую приложение HireMe123. HireMe123 хочет получить доступ к стороннему API MyCalApp для создания события в календаре от моего имени. Приложение HireMe123 уже запросило токен доступа к MyCalApp на сервере авторизации MyCalApp. В этом токене содержится важная информация:
- sub : (мой пользовательский ID в MyCalApp);
- aud : MyCalAppAPI (этот токен создан для доступа к API MyCalApp);
- scope : write:events (область видимости предполагает, что HireMe123 может использовать API для записи событий в моем календаре).
HireMe123 посылает запрос к API MyCalApp с токеном доступа в заголовке авторизации. Когда API MyCalApp получает этот запрос, он видит, что в нем установлена область видимости write:events.
Но на MyCalApp содержатся аккаунты с календарями сотен тысяч пользователей. Поэтому, чтобы убедиться, что этот запрос от HireMe123 будет касаться только моих прав создавать события в моем аккаунте, промежуточное ПО API MyCalApp должно проверить не только область видимости, но и sub — идентификатор субъекта.
В контексте делегированной авторизации области видимости обозначают, что именно приложение сможет делать от имени пользователя. Это подмножество прав пользователя в целом.
Проверка согласия
Помните, мы говорили, что сервер авторизации спрашивает пользователя HireMe123, согласен ли он разрешить HireMe123 воспользоваться правами пользователя для доступа к MyCalApp?
Этот диалог выглядит примерно так:
HireMe123 может попросить предоставить ему самые разные права, на пример:
- write:events (запись событий)
- read:events (чтение событий)
- read:settings (чтение настроек)
- write:settings (запись настроек)
- и т. д.
В целом, следует избегать давать разрешения на чисто пользовательские действия. Области видимости предназначены для прав, делегированных приложениям. Но если ваш сервер авторизации имеет функционал для контроля доступа на основе ролей (Role-Based Access Control, RBAC), вы можете устанавливать разные области видимости для разных пользователей.
При помощи RBAC на сервере авторизации можно установить разные права для разных групп пользователей. После этого сервер авторизации при выпуске токенов доступа сможет включать в scopes указание ролей пользователей.
Итоги
Мы рассмотрели очень много материала, но это даже близко не все, что можно было рассмотреть. Тем не менее, я надеюсь, что этот краткий курс по идентичности, авторизации и аутентификации будет вам полезен.