如何在Spring-Data-Rest中实现细粒度的访问控制?

时间:2014-10-30 00:28:02

标签: spring rest spring-security spring-data-jpa spring-data-rest

TL; DR:如何在Spring-Data-Rest为我们提供的扁平REST api方法中实现细粒度的访问控制?

所以 - 我正在使用Spring-Data-Rest创建一个API,其中包含三个主要访问级别:

1)管理员 - 可以查看/更新所有组

2)群组的所有者 - 可以查看/更新群组及其下的所有内容

3)子组的所有者 - 只能查看/更新他的组。没有递归嵌套,只允许一个子级别。

' group'作为资源公开(有一个crud存储库)。

到目前为止一直很好 - 我已经使用Repository Event Handler实现了一些访问控制以进行修改 - 所以在创建/写入/删除方面,我认为我很好。

现在我需要限制某些项目的可见性。这对于获得单个项目是可以的,因为我可以使用Pre / Post Authorize注释并引用主体。

问题在于findAll()方法 - 我没有一个简单的钩子来过滤掉我不希望根据当前主体暴露的特定实例。例如,子组所有者可以通过执行GET /组来查看所有组。理想情况下,他们应该拥有他们根本无法访问的项目。

对我而言,这听起来像是在存储库接口上编写自定义的@Query()注释,但这似乎并不可行,因为:

  • 我需要在查询中引用主体。 SPeL应该得到支持,但似乎并没有使用?#表达式(尽管这篇博客文章提示不然:https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions)。我通常使用带有1.1.8.RELEASE和Evans-RELEASE列的spring-boot来获取弹簧数据。

  • 我需要编写的查询类型将根据访问级别的不同而不同,访问级别实际上不能包含在单个JPQL语句中(如果管理员选择所有组,则获取全部( sub)与主要用户相关联的组。

因此,我觉得我需要为此编写一些自定义存储库实现,并在代码中引用主体。那很好 - 但是对于我需要控制访问权限的每个存储库来说似乎需要做很多工作(我认为这几乎就是所有存储库)。这适用于findAll和各种自定义搜索方法。

我接近这个错误吗?是否有另一种方法可以根据当前登录的用户动态限制项目可见性,以便更好地工作?在像spring-data-rest暴露的平面命名空间中,我认为这将是一个常见的问题。

在先前的设计中,我刚刚通过公开/ api / groups / {groupId} / ...下的所有内容来解决它,并让子资源定位器充当单个夹点来控制对其下任何内容的访问。在spring-data-rest中没有这样的运气。

更新:现在绊倒了一个覆盖findAll()的自定义方法(这适用于我自定义界面上定义的其他方法)。虽然这可能是一个单独的问题 - 我现在被阻止了。当我执行GET /组时,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而不是一个集合。

我决定只关闭/群组给管理员,让其他人使用@ PostFilters / @ PostAuthorizations的不同方法(/ groups / search / somethingSpecific)。

尽管如此,它似乎与HAL方法非常吻合。感兴趣的是其他人如何使用Spring-data-rest解决这些问题。

1 个答案:

答案 0 :(得分:3)

我们最终接近以下内容:

  • 我们创建了一个自定义方面,它位于存储库上的CRUD方法之前。然后它会查找并调用相关的授权处理程序&#39;在存储库上注释,动态管理授权详细信息。

  • 当涉及到限制结果的findAll()查询时(例如:查看/用户),我们必须非常苛刻 - 实质上,只有管理员可以列出所有敏感的内容。否则,有限的用户必须对特定项目使用查询方法。

  • 我们创建了一些可重用的与授权相关的类,并在某些场景中使用它们 - 尤其是自定义查询,例如:

    @PreAuthorize(&#34; @ authorizations.systemAdminRead()&#34) @Query(&#34;选择你来自用户r,其中......&#34;) 列出findAll();

    @PostAuthorize(&#34; @ otherAuthorizationHandler.readAllowed(returnObject)&#34;)ResponseObject someQuery();

总而言之,它有效 - 但感觉非常笨重,很容易错过。我希望这更多地融入到框架中,即使能够动态调整默认查询也是有用的(当我尝试这个时,我无法使用@Query适当地更新查询)。

我们碰巧正在使用PostgreSQL,因此即将推出的行级别安全性(http://michael.otacoo.com/postgresql-2/postgres-9-5-feature-highlight-row-level-security/)可以很好地满足要求,假设我们可以通过数据库连接向其提供适当的授权详细信息。