【问题标题】:Spring Ownership based access基于 Spring Ownership 的访问
【发布时间】:2020-05-28 18:08:32
【问题描述】:

请不要将此标记为重复,因为提供的可用解决方案包含安全问题并且不涵盖所有 CRUD 案例!

我想通过将对象的存储userIdSecurityContextHolder.getContext().authentication 进行比较来保护某些对象。

我知道我必须在执行操作之前获取对象,因为这是获取 userId 的唯一方法。

我想保护像这样的控制器方法:

@GetMapping("/deliveryAddresses/{id}")
    fun getDeliveryAddressById(@PathVariable(value = "id") deliveryAddressId : Long): 
        ResponseEntity<BasicDeliveryAddress> {
            return deliveryAddressService.findById(deliveryAddressId)?.let { ResponseEntity.ok(it) } 
                ?: ResponseEntity.notFound().build()
    }

保护此对象的最佳实践方法是什么?我不想在我的代码中进行多次重复的所有权检查。另外请建议在哪里添加检查(控制器,服务,...?)

这正是我想要避免的(伪代码):

fetchById(id) {
    val myObject = ...
    if(myObject.userId == authUser.userId) {
        ...
    }
}

请为此问题提供一个干净的解决方案。

更新 - 解决方案的一部分

我不知道我可以通过仅传递 ID 来直接获取方法头中的对象。

@GetMapping("/something/{id}")
fun getSomethingByID(@PathVariable(value = "id") something: Something): ResponseEntity<BasicDeliveryAddress> { ... }

有了这些附加信息和@Ken Chan 提供的答案(已接受),您应该能够轻松实现基于所有权的安全性!

【问题讨论】:

标签: spring kotlin spring-security


【解决方案1】:

使用@PreAuthorize,它允许您定义一个SpEL,它将被评估为布尔值,以查看是否允许执行方法。

您有多种选择:

(1) 使用 SpEL 引用执行检查的 bean 方法:

@PreAuthorize("@authzService.isAllowToDo(#deliveryAddressId)")
public ResponseEntity<BasicDeliveryAddress> getDeliveryAddressById(Long deliveryAddressId {

}


@Service
public class AuthzService{

    public boolean isAllowToDo(Long deliveryAddressId){
        //Do the checking here....
    }
}

(2) 使用内置的hasPermission 表达式:

@PreAuthorize("hasPermission(#deliveryAddressId, 'read')")
public ResponseEntity<BasicDeliveryAddress> getDeliveryAddressById(Long deliveryAddressId{

}

它需要自定义PermissionEvaluator 才能工作。与 (1) 相同的想法,但它是一个内置的解决方案。

(3) 如果求值逻辑简单,方法签名为 允许,你可以直接用SpEL表示:

@PreAuthorize("#deliveryAddress.userId == authentication.userId")
public ResponseEntity<BasicDeliveryAddress> getDeliveryAddressById(BasicDeliveryAddress deliveryAddress){

}

authentication 是访问SecurityContextHolder 中的Authentication 对象的内置表达式之一,我假设您已经对其进行了自定义以包含userId

【讨论】:

  • 这意味着我需要在第一次调用中获取deliveryAddress 两次,对吗?检查权限,然后直接在getDeliveryAddressById。第三种解决方案:这意味着我首先需要获取BasicDeliveryAddress,然后将其传递给方法?这让我很困惑,因为这种方法有望做到这一点。或者第三个解决方案如何工作,因为我只传递了 ID 而不是整个对象。
  • 如果您使用的是 JPA ,并且您通过 ID 查询要处理的对象,则该对象将被缓存在 JPA 一级缓存中以用于同一事务。这意味着在同一事务中的第二次,它只是从缓存中返回对象。
  • 我可以在 mysql 日志中看到查询被执行了两次,所以它看起来不像它的缓存。
  • 您必须使用 entityManager.find(Foo.class, 1) 才能使用一级缓存。使用不使用 JPQL 。如果使用 JPQL 获取结果,使用查询缓存可能会有所帮助
猜你喜欢
  • 2017-10-19
  • 2018-05-18
  • 2019-11-14
  • 2016-03-27
  • 2019-04-24
  • 2019-08-08
  • 2018-11-28
  • 1970-01-01
  • 2015-11-07
相关资源
最近更新 更多