Аутентификация с помощью токенов стала обязательной функцией для современных веб-приложений. Он изначально подходит для одностраничных приложений и мобильных приложений. В то время как Spring де-факто является стандартом в экосистеме Java, есть разработчики, которые предпочитают использовать альтернативные решения. Это верно, потому что Spring – довольно тяжелая платформа, и существует ряд легких и быстрых микро-фреймворков. Javelin – популярный выбор – он начался с хорошо известного проекта Spark Java. Однако микро-фреймворки предлагают разработчикам набор инструментов barre, часто ограниченный обработкой HTTP-запросов/ответов. Все остальные функции, такие как безопасность, выходят за рамки. Это означает, что мы должны реализовать это вручную.
В этой статье представлен обзор реализации аутентификации на основе токенов (JWT authentication) для REST API, написанной с помощью Javalin microframework. Существует решение , но это может быть не лучшим вариантом – в частности, если вам нужна большая гибкость или вам нужно иметь больше контроля над аутентификацией. Для этого вам может быть интересно продолжить чтение этого поста.
Архитектура приложений
В этом посте мы рассмотрим простое приложение для управления задачами. Он организован в виде REST API и имеет две основные группы конечных точек: auth (регистрация/вход в систему) и задачи. защищенная/задачи конечная точка защищена с помощью JWT – пользователь должен прикрепить заголовок Authorization с действительным токеном и идентификатором пользователя. Я не буду подробно описывать каждый компонент, так как вы сами можете получить доступ к полному исходному коду в это репозиторий github . Вместо этого мы сосредоточимся на том, как работать с токенами и внедрять конечные точки аутентификации. Взгляните на приведенный ниже график, который демонстрирует архитектуру приложения:
Как я уже упоминал ранее, в этой статье мы рассмотрим:
- UserController компонент (обрабатывает HTTP-запросы)
- Пользовательский сервис и его реализация (содержит бизнес-логику, связанную с потоком аутентификации)
- Менеджер токенов и его реализация (компонент, который генерирует и утверждает токены).
Генерировать токены
В этом посте мы будем использовать библиотеку jjwt для работы с токенами. Альтернативным решением является использование на Nimbus Jose-JWT – вы можете узнать больше в моем посте о двухфакторной аутентификации для Webflux , где я ее использовал. Чтобы генерировать токены, нам сначала нужно иметь секретный ключ. Обычно ваше приложение считывает его из конфигурации, но в примере мы можем полагаться на генерируемое значение:
public JjwtTokenManagerImpl() {
this.key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
}
Процесс выпуска новых токенов очень прост с помощью jjwt library. Все, что вам нужно, это указать те параметры, которые вы хотите включить в полезную нагрузку токена. В простейшем случае это будет просто userId значение, хотя вы можете добавить другие утверждения :
@Override
public String issueToken(String userId) {
String token = Jwts.builder().setSubject(userId).signWith(key).compact();
return token;
}
Проверка токенов
Чтобы получить доступ к защищенным маршрутам, пользователь должен предоставить два заголовка: токен и идентификатор пользователя. Следовательно, Token Manager.authorize() метод отвечает за утверждение токена.
@Override
public boolean authorize(String token, String userId) {
try {
String subject = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getSubject();
return subject.equalsIgnoreCase(userId);
} catch (Exception ex){
throw new ForbiddenResponse();
}
}
Регистрация
Сначала нам нужно создать пользователя, прежде чем он/она сможет получить доступ к API-интерфейсам задач. Для этого клиент отправляет электронное письмо и пароль на конечную точку /регистрация . Здесь нам нужно выполнить следующие шаги:
- Получить адрес электронной почты и пароль из тела запроса ( Запрос авторизации )
- Создайте нового Пользователя с электронной почтой и паролем
- Сохранить объект пользователя в базе данных
- Получить Идентификатор пользователя
- Выдать новый токен для этого пользователя
- Возврат клиенту успешен authResponse объект со значением токена и идентификатора пользователя
Для части контроллера нам нужно реализовать шаги 1 и 6, в то время как шаги 2-5 обрабатываются службой. Взгляните на фрагмент кода, который содержит код UserController.signup() flow:
public void signup (Context context){
AuthRequest request = context.bodyAsClass(AuthRequest.class);
AuthResponse result = service.signup(request);
context.json(result);
}
Обратите внимание, что Javelin предоставляет объект Context для абстрактной работы с HTTP-запросами/ответами. В нем также есть такие методы, как body As Class() для сериализации тела JSON в Java entity и json() для отправки ответа с данными JSON. Далее давайте проверим, как выполнить шаги 2-5 в UserServiceImpl реализация. Вот код:
@Override
public AuthResponse signup(AuthRequest request) {
String email = request.getEmail();
String password = request.getPassword();
User user = repository.signup(email, password);
String userId = user.getUserId();
String token = manager.issueToken(userId);
AuthResponse response = new AuthResponse(userId, token);
return response;
}
Теперь вы можете запустить приложение и проверить, работает ли процесс регистрации:
Авторизоваться
После того, как мы создали новую запись пользователя в базе данных, мы можем выполнить вход в систему. С технической точки зрения, мы должны реализовать эти шаги:
- Получить адрес электронной почты и пароль из тела запроса ( Запрос авторизации )
- Найти в базе данных a Пользователь с тем же адресом электронной почты
- В случае, если этот пользователь действительно существует – подтвердите что пароли действительно совпадают
- Выдать токен
- Возврат клиенту успешен authResponse объект со значением токена и идентификатора пользователя
- Если пользователь не существует или пароли не совпадают – верните authResponse с
Опять же, мы разделяем процесс на две части: контроллер задерживает HTTP-запрос/ответ и службу для выполнения бизнес-логики. Взгляните на реализацию контроллера ниже:
public void login (Context context){
AuthRequest request = context.bodyAsClass(AuthRequest.class);
AuthResponse result = service.login(request);
context.json(result);
}
Как правило, код контроллера выглядит аналогично предыдущему, поскольку он выполняет только обработку запросов/ответов. Шаги 2-4 определены в части сервиса. Вот как это делается:
@Override
public AuthResponse login(AuthRequest request) {
String email = request.getEmail();
String password = request.getPassword();
Optional result = repository.findByEmail(email);
if (result.isPresent()){
User user = result.get();
String passwordInDatabase = user.getPassword();
if (password.equalsIgnoreCase(passwordInDatabase)) {
String userId = user.getUserId();
String token = manager.issueToken(userId);
AuthResponse response = new AuthResponse(userId, token);
return response;
} else {
throw new ForbiddenResponse();
}
} else {
throw new ForbiddenResponse();
}
}
Взгляните на этот фрагмент кода. Вы могли бы отметить, что если пользователь не представлен или был введен неправильный пароль, приложение прерывает поток с помощью ForbiddenResponse . Это особый тип исключений из Javelin, которые сопоставляются с кодами ответов HTTP и упрощают разработку. Например, давайте попробуем войти в систему пользователя с неправильным паролем:
Наконец, мы можем выполнить тестирование , чтобы убедиться, что процесс входа в систему работает так, как мы планировали. Позвольте вызвать login конечную точку с действительными учетными данными:
Все запущено и работает! Вот и все для этого поста. Вы можете найти полный исходный код, включая реализации защищенных конечных точек, в этом репозитории github . Не стесняйтесь экспериментировать с ним. Если у вас есть вопросы относительно этого поста, вы можете задать их в комментариях ниже или связаться со мной.
Оригинал: “https://dev.to/iuriimednikov/home-made-jwt-authentication-for-javalin-2pen”