Spring Data JPA 和 spring-security:数据库级别的过滤器(尤其是分页) [英] Spring Data JPA and spring-security: filter on database level (especially for paging)

查看:64
本文介绍了Spring Data JPA 和 spring-security:数据库级别的过滤器(尤其是分页)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to add method level security to my open source project using annotations and spring-security. The problem I'm now facing are findAll methods especially the ones for Paging (eg. returning a page).

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

Using @PostFilter works on Lists (but I personally believe its not a good idea to filter in application and not database) but completely fails on paging queries.

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

This is problematic because I have an Entity containing List<Compound>. There are different implementations of compound and a user might only have the privilege to read one of the Compounds. Compound uses TABLE_PER_CLASS inheritance. Repositories implement QueryDslPredicateExecutor.

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

My thinking is to add a predicate to each query that limits the return results based on current user. However I'm kind of lost on a) how the data model for user and roles should look and b) how to then create the predicate (this is probably easy once the model is defined). Or does querydsl already offer type based filtering (on elements contained in the queried class)?

推荐答案

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

For the time being a came up with following solution. Since my project is rather simple this might not work for a more complex project.

  1. 用户可以读取某个类的所有实体或不读取所有实体

因此可以使用包含 hasRole@PreAuthorize 注释任何查询方法.

hence any query method can be annotated with @PreAuthorize containing hasRole.

我的项目中的 Container 实体除外.它可以包含 Compound 的任何子类,并且用户可能没有查看所有子类的权限.它们必须是过滤器.

The exception to this is the Container entity in my project. It can contain any subclass of Compound and a user might not have the privilege to view all of them. They must be filter.

为此,我创建了一个 UserRole 实体.CompoundRole 有一个 OneToOne 关系,该角色是该 Compound 的read_role".UserRole 具有多对多关系.

For that I created a User and Role entity. Compound has a OneToOne relation to Role and the that role is the "read_role" for that Compound. User and Role have a ManyToMany relationship.

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

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

All my repositories implement QueryDSLPredicateExecutor and that becomes very hand here. Instead of creating custom findBy-methods in the repository we create them in the service layer only and use repositry.findAll(predicate) and repository.findOne(predicate). The predicate holds the actual user input + the "security filter".

@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生成的元模型"类.

Note: QCompoundContainer is the "meta-model" class generated by QueryDSL.

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

At last you probably need to initialize the QueryDSL path from Container to User:

@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.

Omitting this last step can lead to a NullPointerException.

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

Further hint: CompoundService automatically sets role on save:

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 类实现的每个实例相关联.

This works. The downside is a bit obvious. The same Role is associated with every single instance of the same Compound class implementation.

这篇关于Spring Data JPA 和 spring-security:数据库级别的过滤器(尤其是分页)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆