Spring Data JPA 和 spring-security:数据库级别的过滤器(尤其是分页)

Posted

技术标签:

【中文标题】Spring Data JPA 和 spring-security:数据库级别的过滤器(尤其是分页)【英文标题】:Spring Data JPA and spring-security: filter on database level (especially for paging) 【发布时间】:2013-02-13 01:12:57 【问题描述】:

我正在尝试使用注释和 spring-security 为我的开源项目添加方法级别的安全性。我现在面临的问题是 findAll 方法,尤其是用于分页的方法(例如返回页面)。

使用@PostFilter 对列表有效(但我个人认为在应用程序而不是数据库中过滤不是一个好主意),但在分页查询上完全失败。

这是有问题的,因为我有一个包含 List<Compound> 的实体。复合有不同的实现,用户可能只有读取其中一个复合的权限。 Compound 使用TABLE_PER_CLASS 继承。存储库实现QueryDslPredicateExecutor

我的想法是为每个查询添加一个谓词,以根据当前用户限制返回结果。但是我有点迷失了a)用户和角色的数据模型应该如何看待以及b)然后如何创建谓词(一旦定义了模型,这可能很容易)。或者 querydsl 是否已经提供基于类型的过滤(在查询类中包含的元素上)?

【问题讨论】:

问题 A 见User Schemasection 我的意思是我可以调整查询以考虑当前用户的角色,例如。从给定实体到角色以及从角色到用户必须存在关系。 然后查看 ACL static.springsource.org/spring-security/site/docs/3.2.x/… 和适当的 DB 架构 static.springsource.org/spring-security/site/docs/3.2.x/… 【参考方案1】:

目前尚无此类支持,但我们已将其列入路线图。您可能想关注DATACMNS-293 以了解总体进展。

【讨论】:

【参考方案2】:

暂时想出了以下解决方案。由于我的项目相当简单,这可能不适用于更复杂的项目。

    用户可以读取某个类的全部或不读取任何实体

因此,任何查询方法都可以使用包含hasRole@PreAuthorize 进行注释。

我的项目中的 Container 实体是个例外。它可以包含Compound 的任何子类,用户可能无权查看所有子类。它们必须是过滤器。

为此,我创建了一个 UserRole 实体。 CompoundRole 具有 OneToOne 关系,并且该角色是该 Compound 的“read_role”。 UserRole 具有多对多关系。

@Entity
public abstract class Compound     
    //...
    @OneToOne    
    private Role readRole;
    //...   

我所有的存储库都实现了QueryDSLPredicateExecutor,这在这里变得非常有用。我们只在服务层创建它们并使用repositry.findAll(predicate)repository.findOne(predicate),而不是在存储库中创建自定义 findBy-methods。谓词包含实际的用户输入+“安全过滤器”。

@PreAuthorize("hasRole('read_Container'")
public T getById(Long id)         
    Predicate predicate = QCompoundContainer.compoundContainer.id.eq(id);
    predicate = addSecurityFilter(predicate);
    T container = getRepository().findOne(predicate);        
    return container;


private Predicate addSecurityFilter(Predicate predicate)        
    String userName = SecurityContextHolder.getContext().getAuthentication().getName();            
    predicate = QCompoundContainer.compoundContainer.compound.readRole
        .users.any().username.eq(userName).and(predicate);        
    return predicate;

注意:QCompoundContainer 是 QueryDSL 生成的“元模型”类。

最后你可能需要初始化从ContainerUser的QueryDSL路径:

@Entity
public abstract class CompoundContainer<T extends Compound> 
    //...
    @QueryInit("readRole.users") // INITIALIZE QUERY PATH
    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL,
            targetEntity=Compound.class)
    private T compound;
    //...

省略最后一步可能会导致NullPointerException

进一步提示:CompoundService 在保存时自动设置角色:

if (compound.getReadRole() == null) 
    Role role = roleRepository.findByRoleName("read_" + getCompoundClassSimpleName());
    if (role == null) 
        role = new Role("read_" + getCompoundClassSimpleName());
        role = roleRepository.save(role);
    
    compound.setReadRole(role);

compound = getRepository().save(compound)

这行得通。缺点有点明显。同一个 Role 与同一个 Compound 类实现的每个实例相关联。

【讨论】:

【参考方案3】:

8 年后,仍然没有开箱即用的解决方案。因此,我创建了支持将安全条件注入所有 JPA 存储库操作的库,包括:

基本操作(findOnefindAllsavedelete) 具有规范的查找器 (findAll(Specification, ...)) querydsl 查找器(用于 Spring WEB 和 Spring Data REST) 特定于用户的查找器 (findByValue)

https://github.com/vlsergey/spring-data-entity-security

【讨论】:

以上是关于Spring Data JPA 和 spring-security:数据库级别的过滤器(尤其是分页)的主要内容,如果未能解决你的问题,请参考以下文章

spring data jpa详解

spring data jpa问题

spring data jpa 详解

Spring Data 系列 Spring+JPA(spring-data-commons)

Spring Data JPA 整合Spring

spring-data-jpa软删除方案