TL; DR: Jak zaimplementować precyzyjną kontrolę dostępu w spłaszczonym podejściu REST API, które daje nam Spring-Data-Rest?Jak zaimplementować precyzyjną kontrolę dostępu w Spring-Data-Rest?
Więc - Robię API przy użyciu Wiosna-data-Rest gdzie tam trzy główne poziomy dostępu:
1) Administrator - widzi/aktualizować wszystkie grupy
2) właściciel grupa - można zobaczyć/zaktualizować grupę i wszystko pod nią
3) Właściciel podgrupy - może zobaczyć/zaktualizować tylko swoją grupę. Brak zagnieżdżania rekursywnego, dozwolony tylko jeden poziom podrzędny.
"Grupa" jest odsłonięta jako zasób (ma repozytorium crud).
Jak dotąd tak dobrze - i zaimplementowałem pewną kontrolę dostępu do modyfikacji za pomocą repozytorium obsługi zdarzeń - tak po stronie tworzenia/zapisu/usuwania wydaje mi się, że nic mi nie jest.
Teraz muszę dojść do ograniczenia widoczności niektórych przedmiotów. Jest to OK, aby uzyskać pojedynczy element, ponieważ mogę używać adnotacji Pre/Post Authorize i odwołuję się do zleceniodawcy.
Problem leży w metodach findAll() - nie mam łatwego haka do odfiltrowania konkretnych wystąpień, których nie chcę ujawnić na podstawie aktualnej wartości głównej. Na przykład - właściciel podgrupy może zobaczyć wszystkie grupy, wykonując polecenia GET/grupy. Najlepiej byłoby, gdyby przedmioty, do których nie mają dostępu, nie były nawet widoczne.
Dla mnie brzmi to jak pisanie własnego @query() adnotacje w repozytorium interfejsów, ale nie wydaje się wykonalne, ponieważ:
muszę odwołać się do głównego w zapytaniu. SPeL ma być obsługiwany, ale w ogóle nie działa z wyrażeniami? # (Pomimo tego posta na blogu sugerującego inaczej: https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions). Używam wiosennego rozruchu z 1.1.8.RELEASE i pociągiem Evans-RELEASE dla danych sprężynowych ogólnie.
Rodzaj zapytania, które muszę napisać, będzie różnił się w zależności od poziomu dostępu, którego nie można realistycznie zawrzeć w pojedynczej instrukcji JPQL (jeśli administrator wybierze wszystkie grupy, dostanie wszystkie (pod) grupy powiązany z użytkownikiem głównego użytkownika).
W związku z tym wydaje mi się, że muszę napisać kilka implementacji repozytorium niestandardowego i po prostu odwołać się do głównej w kodzie. Cóż, to dobrze - ale wydaje się, że dużo pracy dla każdego repozytorium, które muszę kontrolować dostęp (myślę, że to będzie prawie wszystkie z nich). Odnosi się to do findAll i różnych niestandardowych metod wyszukiwania.
Czy zbliżam się do tego źle? Czy istnieje inne podejście do dynamicznego ograniczania widoczności elementu w oparciu o aktualnie zalogowanego użytkownika, który działałby lepiej? W płaskiej przestrzeni nazw, tak jak eksponuje dane wiosenne, wyobrażam sobie, że byłby to powszechny problem.
W poprzednim projekcie właśnie rozwiązałem go, ujawniając wszystko pod/api/groups/{groupId}/... i miał lokalizator pod-zasobów działać jako pojedynczy punkt kontrolny, aby kontrolować dostęp do wszystkiego pod nim. Brak szczęścia w czasie wiosennych danych.
Aktualizacja: teraz potyka się z niestandardową metodą nadpisywania metody findAll() (działa to w przypadku innych metod zdefiniowanych w moim niestandardowym interfejsie).Choć może to być osobne pytanie - jestem teraz zablokowany. Wiosenne dane po prostu nie wywołują tego, gdy robię GET/grupy, ale wywołuję oryginał. Co dziwne, używa mojego zapytania, jeśli zdefiniuję je w interfejsie i oznaczy je @Query (być może niestandardowe przesłonięcia wbudowanych metod nie są już obsługiwane?).
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;
}
}
Aktualizacja 2: Ponieważ nie mogę uzyskać dostęp do kapitału w @query, a ja nie mogę zastąpić go z niestandardową metodę, jestem w mur. @PostFilter nie działa, ponieważ obiekt zwracany jest stroną, a nie kolekcją.
Postanowiłem po prostu zamknąć grupy/grupy tylko administratorom, a wszyscy inni stosują różne podejścia (/ groups/search/somethingSpecific) za pomocą @ PostFilters/@ PostAuthorizations.
To nie wygląda na to, że dobrze pasuje do podejścia HAL. Zainteresowany tym, jak inni ludzie rozwiązują tego rodzaju problemy z Spring-data-rest.
Nie możesz użyć '@ PostFilter'? Istnieje więcej niż jedna metoda BTW "findAll". Zastąpienie jednego może nie wystarczyć. – zeroflagL
Próbowałem, że :) @PostFilter nie działa z Pages - i to by wyrzuciło logikę strony (i prawdopodobnie ma słabą wydajność). Wygląda na to, że masz tylko zastąpić jedną z opcji findAll() - inaczej traktuje je jako niejednoznaczne ścieżki. Nie jestem pewien, czy mogę wesprzeć zarówno sortowanie, jak i stronicowanie, jeśli tylko pomijam jedną z metod. –
Czy każdy znalazł rozwiązanie tego @DaveLeBlanc? –