- Вопрос или проблема
- Сценарий использования 1: Это работает
- Сценарий использования 2: НЕ работает
- Вопрос
- Ответ или решение
- 1. Обеспечение поддержки инъекции для всех конечных точек
- 2. Изменение обработки аннотации на уровне метода
- 3. Пример класса ресурса с корректной инъекцией
- 4. Настройка стиля CDI
- 5. Тестирование и оптимизация
- Заключение
Вопрос или проблема
В нашем приложении мы используем Quarkus и расширение SmallRye JWT (см. здесь) для аутентификации и авторизации на основе ролей.
Мы определили аннотацию CurrentUser
:
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CurrentUser
Аннотация имеет конкретную реализацию в UserProvider
:
import ---.User
import ---.UserRepository
import jakarta.enterprise.context.RequestScoped
import jakarta.enterprise.inject.Produces
import org.eclipse.microprofile.jwt.Claim
@RequestScoped
class UserProvider(
private val userRepository: UserRepository,
@Claim("upn") private val currentUserEmail: String?
) {
@Produces
@CurrentUser
fun currentUser(): User? {
return if (currentUserEmail != null) {
userRepository.findByEmail(currentUserEmail)
} else {
null
}
}
}
Как вы можете легко увидеть, UserProvider
загружает User
из базы данных на основе утверждения upn
, найденного в потенциальном JWT.
Сценарий использования 1: Это работает
В следующем сценарии аннотация работает хорошо:
@Path("auditions")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@PermitAll
class AuditionResource(
@CurrentUser private val currentUser: User?
) {
// Конечные точки
}
Как видите, когда мы внедряем текущего пользователя в каждом запросе, аннотация @CurrentUser
работает как задумано.
Сценарий использования 2: НЕ работает
Тем не менее, это может стать проблемой производительности, так как не каждая конечная точка всегда требует текущего пользователя, загруженного из базы данных. Поэтому мы хотели РАССШИРИТЬ использование @CurrentUser
, чтобы поддерживать более детальную основу, например, следующим образом:
@Path("auditions")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@PermitAll
class AuditionResource {
@PATCH
fun foo(
@CurrentUser currentUser: User?
) {
println("HI")
}
}
К сожалению, в этом случае метод currentUser()
класса UserProvider
НЕ вызывается.
Вопрос
Как мы можем добиться поддержки как сценария использования 1, так и сценария использования 2?
Ответ или решение
Чтобы обеспечить поддержку инъекции текущего пользователя на уровне каждого эндпоинта в вашем приложении, использующем Quarkus и SmallRye JWT, нам необходимо правильно реализовать механизм инъекции, позволяющий использовать аннотацию @CurrentUser
как на уровне класса ресурса, так и в отдельных методах. Ниже приведены решения для обеспечения корректной работы как в первом, так и во втором случаях.
1. Обеспечение поддержки инъекции для всех конечных точек
Сначала обеспечим, чтобы аннотация @CurrentUser
работала как для инъекций на уровне класса, так и для методов. Для этого необходимо убедиться, что способ получения текущего пользователя из базы данных адаптирован и для случаев, когда он инжектируется непосредственно в метод. Класс UserProvider
остается без изменений, поскольку он уже корректно загружает пользователя на основе upn
клайма:
@RequestScoped
class UserProvider(
private val userRepository: UserRepository,
@Claim("upn") private val currentUserEmail: String?
) {
@Produces
@CurrentUser
fun currentUser(): User? {
return if (currentUserEmail != null) {
userRepository.findByEmail(currentUserEmail)
} else {
null
}
}
}
2. Изменение обработки аннотации на уровне метода
Чтобы аннотация @CurrentUser
работала в методах, необходимо настроить контекст CDI таким образом, чтобы инъекция выполнялась при вызове метода. В Quarkus/SmallRye автоматически не происходит вызова метода currentUser()
в зависимости от контекста вызова. Для достижения этого эффекта, необходимо использовать механизм @Produces непосредственно в методах вашего класса ресурса, например, через специальный синтаксис, поддерживающий CDI.
3. Пример класса ресурса с корректной инъекцией
Рассмотрим, как это может быть выполнено:
@Path("auditions")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@PermitAll
class AuditionResource {
@Inject
@CurrentUser
lateinit var currentUser: User?
@PATCH
fun foo() {
println("Текущий пользователь: ${currentUser?.email ?: "неизвестен"}")
// Логика метода с использованием currentUser
}
}
4. Настройка стиля CDI
Вот как вы можете более гибко внедрять пользователя в отдельные методы, применяя CDI:
@PATCH
fun foo(@CurrentUser currentUser: User?) {
if (currentUser != null) {
println("Текущий пользователь: ${currentUser.email}")
// Логика с использованием текущего пользователя
} else {
println("Текущий пользователь не найден")
}
}
5. Тестирование и оптимизация
После внесения изменений в код, проведите тестирование:
- Убедитесь, что пользователь загружается корректно как в методах, так и на уровне класса.
- Испытайте сценарии, когда текущий пользователь не определен, чтобы гарантировать, что приложение корректно обрабатывает такие ситуации.
Заключение
Инъекция текущего пользователя по усмотрению на уровне каждого метода – это удачное решение для оптимизации работы с пользователями и повышения производительности приложения на Quarkus. Описанные действия обеспечат гибкое и эффективное использование вашего метода инъекции @CurrentUser
, что позволит избежать избыточных запросов к базе данных в тех случаях, когда это не требуется.