Использование лямбда-авторизатора с AWS WebSocket

Предыдущая часть: “Знакомство с AWS WebSocket

В предыдущей статье мы научились создавать WebSocket API в AWS. В том мини-проекте мы выстроили функциональный API, к которому можно подключаться, и от которого можно отправлять сообщения. Но это лишь часть паззла.

Теперь, когда мы начали работать с WebSocket, необходимо уделить внимание безопасности. Вообще-то с безопасности и надо было начинать, но поезд, как говориться, ушел. Тем не менее мы не хотим, чтобы злоумышленники подключались к нашему API и вставляли нам палки в колеса своими DDoS-атаками или попытками внедрения кода.

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

Что такое аутентификация в WebSocket

Вы можете задуматься: “А в чем вообще польза этой статьи? Лямбда-авторизатор в API я добавлять умею”.

Это, конечно, может быть так, но у WebSocket здесь есть свои причуды, о которых необходимо знать. Большинство JS-библиотек фронтенда, которые подключаются к WebSocket, по факту не поддерживают стандартные заголовки Authorization.

WebSocket API при установке подключения поддерживает только заголовок Sec-WebSocket-Protocol. Конечно, инструменты вроде Postman позволяют вам передавать при подключении другие заголовки, но когда дело дойдет до написания кода для фронтенда приложения, вы тут же застрянете.

Чтобы обойти эту проблему, не утеряв возможность обеспечения безопасного соединения, у нас есть два варианта:

  • предоставить в заголовке Sec-WebSocket-Protocol разграниченные значения;
  • предоставить токен-аутентификации в параметре строки запроса access_token.

У обоих подходов есть как сильные и слабые стороны, так что последнее решение будет за вами. Решение, которое мы развернули в своих аккаунтах AWS, поддерживает оба варианта. Лично я советую использовать подход с параметром строки запроса, потому что он прост и не переназначает заголовок Sec-WebSocket-Protocol.

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

Еще один нюанс аутентификации в WebSocket в том, что вам необходимо выполнять ее только при $connect. Каждый последующий вызов будет использовать одно и то же аутентифицированное соединение. Это все упрощает!

Если вы проработали первую часть серии, значит у вас в аккаунте AWS уже развернут базовый WebSocket. В нем есть возможность добавлять и удалять соединения, а также добавлять и удалять подписки на конкретные сущности. Сегодня же мы используем ветку того репозитория, которая расширит то, что у нас уже было.

Если вы не знаете, как переключаться на побочные ветки в репозитории, то можете выполнить в терминале либо VS Code следующие команды для локального переключения:

git fetch git 
checkout part-two

Когда исходный код будет у вас, можете развернуть его точно так же, как делали это ранее: с помощью команды sam deploy. Перед ее выполнением в файл samconfig.toml нужно внести одно изменение.

В нашем лямбда-авторизаторе мы проверяем, поступил ли jwt (он же токен аутентификации) из доверенного источника. Для этого мы уточняем, подписан ли jwt нашим секретным ключом. В качестве ключа вы можете установить что-угодно, только не забудьте поделиться им с другими, если реализуете этот процесс в продакшен-сценарии.

Я рекомендую использовать команду sam deploy --guided, чтобы повторно инстанцировать параметры для этого стека. Как только это будет сделано, можете деплоить!

И что я задеплоил?

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

  • новым лямбда-авторизатором;
  • обновленной лямбдой $connect, которая сохраняет пользовательскую информацию (подробнее об этом позже);
  • проверочной лямбдой, которая генерирует jwt на основе предоставленного при развертывании секретного ключа;
  • секретом Secrets Manager для безопасного сохранения вашего секрета jwt.

В репозиторий также входит схема всей инфраструктуры, сгенерированная из файла template.yaml. Если вы еще не используете подобные схемы в повседневной работе, то очень рекомендую. Они не требуют никаких усилий, но привносят невероятную ценность. Ниже показан весь набор ресурсов, развернутых в AWS для создания безопасного WebSocket API.

Схема инфраструктуры безопасной архитектуры WebSocket

Подключение к защищенному WebSocket

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

  1. Откройте desktop app.
  2. Выберите New -> WebSocket Request.
  3. Введите в адресное поле маршрут из вывода развернутой SAM (используйте выходное значение WebsocketUri). 
  4. Кликните по вкладке Headers и добавьте заголовок Sec-WebSocket-Protocol со значением websocket.
  5. Жмите Connect.

Если все пройдет как задумано, то мы должны получить ответ 401, потому что не предоставили токен аутентификации.

Ответ 401 при подключении без токена аутентификации

А теперь мы установим соединение, получив тот самый необходимый токен:

aws lambda invoke --function-name CreateTestJwt response.json
  1. Откройте сгенерированный файл response.json и скопируйте значение из свойства authToken
  2. В Postman добавьте в url параметр строки запроса access_token и вставьте для него значение authToken.
  3. Жмите Connect.
Успешное подключение!

После успешного подключения можно отправлять и получать сообщения в WebSocket без дополнительной аутентификации.

Что еще мы можем?

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

Во-вторых, мы получили возможность отправлять уведомления конкретным пользователям. Лямбда-авторизаторы возвращают объект context, содержащий дополнительную информацию, которую можно использовать в коде. В нашем образце авторизатора мы декодировали userId, firstName, lastName и sub из jwt и передали все это функции $connect.

Эта информация хранится в записи подключения в Dynamo, так что ее можно использовать для отправки push-уведомлений пользователям.

Пользовательская информация из лямбда-авторизатора

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

Дальнейшие шаги

Впереди вас ждут уроки о том, как документировать WebSocket API, используя Async API Spec, добавлять пользовательские push-уведомления для конкретного пользователя и кое-что другое.

Поэкспериментируйте со стеком в этом уроке, ознакомьтесь со всеми компонентами и попробуйте внести изменения.

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Allen Helton: Using Lambda Authorizers with AWS Websockets

Предыдущая статьяКак создать хранилище данных за 5 шагов
Следующая статьяСоздание модели машинного обучения с помощью Google Colab без дополнительных настроек