如何在 Spring-Data-Rest 中实现细粒度的访问控制?
Posted
技术标签:
【中文标题】如何在 Spring-Data-Rest 中实现细粒度的访问控制?【英文标题】:How do I implement fine-grained access control in Spring-Data-Rest? 【发布时间】: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 的方法了吗?
【参考方案1】:
我们最终以如下方式处理这个问题:
我们创建了一个自定义方面,它位于存储库中 CRUD 方法的前面。然后它查找并调用关联的“授权处理程序”,该处理程序在动态管理授权详细信息的存储库上进行注释。
在限制 findAll() 查询中的结果(例如:查看 /users)时,我们不得不非常严厉——本质上,只有管理员可以列出所有敏感信息。否则,受限用户必须对特定项目使用查询方法。
我们创建了一些可重用的授权相关类,并在某些场景中使用这些类 - 特别是自定义查询,例如:
@PreAuthorize("@authorizations.systemAdminRead()") @Query("select u FROM User r where ...") 列出 findAll();
@PostAuthorize("@otherAuthorizationHandler.readAllowed(returnObject)") ResponseObject someQuery();
总而言之,它有效 - 但感觉非常笨重,而且很容易错过一些东西。我确实希望这更多地融入到框架中,即使能够动态调整默认查询也会很有用(当我尝试这样做时,我无法使用 @Query 适当地更新查询)。
我们碰巧使用的是 PostgreSQL,因此即将推出的行级安全性 (http://michael.otacoo.com/postgresql-2/postgres-9-5-feature-highlight-row-level-security/) 将非常符合要求,假设我们可以通过数据库连接为其提供适当的授权详细信息。
【讨论】:
以上是关于如何在 Spring-Data-Rest 中实现细粒度的访问控制?的主要内容,如果未能解决你的问题,请参考以下文章
如何配置 Spring-Data-Rest 以仅返回登录用户拥有的数据
如何将 spring-data-rest 与 spring websocket 混合到一个实现中
如何在 spring-data-rest 中将 Page<ObjectEntity> 映射到 Page<ObjectDTO>