Авторизация в системе через REST API
REST API работает только если при работе с ТСД используется сервер Mobile SMARTS. При прямом подключении ТСД к компьютеру через кабель/крэдл или при обмене с учетной системой через папку использовать REST API не получится.
В базе Mobile SMARTS должна быть включена авторизация, тогда для выполнения HTTP-запросов необходимо будет каждый раз авторизовываться, есть 2 варианта авторизации:
- BASIC авторизация — в этом случае при каждом HTTP-запросе нужно будет отправлять логин и пароль;
- авторизация с использованием токена — при первом HTTP-запросе передаются логин и пароль, а сервер MS возвращается токен — уникальный идентификатор сессии (access token), который при последующих HTTP-запросах можно будет использовать вместо передачи логина и пароля. Срок действия токена (т.е. сессии) ограничен, поэтому для обновления токена при первом запросе сервер Mobile SMARTS дополнительно возвращает «токен обновления» (refresh token) — он нужен для получения нового токена после истечения срока действия текущего токена.
Обратите внимание. Для защиты передаваемых данных рекомендуется использовать режим с включенной аутентификацией и доступом по https.
Для работы через протокол https необходимо указать это в настройках базы данных, для этого зайдите в менеджер базы, справа кнопки «Добавить» нажмите на выпадающий список и выберите пункт «Настройки», далее в окне «Редактирование настроек» поставьте галочку «Использовать http» и нажмите «ОК».
Для работы в режиме с включенной аутентификацией необходимо завести пользователей, задать им логины и пароли. В панели управления Mobile SMARTS узел «Пользователи и группы» содержит данные о пользователях и группах, в которых они состоят.
Инструкция по конфигурации настроек пользователей и групп пользователей, как определить роль пользователей и список типов документов, доступных для обработки пользователям такой группы описана в одноименной статье на сайте.
Пример запроса на авторизацию приложения методом POST
- Для начала в настройках базы Mobile SMARTS включите авторизацию/аутентификацию по пользователю и нажмите на кнопку «ОК»
- Пример запроса на авторизацию
http://localhost:51434/api/v1/session?username=&password=
в ответ приходит Access_token
BASIC авторизация
Данный вид авторизации чаще всего используется браузером для доступа к функциям API.
При использовании данного метода необходимо в заголовке каждого запроса указывать:
Authorization: Basic :
Допускается base64 при формировании строки :
Авторизация методом GET
Еще один способ авторизации — отправить GET запрос по адресу /api/v1/session, при этом в url запросе указать параметры login и password:
Ответ сервера:
Access_token:"123123123", Token_type:"bearer", Expires_in:86400, Refresh_token:"321321321",
>
Авторизация методом POST
Авторизация по логину и паролю происходит путем отправки POST запроса на сервер, в результате которого возвращается access_token и refresh token в формате JSON.
Пример запроса:
POST /oauth/token HTTP/1.1
Host: mobilesmarts.ru/api/session
Content-Type: application/x-www-form-urlencoded
Ответ сервера:
Access_token:"123123123",
Token_type:"bearer", Expires_in:86400, Refresh_token:"321321321", >
Восстановление после окончания срока действия сессии
Восстановление после окончания срока действия сессии происходит путем отправки refresh_token на сервер, в результате приходит новый access_token и refresh_token
POST /oauth/token HTTP/1.1
Host: mobilesmarts.ru/api/session
Content-Type: application/x-www-form-urlencoded
HTTP/1.1 200 OK
Content-Type: application/json
Access_token:"99999",
Token_type:"bearer",
Expires_in:86400,
Refresh_token:"789789789",
>
Вызов функций с использованием token
Для того чтобы обратиться к функциям (если не используется Basic авторизация), для которых необходима авторизация, необходимо в заголовке Authorization передавать токен:
Иначе сервер вернет ошибку авторизации 401.
Контроль доступа к API
Летом 2022 года Банк России начал готовить к публикации документ, получивший название «Концепция внедрения Открытых API». Публикация концепции — очередной шаг в последовательном процессе, который регулятор реализует уже несколько лет. Цель мероприятия — распространить новые технологии обслуживания при помощи API на всю банковскую отрасль России.
API. Определение и особенности применения
Открытые API (Open API) — это технология обмена данными между информационными системами разных субъектов коммерческой деятельности по стандартным протоколам взаимодействия. По данным РБК, немалое количество крупных отечественных банков уже обладают опытом успешного внедрения Открытых API в собственные бизнес-процессы.
Однако Open API — лишь частный случай применения API как таковых. Банки и другие финансовые организации уже давно используют API (интерфейсы прикладного программирования) для подключения внешних финтех-приложений и сервисов к своим внутренним программным системам. API помогают реализовать то, что мы называем «цифровыми банковскими услугами»: обработку транзакций, перевод денежных средств, работу с клиентскими счетами, подачу кредитных заявлений и проч.
Принцип работы API
Когда пользователь запрашивает баланс в банковском приложении, оно обращается ко внутреннему API, который, в свою очередь, формирует запрос к автоматизированной банковской системе (АБС) и возвращает ответ клиенту.
В широкой трактовке API позволяет получить доступ к различным программным инструментам, вынести в отдельное приложение функциональность, которую необходимо защитить. Благодаря API те же банки могут связывать отдельные компоненты своей системы между собой. Например, к единой экосистеме можно подключить платежный сервис, функционал обмена межбанковскими документами или настроить аутентификацию в личном кабинете пользователя через социальную сеть.
Требования аутентификации и авторизации API
В программной системе современного финансового учреждения, пользователю, прежде чем обратиться к API для получения необходимой услуги, потребуется пройти процедуру аутентификации. API-интерфейсы нуждаются в этой процедуре: поскольку при запросе программной системы банка со стороны стороннего приложения (например, финтех-сервиса) происходит обращение к конфиденциальным клиентским данным. Более того, требования информационной безопасности предполагают, что клиентские запросы должны быть верифицированы — то есть их подлинность не должна вызывать сомнений. На этом основано безопасное взаимодействие между коммерческими партнерами при помощи API.
Напомним, что аутентификация — один из этапов защиты информации (функциональной, персональной или финансовой), во время которого пользователь предоставляет доказательства, что он и есть тот субъект, который запрашивает доступ к защищенным данным.
Аутентификация API необходима еще и потому, что без защиты интерфейсы могли бы быть вызываемы бесчисленное количество раз, что создавало бы избыточную нагрузку на программную инфраструктуру. То есть программная система не смогла бы связывать запросы пользователей с конкретными данными, не рискуя их скомпрометировать.
Методов аутентификации API-интерфейсов довольно много, но мы остановимся на наиболее часто используемых:
- Базовая аутентификации (Basic Auth)
- С использованием API-ключа
- На основе токенов доступа
- Аутентификация сообщений на основе хэша
- Стандарты OAuth 2.0 и OpenID Connect
Базовая аутентификация — наиболее простая схема, при которой в отправленный на сервер запрос помещаются имя пользователя и пароль. Чтобы передать реквизиты через веб, они преобразуются в кодированный набор 64 символов. Когда сервер API получает сообщение, он декодирует его и сверяет заголовок с сохраненными учетными данными. Расшифровав и проанализировав содержимое строки, он принимает или отклоняет запрос.
Аутентификацию API-ключом использует большинство программных интерфейсов. Ключ — это длинная последовательность уникальных символов, используемая вместо стандартных реквизитов. Как правило, такой ключ размещается в заголовок или в URL запроса и позволяет идентифицировать запрашивающий API субъект. Помимо раздачи ключей, сервер API может также заранее ограничить клиенту доступ к части административных функций.
При построении распределенных систем Single Sign-On (когда один сервис делегирует другому функцию аутентификации пользователя) применяют аутентификацию по токенам. Примером такого способа является вход в приложение при помощи привязанного аккаунта социальной сети. Соцсеть передает достоверные сведения о пользователе в виде токена, который приложение сможет использовать для идентификации, аутентификации и авторизации.
Токен содержит широкий набор информации: кем он сгенерирован? кто может его получить? в течение какого срока токен действителен? Также токен может заключать в себе ряд сведений о пользователе и иметь уникальную подпись, подтверждающих достоверность данных.
Hash-based message authentication code (или HMAC) — тип аутентификации на основе хэша, который часто используется в API организаций, обслуживающих финансовые операции. При использовании этого метода сообщение пропускается через алгоритм безопасного хэширования, затем от хэша вычисляется HMAC-подпись.
В методе HMAC отправитель кодирует сообщение, предназначенное получателю, секретным ключом, который после этого пропускается через алгоритм безопасного хэширования. Значение, полученное на выходе (сигнатура или подпись) помещает в заголовок запроса. Сервер API, зная секретный ключ, принимает подпись и генерирует аналогичную строку. Запрос успешно принимается, если содержимое строки и сигнатуры совпадают.
Стандарт OAuth 2.0 относится к одним из наиболее популярных методов аутентификации и авторизации. Он описывает, каким образом стороннее приложение получает доступ к ресурсам пользователя, благодаря чему тот может «связывать» между собой «клиент» (то есть приложение, которым он пользуется) с сервером API и сервером аутентификации.
Схема работы протокола OAuth 2.0
Сперва пользователь дает «клиенту» (приложению или серверу) разрешение на доступ к своим ресурсам. Приложение отправляет данные на страницу входа в систему на сервере аутентификации. Если все успешно — сервер возвращает пользователю токен доступа. После чего пользователь отправляет на сервер API запрос, в заголовок которого добавлена строка токена. Сервер API проверяет токен и принимает решение об авторизации пользователя.
Стандарт OpenID Connect описывает слой учетных данных, накладываемых поверх OAuth. По этому стандарту сервер аутентификации предоставляет дополнительный токен с набором добавочных полей, содержащих некие сведения о пользователе.
Обеспечение безопасности API-ключей
Поскольку все больше организаций используют API для работы с разными сервисами, а количество обрабатываемых данных постоянно увеличивается, API-интерфейсы все чаще становятся объектами кибератак. Проблема с API заключается в том, что они предоставляют доступ к большим объемам информации, минуя традиционные защитные механизмы веб-браузеров. Недостаточно просто перекрыть XSS-эксплойты и подобрать защиту от встраиваний вредоносного SQL-кода — нужно побеспокоиться о злоумышленниках, которые могут авторизоваться и постранично просматривать пользовательские записи.
Выше мы говорили, что обычно программные интерфейсы защищены API-ключом или требуют аутентификации при помощи токена (например, по стандарту JSON Web Token). На практике таким образом обеспечивается естественная защита API, так как инструменты безопасности могут обнаружить аномальное поведение интерфейса и заблокировать доступ к API-ключу. Тем не менее злоумышленники могут сгенерировать колоссальный набор API¬-ключей, перебирая их подобно тому, как это делают с ID-адресами при совершении DDoS-атак.
Самым простым способом обезопасить веб-сайт или сервис является запрет генерировать API-ключи новым пользователям, едва прошедшим регистрацию. Только пользователи, получившие от системы статус доверенных, должны иметь возможность программно генерировать API-ключи.
Можно сократить шанс компрометации ключа, если задать определенное время доступа к API, по истечении которого рабочая сессия завершится. Также хорошей практикой является использование двухфакторной аутентификации для сгенерированных API-ключей.
Используя два токена вместо одного, можно дополнительно защитить API-ключ от раскрытия злоумышленником. Такая схема реализована в OAuth 2.0 — где первый токен (Access token) применяется для запроса к серверу. Как правило, он может быть использован несколько раз и имеет небольшой срок действия. Второй токен (Refresh token) — одноразовый и «живет дольше», он используется для обновления истекшего Access token’а.
Преимущество схемы с двумя токенам состоит в том, что сокращается временное окно, в течение которого злоумышленники могут провести успешную кибератаку на сервис и получить к нему доступ.
Применение API Gateway
С ростом количества пользователей и их запросов к какому-либо сервису API-трафик может замедляться. Поэтому в высоконагруженных системах информация перенаправляется в API Gateway — сервис шлюза, который является единой точкой входа для веб-приложений и API. Этот шлюз выполняет разные задачи — от получения и обработки API-запросов до контроля трафика и уровня доступа.
API Gateway устанавливается «на входе» в инфраструктуру организации и выступает в качестве прокси-буфера между пользователями и API-сервисами. Любые наружные запросы к интерфейсам прикладного программирования проходят через этот шлюз, после чего перенаправляются в один из нескольких микросервисов.
Схема работы API Gateway
Сегодня в разработке все чаще используют облачные API Gateway. Наряду со стандартными задачами, они могут реализовывать быстрое масштабирование, создание и публикацию API, отладку API внутренними средствами, мониторинг, а также интеграцию с иными облачными сервисами.
Самые известные облачные платформы по управлению трафиком API:
- Amazon API Gateway
- Azure API Management
- Oracle API Gateway
- Google API Gateway
- Yandex API Gateway и прочие.
Все они обладают схожим набором функций и позволяют предоставлять контролируемый доступ к API, использовать и тестировать различные типы интерфейсов в одном приложении, создавать API-ключи и проч.
Разработка системы заметок с нуля. Часть 2: REST API для RESTful API Service + JWT + Swagger
Продолжаем серию материалов про создание системы заметок. В этой части мы спроектируем и разработаем RESTful API Service на Go cо Swagger и авторизацией. Будет много кода, ещё больше рефакторинга и даже немного интеграционных тестов.
В первой части мы спроектировали систему и посмотрели, какие сервисы требуются для построения микросервисной архитектуры.
Подробности в видео и текстовой расшифровке под ним.
Прототипирование
Начнём с макетов интерфейса. Нам нужно понять, какие ручки будут у нашего API и какой состав данных он должен отдавать. Макеты мы будем делать, чтобы понять, какие сущности, поля и эндпоинты нам нужны. Используем для этого онлайн-сервис NinjaMock. Он подходит, если макет надо сделать быстро и без лишних действий.
Страницу регистрации сделаем простую, с четырьмя полями: Name, Email, Password и Repeat Password. Лейблы делать не будем, обойдемся плейсходерами. Авторизацию сделаем по юзернейму и паролю.
После входа в приложение пользователь увидит список заметок, который будет выглядеть примерно так:
Интерфейс, который будет у нашего веб-приложения
- Слева — список категорий любой вложенности.
- Справа — список заметок в виде карточек, который делится на два списка: прикреплённые и обычные карточки.
- Каждая карточка состоит из заголовка, который урезается, если он очень длинный.
- Справа указано, сколько секунд/минут/часов/дней назад была создана заметка.
- Тело заголовка — отрендеренный Markdown.
- Панель инструментов. Через неё можно изменить цвет, прикрепить или удалить заметку.
Тут важно отметить, что файлы заметки мы не отображаем и не будем запрашивать у API для списка заметок.
Полная карточка открывается по клику на заметку. Тут можно сразу отобразить полностью длинный заголовок. Высота заметки зависит от количества текста. Для файлов появляется отдельная секция. Мы их будем получать отдельным асинхронным запросом, который не помешает пользователю редактировать заметку. Файлы можно скачать по ссылке, также есть отдельная кнопка на добавление файлов.
Так будет выглядеть открытая заметка
В ходе прототипирования стало понятно, что в первой части мы забыли добавить еще один микросервис — TagsService. Он будет управлять тегами.
Определение эндпоинтов
Для страниц авторизации и регистрации нам нужны эндпоинты аутентификации и регистрации соответственно. В качестве аутентификации и сессий пользователя мы будем использовать JWT. Что это такое и как работает, разберём чуть позднее. Пока просто запомните эти 3 буквы.
Для страницы списка заметок нам нужны эндпоинты /api/categories для получения древовидного списка категорий и /api/notes?category_id=? для получения списка заметок текущей категории. Перемещаясь по другим категориям, мы будем отдельно запрашивать заметки для выбранной категории, а на фронтенде сделаем кэш на клиенте. В ходе работы с заметками нам нужно уметь создавать новую категорию. Это будет метод POST на URL /api/categories . Также мы будем создавать новый тег при помощи метода POST на URL /api/tags .
Чтобы обновить заметку, используем метод PATCH на URL /api/notes/:uuid с измененными полями. Делаем PATCH , а не PUT , потому что PUT требует отправки всех полей сущности по спецификации HTTP, а PATCH как раз нужен для частичного обновления. Для отображения заметки нам ещё нужен эндпоинт /api/notes/:uuid/files с методами POST и GET . Также нам нужно скачивать файл, поэтому сделаем метод GET на URL /api/files/:uuid .
Эндпоинты обновления заметки и работы с файлами
Структура репозитория системы
Ещё немного общей информации. Структура репозитория всей системы будет выглядеть следующим образом:
В директории app будет исходный код сервиса (если он будет). На уровне с app будут другие директории других продуктов, которые используются с этим сервисом, например, MongoDB или ELK. Продукты, которые будут использоваться на уровне всей системы, например, Consul, будут в отдельных директориях на уровне с сервисами.
Разработка сервиса
Писать будем на Go
Установка завершена, всё работает.
- Идём на официальный сайт.
- Копируем ссылку до архива, скачиваем, проверяем хеш-сумму.
- Распаковываем и добавляем в переменную PATH путь до бинарников Go
- Пишем небольшой тест проверки работоспособности, собираем бинарник и запускаем.
Установка завершена, всё работает
Теперь создаём проект. Структура стандартная:
- build — для сборок,
- cmd — точка входа в приложение,
- internal — внутренняя бизнес-логика приложения,
- pkg — для кода, который можно переиспользовать из проекта в проект.
Исходники проекта — в репозитории на GitHub.
Я очень люблю логировать ход работы приложения, поэтому перенесу свою обёртку над логером logrus из другого проекта. Основная функция здесь Init , которая создает логер, папку logs и в ней файл all.log со всеми логами. Кроме файла логи будут выводиться в STDOUT . Также в пакете реализована поддержка логирования в разные файлы с разным уровнем логирования, но в текущем проекте мы это использовать не будем.
APIService будет работать на сокете. Создаём роутер, затем файл с сокетом и начинаем его слушать. Также мы хотим перехватывать от системы сигналы завершения работы. Например, если кто-то пошлёт приложению сигнал SIGHUP , приложение должно корректно завершиться, закрыв все текущие соединения и сессии. Хотел перехватывать все сигналы, но линтер предупреждает, что os.Kill и SIGSTOP перехватить не получится, поэтому их удаляем из этого списка.
Теперь давайте добавим сразу стандартный handler для метрик. Я его копирую в директорию pkg , далее добавляю в роутер. Все последующие роутеры будем добавлять так же.
Далее создаём точку входа в приложение. В директории cmd создаём директорию main , а в ней — файл app.go . В нём мы создаём функцию main , в которой инициализируем и создаём логер. Роутер создаём через ключевое слово defer , чтобы метод Init у роутера вызвался только тогда, когда завершится функция main . Таким образом можно выполнять очистку ресурсов, закрытие контекстов и отложенный запуск методов. Запускаем, проверяем логи и сокет, всё работает.
Но для разработки нам нужно запускать приложение на порту, а не на сокете. Поэтому давайте добавим запуск приложения на порту в наш роутер. Определять, как запускать приложение, мы будем с помощью конфига.
Создадим для приложения контекст. Сделаем его синглтоном при помощи механизма sync.Once . Пока что в нём будет только конфиг. Контекст в виде синглтона создаю исключительно в учебных целях, впоследствии он будет выпилен. В большинстве случаев синглтоны — необходимое зло, в нашем проекте они не нужны. Далее создаём конфиг. Это будет YAML-файл, который мы будем парсить в структуру.
В роутере мы вытаскиваем из контекста конфиг и на основании listen.type либо создаем сокет, либо вешаем приложение на порт. Код graceful shutdown выделяем в отдельный пакет и передаём на вход список сигналов и список интерфейсов io.Close , которые надо закрывать. Запускаем приложение и проверяем наш эндпоинт heartbeat . Всё работает. Давайте и конфиг сделаем синглтоном через механизм sync.Once , чтобы потом безболезненно удалить контекст, который создавался в учебных целях.
Теперь переходим к API. Создаём эндпоинты, полученные при анализе прототипов интерфейса. Тут важно отметить, что у нас все данные привязаны к пользователю. На первый взгляд, все ручки должны начинаться с пользователя и его идентификатора /api/users/:uuid . Но у нас будет авторизация, иначе любой пользователь сможет программно запросить заметки любого другого пользователя. Авторизацию можно сделать следующим образом: Basic Auth, Digest Auth, JSON Web Token, сессии и OAuth2. У всех способов есть свои плюсы и минусы. Для этого проекта мы возьмём JSON Web Token.
Работа с JSON Web Token
JSON Web Token (JWT) — это JSON-объект, который определён в открытом стандарте RFC 7519. Он считается одним из безопасных способов передачи информации между двумя участниками. Для его создания необходимо определить заголовок (header) с общей информацией по токену, полезные данные (payload), такие как id пользователя, его роль и т.д., а также подписи (signature).
JWT использует преимущества подхода цифровой подписи JWS (Signature) и кодирования JWE (Encrypting). Подпись не даёт кому-то подделать токен без информации о секретном ключе, а кодирование защищает от прочтения данных третьими лицами. Давайте разберёмся, как они могут нам помочь для аутентификации и авторизации пользователя.
- Аутентификация — процедура проверки подлинности. Мы проверяем, есть ли пользователь с полученной связкой логин-пароль в нашей системе.
- Авторизация — предоставление пользователю прав на выполнение определённых действий, а также процесс проверки (подтверждения) данных прав при попытке выполнения этих действий.
Другими словами, аутентификация проверяет легальность пользователя. Пользователь становится авторизированным, если может выполнять разрешённые действия.
Важно понимать, что использование JWT не скрывает и не маскирует данные автоматически. Причина использования JWT — проверка, что отправленные данные были действительно отправлены авторизованным источником. Данные внутри JWT закодированы и подписаны, но не зашифрованы. Цель кодирования данных — преобразование структуры. Подписанные данные позволяют получателю данных проверить аутентификацию источника данных.
Реализация JWT в нашем APIService
- Создаём директории middleware и jwt , а также файл jwt.go .
- Описываем кастомные UserClaims и сам middlware .
- Получаем заголовок Authorization , оттуда берём токен.
- Берём секрет из конфига.
- Создаём верификатор HMAC.
- Парсим и проверяем токен.
- Анмаршалим полученные данные в модель UserClaims .
- Проверяем, что токен валидный на текущий момент.
При любой ошибке отдаём ответ с кодом 401 Unauthorized . Если ошибок не было, в контекст сохраняем ID пользователя в параметр user_id , чтобы во всех хендлерах его можно было получить. Теперь надо этот токен сгенерировать. Это будет делать хендлер авторизации с методом POST и эндпоинтом /api/auth . Он получает входные данные в виде полей username и password, которые мы описываем отдельной структурой user . Здесь также будет взаимодействие с UserService , нам надо там искать пользователя по полученным данным. Если такой пользователь есть, то создаём для него UserClaims , в которых указываем все нужные для нас данные. Определяем время жизни токена при помощи переменной ExpiresAt — берём текущее время и добавляем 15 секунд. Билдим токен и отдаём в виде JSON в параметре token . Клиента к UserService у нас пока нет, поэтому делаем заглушку.
Добавим в хендлер с heartbeat еще один тестовый хендлер, чтобы проверить работу аутентификации. Пишем небольшой тест. Для этого используем инструмент sketch , встроенный в IDE. Делаем POST -запрос на /api/auth , получаем токен и подставляем его в следующий запрос. Получаем ответ от эндпоинта /api/heartbeat , по истечении 5 секунд мы начнём получать ошибку с кодом 401 Unauthorized .
Наш токен действителен очень ограниченное время. Сейчас это 15 секунд, а будет минут 30. Но этого всё равно мало. Когда токен протухнет, пользователю необходимо будет заново авторизовываться в системе. Это сделано для того, чтобы защитить пользовательские данные. Если злоумышленник украдет токен авторизации, который будет действовать очень большой промежуток времени или вообще бессрочно, то это будет провал.
Чтобы этого избежать, прикрутим refresh-токен. Он позволит пересоздать основной токен доступа без запроса данных авторизации пользователя. Такие токены живут очень долго или вообще бессрочно. После того как только старый JWT истекает мы больше не можем обратиться к API. Тогда отправляем refresh-токен. Нам приходит новая пара токена доступа и refresh-токена.
Хранить refresh-токены на сервере мы будем в кэше. В качестве реализации возьмём FreeCache . Я использую свою обёртку над кэшем из другого проекта, которая позволяет заменить реализацию FreeCache на любую другую, так как отдает интерфейс Repository с методами, которые никак не связаны с библиотекой.
Пока рассуждал про кэш, решил зарефакторить существующий код, чтобы было удобней прокидывать объекты без dependency injection и синглтонов. Обернул хендлеры и роутер в структуры. В хендлерах сделал интерфейс с методом Register , которые регистрируют его в роутере. Все объекты теперь инициализируются в main , весь роутер переехал в мейн. Старт приложения выделили в отдельную функцию также в main -файле. Теперь, если хендлеру нужен какой-то объект, я его просто буду добавлять в конструктор структуры хендлера, а инициализировать в main . Плюс появилась возможность прокидывать всем хендлерам свой логер. Это будет удобно когда надо будет добавлять поле trace_id от Zipkin в строчку лога.
Вернемся к refresh_token. Теперь при создании токена доступа создадим refresh_token и отдадим его вместе с основным. Сделаем обработку метода PUT для эндпоинта /api/auth , а в теле запроса будем ожидать параметр refresh_token , чтобы сгенерировать новую пару токена доступа и refresh-токена. Refresh-токен мы кладём в кэш в качестве ключа. Значением будет user_id , чтобы по нему можно было запросить данные пользователя у UserService и сгенерировать новый токен доступа. Refresh-токен одноразовый, поэтому сразу после получения токена из кэша удаляем его.
Описание API
Для описания нашего API будем использовать спецификацию OpenAPI 3.0 и Swagger — YAML-файл, который описывает все схемы данных и все эндпоинты. По нему очень легко ориентироваться, у него приятный интерфейс. Но описывать вручную всё очень муторно, поэтому лучше генерировать его кодом.
- Создаём эндпоинты /api/auth с методами POST и PUT для получения токена по юзернейму и паролю и по Refresh-токену соответственно.
- Добавляем схемы объектов Token и User .
- Создаём эндпоинты /api/users с методом POST для регистрации нового пользователя. Для него создаём схему CreateUser.
Понимаем, что забыли сделать хендлер для регистрации пользователя. Создаём метод Signup у хенлера Auth и структуру newUser со всеми полями для регистрации. Генерацию JWT выделяем в отдельный метод, чтобы можно было его вызывать как в Auth , так и в Signup -хендлерах. У нас всё еще нет UserService , поэтому проставляем TODO. Нам надо будет провалидировать полученные данные от пользователя и потом отправить их в UserService , чтобы он уже создал пользователя и ответил нам об успехе. Далее вызываем функцию создания пары токена доступа и refresh-токена и отдаём с кодом 201.
У нас есть подсказка в виде Swagger-файла. На его основе создаём все нужные хендлеры. Там, где вызов микросервисов, будем проставлять комментарий с TODO.
Создаём хендлер для категорий, определяем URL в константах. Далее создаём структуры. Опираемся на Swagger-файл, который создали ранее. Далее создаём сам хендлер и реализуем метод Register , который регистрирует его в роутере. Затем создаём методы с логикой работы и сразу пишем тест API на этот метод. Проверяем, находим ошибки в сваггере. Таким образом мы создаём все методы по работе с категориями: получение и создание.
Далее создаём таким же образом хендлер для заметок. Понимаем, что забыли методы частичного обновления и удаления как для заметок, так и для категорий. Дописываем их в Swagger и реализуем методы в коде. Также обязательно тестируем Swagger в онлайн-редакторе.
Здесь надо обратить внимание на то, что методы создания сущности возвращают код ответа 201 и заголовок Location , в котором находится URL для получения сущности. Оттуда можно вытащить идентификатор созданной сущности.
В третьей части мы познакомимся с графовой базой данных Neo4j, а также будем работать над микросервисами CategoryService и APIService.
Аутентификация и авторизация при построении API на Grape
При построении API нужно решать задачу контроля доступа. Она состоит из двух подзадач: аутенификации – процесса определения того, кто пытается получить доступ, и авторизации – процесса предоставления доступа к тем или иным ресурсам.
В Grape уже реализованы два вида аутентификации: HTTP Basic и HTTP Digest. Встроенного решения для авторизации в геме нет.
В этой статье я расскажу, как решить задачу контроля доступа с помощью токена. Рассмотрим пример:
Аутентифкация
Задача аутентификации решается вызовом метода authenticate_current_user! в строке 13 файла friends_api.rb . Этот метод ищет пользователя по переданному токену, и поднимает исключение Api::V1::BaseAPI::InvalidToken если пользователь не найден.
Важно явно в начале каждой точки входа в API вызывать authenticate_current_user! – таким образом мы показываем, что мы будем работать в этой точке входа с пользователем по токену.
Авторизация
Задача авторизации решается с помощью гема six, об использовании которого в Ruby on Rails я уже писал. В строке 14 файла friends_api.rb мы вызываем метод authorize! , который проверяет, что разрешение :friends находится в списке доступных действий над ресурсом resource_user для пользователя current_user (файл base_api.rb , строка 14). Если нужного разрешения в списке нет, то поднимается исключение Api::V1::BaseAPI::AccessDenied .
Код, который определяет список доступных действий над ресурсом нужно вынести в отдельный файл.
Про токен доступа
Безопасность токена
- Чтобы сделать токен доступа максимально устойчивым к подбору, его нужно сделать случайной строкой большой длины. После очередной авторизации или смены пароля токен желательно генерировать заново;
- для того, чтобы исключить перехват токенов между клиентами и API третьей стороной, используйте HTTPS;
- чтобы усложнить исследование протокола, можно добавить механизм подписи токена секретным ключом на клиенте, с последующей проверкой подписи на сервере.
Отслеживание сессий
Токены можно использовать для отслеживания сессий в API: по сессиям можно понять, кто, когда и какие действия совершал.
В следующей статье я расскажу про вывод данных в API на Grape.
Смотрите также
- github.com/intridea/grape
- github.com/intridea/grape-entity
- Grape API authentication using Devise Auth Token
- Авторизация в приложении на Ruby on Rails с помощью гема six
Блог Цифрономики
Мысли о веб-разработке на Ruby on Rails: работа с кодом, приёмы, инструменты, организация процесса разработки.
Веб-разработка на Ruby on Rails, реализация сложных проектов
mailbox@cifronomika.ru
+7 (910) 535-99-11