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

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

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

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

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

【问题讨论】:

标签: spring-security spring-data querydsl


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

    因此,任何查询方法都可以使用包含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

      【讨论】:

        猜你喜欢
        • 2013-12-15
        • 1970-01-01
        • 1970-01-01
        • 2017-11-29
        • 2013-09-15
        • 2016-02-01
        • 2014-07-02
        • 2023-03-03
        相关资源
        最近更新 更多