暂时想出了以下解决方案。由于我的项目相当简单,这可能不适用于更复杂的项目。
- 用户可以读取某个类的全部或不读取任何实体
因此,任何查询方法都可以使用包含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 类实现的每个实例相关联。