【发布时间】: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