-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Модуль 8 «Ограничение доступа. Авторизация» #8
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Следующим шагом добавим в приложение поддержку авторизации. Есть разные способы решения этой задачи. Мы воспользуемся JWT (JSON Web Token). Для работы с JWT есть несколько готовых пакетов. Наиболее популярный — `jsonwebtoken`. Он поддерживает большое количество алгоритмов шифрования и можно сказать проверен временем. Однако, у него не очень удобный интерфейс. Он построен на функциях обратного вызова, колбеках. Можно самостоятельно сделать все необходимые обёртки, но мы пойдём немного другим путём. Воспользуемся более современным вариантом — jose. С точки зрения функциональности пакеты похожие. jose поддерживает меньше алгоритмов шифрования, но зато предоставляет удобный программный интерфейс. Воспользуемся им. При желании вы можете самостоятельно экспериментировать с jsonwebtoken. В нашем учебнике есть подробная статья на эту тему. Установим пакет jose в качестве основной зависимости. Типы поставляются из коробки, поэтому дополнительно их указывать не нужно.
При подготовке токена нам потребуется подготовить секрет — строка, которая будет использоваться в процессе шифрования. За время работы приложения она может меняться. Будем передавать её в переменной окружения. Заведём в конфигурации отдельную переменную окружения `JWT_SECRET`. Значения по умолчанию не будет (`null`). Затем добавим её в `.env`.
В классе `UserEntity` мы реализовали метод `setPassword` для формирования хеша на основе пароля. Пришло время добавить дополнительный метод, который сможет сравнить пароли. Идея в следующем. Чтобы проверить, что пользователь тот за кого себя выдаёт, мы запросим у него пароль. Полученный пароль хешируем с помощью функции `createSHA256`, а затем сравним его с хешем, который сохранён в базе. Если хэши паролей совпадают, значит пользователь ввёл корректный пароль и его можно пропускать дальше.
Для реализации проверки логина и пароля, а также формирования токена подготовим отдельный модуль `Auth`. В модуле предусмотрим методы * `authenticate`. Аргументом принимает `UserEntity`. Результатом становится токен. * `verify`. Выполняет аутентификацию. На вход принимает `LoginUserDto`. Результатом станет `UserEntity` или ошибка. В этом же коммите добавим константы `JWT_ALGORITHM` и `JWT_EXPIRED`. В первой зафиксируем алгоритм для формирования токена, а во второй время, в течение которого токен считается валидным. WIP: Задача в процесс решения.
…eption`. WIP При выполнении процедуры аутентификации могут возникнуть различные ошибки. Например, пользователь может быть не обнаружен в базе данных. Или его пароль неправильный. Это только два частых кейса, но их быть может быть много. Есть разные способы обработки подобных ситуаций. Методы сервиса `Auth` могут возвращать, например, значение `null`, если пользователь не найден. Можно обработать это значение где-то в месте обращения к серверу и подготовить соответствующий ответ клиенту. Можно пойти другим путём и описать отдельные виды ошибок. Определяем для каждой ситуации ошибки, бросаем их при наступлении определённого события и дальше где-нибудь их обрабатываем.
А как обработать отдельные ошибки? Те, что были созданы в предыдущем коммите. Можно расширить ранее созданный `AppExceptionFilter`. Мы можем добавить отдельные методы и обрабатывать по-своему новые виды ошибок. Однако, этот подход отразится на поддержке `AppExceptionFilter`. Он станет слишком большим. Выход из этой ситуации: создание отдельного `AuthExceptionFilter`. Разместим его в директории `Auth`.
Теперь подготовим инфраструктуру для нового модуля `Auth`. Соберём все необходимые зависимости в контейнере, внедрим нужные компоненты в `RestApplication`, зарегистрируем новый фильтр исключений, добавим компоненты в `Component` и так далее.
Теперь внедрим в качестве зависимости новый сервис в `UserController` и имплементируем обработчик `login`. Если всё прошло хорошо, то он вернёт новый токен. Проверить токен на корректность и посмотреть его содержимое вы можете на сайте https://jwt.io/. При тестировании попробуйте воспроизвести негативные сценарии: отправьте некорректные логин или пароль. `AuthExceptionFilter` должен перехватить ошибки.
Токены формируются. Теперь надо как-то извлечь информацию из `payload` токена. Это можно делать на уровне обработчика маршрута, но в этом случае, один и тот же код придётся дублировать в разных местах приложения. Чтобы этого избежать, создадим `ParseTokenMiddleware`, которую зарегистрируем глобально. Если к запросу будет приложен токен, то middleware сможет извлечь из него информацию и добавить к запросу. Начнём с извлечения информации. Сначала проверим наличие заголовка `authorization`. Если он есть, значит продолжаем работать. Затем извлечём сам токен. После этого проверим с помощью `jwtVerify`, Если всё ок, то сохраним в свойство запроса `user`. Таким образом, когда запрос долетит до обработчика маршрута, у нас будет вся необходимая информация.
Создадим файл `custom.d.ts` и расширим информацию о типе `Request`. Обновим конфигурационный файл `tsconfig.json`.
Теперь воспользуемся middleware и будем получать ID пользователя автоматически.
Токены работают, но по-прежнему есть проблема: клиент может обращаться ко всем маршрутам. Такого быть не должно. Некоторые маршруты априори приватны. Реализуем дополнительную проверку. Для этого создадим новый middleware — `PrivateRouteMiddleware` и в нём выполним проверку. Если в объекте запроса нет объекта `tokenPayload`, значит не авторизованы.
Последним шагом добавим `PrivateRouteMiddleware` в обработчике `create`. Теперь, маршрут для создания комментария стал приватным. К нему нельзя выполнить запрос без токена.
К обработчикам маршрутов, которые отвечают за создание данных подключим middleware `PrivateRouteMiddleware`. К этим ресурсам могут обращаться только авторизованные клиенты.
После добавления ограничений для маршрутов внесём исправления в обработчик создания нового объявления. Вместо ожидания идентификатора автора объявления от клиента, подставим его самостоятельно. Затем откроем `CreateOfferDto` и уберём валидатор с `userId`. Больше он не потребуется.
Фронтенду необходима возможность проверять актуальность JWT-токена и если токен актуален, возвращать информацию о пользователе. Сделать такой ресурс несложно. Добавим обработчик для маршрута `GET /login` в `UserController`.
Добавим запрос для ресурса `GET /login`, чтобы клиент мог проверить токен на актуальность.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
No description provided.