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).
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.
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
.
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)?
The simplest way to implement pagination is to use the Java Query Language – create a query and configure it via setMaxResults and setFirstResult: Query query = entityManager. createQuery("From Foo"); int pageNumber = 1; int pageSize = 10; query.
Each of these defines its own functionality: CrudRepository provides CRUD functions. PagingAndSortingRepository provides methods to do pagination and sort records. JpaRepository provides JPA related methods such as flushing the persistence context and delete records in a batch.
Pagination is used to display a large number of records in different parts. In such case, we display 10, 20 or 50 records in one page. For remaining records, we provide links. We can simply create pagination example in Spring MVC.
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.
hence any query method can be annotated with @PreAuthorize
containing hasRole
.
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.
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;
//...
}
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;
}
Note: QCompoundContainer
is the "meta-model" class generated by 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;
//...
}
Omitting this last step can lead to a NullPointerException
.
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)
This works. The downside is a bit obvious. The same Role
is associated with every single instance of the same Compound
class implementation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With