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
的任何子类,用户可能无权查看所有子类。它们必须是过滤器。
为此,我创建了一个 User
和 Role
实体。 Compound
与 Role
具有 OneToOne 关系,并且该角色是该 Compound
的“read_role”。 User
和 Role
具有多对多关系。
@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 生成的“元模型”类。
最后你可能需要初始化从Container
到User
的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 存储库操作的库,包括:
基本操作(findOne
、findAll
、save
、delete
)
具有规范的查找器 (findAll(Specification, ...)
)
querydsl 查找器(用于 Spring WEB 和 Spring Data REST)
特定于用户的查找器 (findByValue
)
https://github.com/vlsergey/spring-data-entity-security
【讨论】:
以上是关于Spring Data JPA 和 spring-security:数据库级别的过滤器(尤其是分页)的主要内容,如果未能解决你的问题,请参考以下文章