How to implement fine-grained access control in Spring-Data-Rest?

TL DR: How to implement fine-grained access control in the flattened REST api approach that Spring-Data-Rest gives us?

So, I am creating an API using Spring-Data-Rest, where there are three main levels of access:

1) Admin - can see / update all groups

2) The owner of the group - can see / update the group and everything under it

3) Owner of a subgroup - can only see / update his own group. There is no recursive embedding, only one sub-level is allowed.

And the "group" is displayed as a resource (has a crud repository).

So far, so good - and I have implemented some access control for modification using the repository event handler, so on the create / write / delete side, I think I'm fine.

Now I need to move on to restricting the visibility of some items. This is normal for getting a single item, since I can use Pre / Post Authorize annotations and reference the principal.

The problem is with the findAll () methods - I have no easy way to filter out specific instances that I don't want to show based on the current principal. For example, the owner of a subgroup could see all groups by running GET / groups. Ideally, they should have elements to which they do not have access, not even visible at all.

For me, this sounds like writing custom @Query () annotations on the repository interfaces, but it doesn't seem feasible because:

  • I need to indicate to the principal in the request. SPeL is supposed to be supported, but doesn't seem to work with # # expressions at all (despite this blog post suggesting something else: https://spring.io/blog/2014/07/15/spel-support-in- spring-data-jpa-query-definitions ). I am using Spring-boot with 1.1.8.RELEASE and the Evans-RELEASE train for Spring -data in general.

  • The type of query that I need to write will differ depending on the access level, which cannot be realistically included in a single JPQL statement (if the administrator selects all groups and also receives all (sub) groups associated with the main user).

Therefore, it seems to me that I need to write some custom repository implementations for this and just reference the principal in the code. Well, that’s good - but for each repository it seems to me that I need to control access (I think that it will be almost all of them). This applies to findAll and various custom search methods.

Am I approaching this wrong? Is there another approach to dynamically limiting the visibility of elements based on the current user that will work better? In a flat namespace like Spring-data-rest, I imagine this will be a common problem.

In the previous project, I just solved it, exposing everything under / api / groups / {groupId} / ... and used the subresource locator as a single pinch point to control access to anything under it. There is no such luck in Spring -data-rest.

Update: now stumbles with the custom findAll () override method (this works for other methods defined on my user interface). Although this may be a separate issue - I'm blocked right now. Spring -data just doesn't call this when I do GET / groups, but I call the original. Oddly enough, it uses my query if I define it on the interface and mark it with @Query (maybe custom overrides of inline methods are no longer supported?).

public interface GroupRepository extends JpaRepository<Group, Long>, GroupCustomRepository {} public interface GroupCustomRepository { Page<Group> findAll(Pageable pageable); } public class GroupCustomRepositoryImpl extends SimpleJpaRepository<Group, Long> implements GroupCustomRepository { @Inject public GroupCustomRepositoryImpl(EntityManager em) { super(Group.class, em); } @Override public Page<Group> findAll(Pageable pageable) { MyPrincipal principal = (MyPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Page<Group> result; if (principal.isAdmin()) { result = findAll(pageable); } else { Specification<Group> spec = (root, query, cb) -> cb.or( cb.equal(root, principal.getGroup()), cb.and(cb.isNotNull(root.get(Group_.parentGroup)), cb.equal(root.get(Group_.parentGroup), principal.getGroup())) ); result = findAll(spec, pageable); } return result; } } 

Update 2: Since I cannot access the main thing in @Query, and I cannot override it using a special method, I am on a brick wall. @PostFilter does not work either because the returned object is a page, not a collection.

I decided to simply disable / groups of only administrators, and everyone else uses different approaches (/ groups / search / somethingSpecific) using @ PostFilters / @ PostAuthorizations.

It does not seem to be very well connected with the HAL approach. Interested in how other people solve these problems with Spring-data-rest.

+6
source share
1 answer

As a result, we approached this as follows:

  • We have created a custom aspect that is in front of the CRUD methods in the repository. He then scans and calls the associated “authorization handler”, which is annotated in the repository, which dynamically manages the authorization data.

  • We should have been pretty tough when it came to limiting the results in the findAll () query (for example: search / users) - essentially, only administrators could list anything sensitive. Otherwise, limited users had to use query methods for certain elements.

  • We have created several reusable classes related to authorization, and we use them in certain scenarios - in particular, user queries, for example:

    @PreAuthorize ("@ authorizations.systemAdminRead ()") @Query ("select u FROM User r where ...") List findAll ();

    @PostAuthorize ("@ otherAuthorizationHandler.readAllowed (returnObject)") ResponseObject someQuery ();

All in all, it works - but it feels very awkward, and it's easy to skip things. I would like it to be fixed in a larger framework, even if it was possible to dynamically tune default queries, it would be useful (when I tried to do this, I was not able to update queries accordingly using @Query).

We use PostgreSQL, so the upcoming row-level security ( http://michael.otacoo.com/postgresql-2/postgres-9-5-feature-highlight-row-level-security/ ) would have to fit the bill well, assuming that we can pass him the corresponding authorization data through the database connection.

+3
source

Source: https://habr.com/ru/post/977449/


All Articles