【问题标题】:Spring Data JPA - Is it possible to update an entity with principal before persisting it inside a repository?Spring Data JPA - 是否可以在将实体持久保存在存储库中之前使用主体更新实体?
【发布时间】:2019-09-20 08:10:52
【问题描述】:

我想用 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 主体的方法。我已经做的是我在interface TaskRepository : JpaRepository<Task, Long> 中用@Query 注释覆盖了findAll() 方法:

@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 中注入 Principal/Authentication 并提供给服务,让服务处理身份验证?
  • 这是解决此问题的最简单但最烦人的解决方案。每个端点都需要添加 Principal 作为参数,并且需要进一步传递。其次,我需要在任何地方使用一些 Util 类来获取 userId。当我在 JpaRepository 级别执行此操作时,我认为这是最优雅且最合适的解决方案,因为我正在处理数据级别的授权授予。 Service 和 RestController 对用户一无所知,代码更简洁。
  • 我喜欢清晰易懂的代码,即使写起来很烦人;-)
  • 您是否建议在您看来我的解决方案不如每次传递 Principal 内部服务方法清晰?
  • 至少我认为“烦人”不是解决方案好坏的论据。在我的情况下,我每次都将主体传递给服务方法,因为如果你阅读了你知道的关于身份验证的方法,那么数据层因此非常愚蠢..另一方面是我还不知道的审计功能,阅读后看起来非常有用。但是你必须知道这一点 - 这可能是一些风险

标签: java spring kotlin spring-security spring-data-jpa


【解决方案1】:

解决方案实际上很简单,但我不知道我在寻找什么。不过,我不知道这是否是一个规范的解决方案。我为此使用 Jpa 审计。首先,我将@CreatedBy 注释添加到我想要在插入我的实体之前更新的字段。我还用@EntityListeners(AuditingEntityListener::class) 注释的实体类。请注意,需要将 val 关键字更改为 var(在 Java 世界中从 final 字段更改为非最终字段)。

@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&lt;T&gt;,并通过使用@Component(或在配置类中创建一个bean)使其对Spring可见

@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

【讨论】:

    猜你喜欢
    • 2013-03-25
    • 2013-05-09
    • 2019-11-03
    • 2018-04-20
    • 1970-01-01
    • 2022-11-29
    • 1970-01-01
    • 2018-05-01
    • 1970-01-01
    相关资源
    最近更新 更多