Spring Data JPA 和 spring-security:数据库级别的过滤器(尤其是分页) [英] Spring Data JPA and spring-security: filter on database level (especially for paging)
问题描述
我正在尝试使用注释和 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.
- 用户可以读取某个类的所有实体或不读取所有实体
因此可以使用包含 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.
为此,我创建了一个 User
和 Role
实体.Compound
与 Role
有一个 OneToOne 关系,该角色是该 Compound
的read_role".User
和 Role
具有多对多关系.
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.
最后你可能需要初始化从Container
到User
的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屋!