【发布时间】:2014-12-25 21:44:51
【问题描述】:
TL;DR:如何在 Spring-Data-Rest 提供的扁平化 REST api 方法中实现细粒度的访问控制?
所以 - 我正在使用 Spring-Data-Rest 创建一个 API,其中有三个主要访问级别:
1) 管理员 - 可以查看/更新所有组
2) 群组的所有者 - 可以查看/更新群组及其下的所有内容
3) 子组的所有者 - 只能查看/更新他的组。没有递归嵌套,只允许一个子级。
“组”作为资源公开(有一个 crud 存储库)。
到目前为止一切顺利 - 我已经使用存储库事件处理程序实现了一些修改访问控制 - 所以在创建/写入/删除方面我认为我很好。
现在我需要限制某些项目的可见性。这对于获取单个项目是可以的,因为我可以使用 Pre/Post Authorize 注释并引用主体。
问题在于 findAll() 方法——我没有一个简单的钩子来根据当前主体过滤掉我不想暴露的特定实例。例如 - 子组所有者可以通过执行 GET /groups 查看所有组。理想情况下,他们应该让他们无权访问的项目甚至根本不可见。
对我来说,这听起来像是在存储库接口上编写自定义 @Query() 注释,但这似乎不可行,因为:
我需要在查询中引用主体。应该支持 SPeL,但似乎根本不适用于 ?# 表达式(尽管这篇博客文章另有建议:https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions)。我一般使用带有 1.1.8.RELEASE 的 spring-boot 和 Evans-RELEASE 训练来获取 spring-data。
我需要编写的查询类型将根据访问级别而有所不同,这实际上不能包含在单个 JPQL 语句中(如果管理员选择所有组,否则获取所有(子)与主体用户关联的组)。
因此,听起来我需要为此编写一些自定义存储库实现,并在代码中引用主体。没关系 - 但对于我需要控制访问的每个存储库来说,似乎需要做很多工作(我认为这几乎是所有存储库)。这适用于 findAll 和各种自定义搜索方法。
我是不是搞错了?是否有另一种方法可以更好地根据当前登录的用户动态限制项目可见性?在像 spring-data-rest 暴露这样的平面命名空间中,我想这将是一个常见问题。
在之前的设计中,我只是通过暴露 /api/groups/{groupId}/... 下的所有内容来解决这个问题,并让一个子资源定位器作为一个单一的夹点来控制对其下任何内容的访问。在 spring-data-rest 中没有这样的运气。
更新:现在遇到一个覆盖 findAll() 的自定义方法(这适用于我的自定义接口上定义的其他方法)。虽然这可能是一个单独的问题 - 我现在被阻止了。当我执行 GET /groups 时,Spring-data 只是不调用它,而是调用原始数据。奇怪的是,如果我在接口上定义一个并用@Query 标记它,它确实会使用我的查询(也许不再支持内置方法的自定义覆盖?)。
public interface GroupRepository extends JpaRepository<Group, Long>, GroupCustomRepository {}
public interface GroupCustomRepository {
Page<Group> findAll(Pageable pageable);
}
public class GroupCustomRepositoryImpl extends SimpleJpaRepository<Group, Long> implements GroupCustomRepository {
@Inject
public GroupCustomRepositoryImpl(EntityManager em) {
super(Group.class, em);
}
@Override
public Page<Group> findAll(Pageable pageable) {
MyPrincipal principal = (MyPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Page<Group> result;
if (principal.isAdmin()) {
result = findAll(pageable);
} else {
Specification<Group> spec = (root, query, cb) -> cb.or(
cb.equal(root, principal.getGroup()),
cb.and(cb.isNotNull(root.get(Group_.parentGroup)), cb.equal(root.get(Group_.parentGroup), principal.getGroup()))
);
result = findAll(spec, pageable);
}
return result;
}
}
更新 2:由于我无法访问 @Query 中的主体,也无法使用自定义方法覆盖它,因此我陷入了困境。 @PostFilter 也不起作用,因为返回对象是 Page 而不是集合。
我决定只将 /groups 隔离给管理员,并让其他所有人使用 @PostFilters/@PostAuthorizations 的不同方法(/groups/search/somethingSpecific)。
这似乎与 HAL 方法不太吻合。对其他人如何使用 Spring-data-rest 解决此类问题感兴趣。
【问题讨论】:
-
你不能用
@PostFilter吗?顺便说一句,findAll方法不止一种。覆盖一个可能还不够。 -
试过 :) @PostFilter 不适用于 Pages - 无论如何都会抛出页面逻辑(并且可能性能很差)。看起来你只应该覆盖 findAll() 之一 - 否则它将它们视为不明确的路径。如果我只是覆盖其中一种方法,我不确定我什至可以同时支持排序和分页。
-
你们都找到解决这个@DaveLeBlanc 的方法了吗?
标签: spring rest spring-security spring-data-jpa spring-data-rest