Внедрять текущего пользователя (загруженного из БД) на основе каждого конечного пункта.

Вопрос или проблема

В нашем приложении мы используем 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, что позволит избежать избыточных запросов к базе данных в тех случаях, когда это не требуется.

Оцените материал
Добавить комментарий

Капча загружается...