Spring Data JPA. Можно ли обновить объект с принципалом перед его сохранением в репозитории?

Я хотел бы защитить свой сервер ресурсов с помощью Spring Security 5, чтобы его могли использовать многие пользователи. У меня есть сущность Task, которая содержит поле userId.

@Entity
@Table(name = "tasks")
data class Task(
    @NotBlank
    val name: String,

    val description: String?,

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "task_id_seq")
    val id: Long = 0,

    @UUID //custom validation annotation
    val userId: String? = null
)

Я нашел способ использовать SpEL для внедрения принципала JWT внутрь запроса. Что я уже сделал, так это переопределил метод findAll() в моем interface TaskRepository : JpaRepository<Task, Long> с аннотацией @Query:

@Query("select t from Task t where t.userId = :#{principal.claims['sub']}")
//sub is a userId in OpenID Connect 1.0, claim is a Map<String, Object>
override fun findAll(): List<Task>

Проблема в том, что я хотел бы сделать что-то подобное с методом save(). Единственное, что я нашел, это обновить уже сохраненный объект. Я создал совершенно новый метод:

@Modifying
@Query("update Task t set t.userId = :#{principal.claims['sub']} where t.id = :id")
fun setUserId(@Param("id") id: Long)

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

@Transactional
fun createTask(taskDto: CreateTaskDto): Task {
    val task = taskRepository.save(taskDto.toEntity())
    taskRepository.setUserId(task.id)
    return task
}

Можно ли автоматически добавлять эту информацию каждый раз, когда я сохраняю объект в своей базе данных? Тогда мой код внутри TaskService будет выглядеть так:

@Transactional
fun createTask(taskDto: CreateTaskDto): Task {
    return taskRepository.save(taskDto.toEntity())
}

Почему бы не внедрить принципала/аутентификацию в RestController и не предоставить его службе, а службе разрешить проверку подлинности?

IEE1394 01.05.2019 22:52

Это самое простое, но самое раздражающее решение этой проблемы. Каждая конечная точка должна будет добавить принципала в качестве параметра, и это необходимо для дальнейшего прохождения. Во-вторых, мне нужно было бы везде использовать некоторый класс Util, чтобы получить идентификатор пользователя. Когда я делаю это на уровне JpaRepository, я думаю, что это самое элегантное и наиболее подходящее решение, потому что я имею дело с предоставлением авторизации на уровне данных. Сервис и RestController ничего не знают о пользователе, и код намного чище.

mregulski 01.05.2019 23:18

мне нравится четкий и понятный код, даже если писать надоедает ;-)

IEE1394 01.05.2019 23:32

Вы предполагаете, что, по вашему мнению, мое решение менее понятно, чем каждый раз передавать Principal внутри методов обслуживания?

mregulski 01.05.2019 23:52

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

IEE1394 02.05.2019 01:25
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
585
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Решение на самом деле довольно простое, но я не знал, что ищу. Тем не менее, я не знаю, является ли это каноническим решением. Для этого я использую Jpa Auditing. Во-первых, я добавил аннотацию @CreatedBy к полю, которое я хотел обновить перед вставкой внутри моей сущности. Класс сущности, который я также пометил @EntityListeners(AuditingEntityListener::class). Обратите внимание, что было необходимо изменить ключевое слово val на var (с поля final на неконечное поле в мире Java).

@Entity
@Table(name = "tasks")
@EntityListeners(AuditingEntityListener::class)
data class Task(
        @NotBlank
        val name: String,

        val description: String?,

        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        @SequenceGenerator(name = "task_id_seq")
        val id: Long = 0,

        @UUID
        @CreatedBy
        var userId: String? = null
    )

Следующим шагом было реализовать interface AuditorAware<T> и сделать его видимым для Spring с помощью @Component (или создать bean-компонент внутри класса конфигурации)

@Component
class SpringSecurityAuditorAware : AuditorAware<String> {

    override fun getCurrentAuditor(): Optional<String> {
        return Optional.ofNullable(SecurityContextHolder.getContext()?.authentication)
                .filter { authentication -> authentication.isAuthenticated }
                .map { authentication -> authentication.principal as Jwt }
                .map { jwt -> jwt.claims["sub"] as String? }
    }

}

И последнее, но не менее важное: добавление @EnableJpaAuditing в некоторый класс конфигурации:

@Configuration
@EnableJpaAuditing
class ResourceServerContext

Другие вопросы по теме