6

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.

+0

Nie możesz użyć '@ PostFilter'? Istnieje więcej niż jedna metoda BTW "findAll". Zastąpienie jednego może nie wystarczyć. – zeroflagL

+0

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. –

+0

Czy każdy znalazł rozwiązanie tego @DaveLeBlanc? –

Odpowiedz

3

Skończyło się zbliża to w następujący sposób:

  • Stworzyliśmy własny aspekt, który siedzi przed metod CRUD na repozytorium. Następnie wyszukuje i wywołuje skojarzony moduł obsługi autoryzacji, który jest zapisany w repozytorium, który dynamicznie zarządza danymi autoryzacji.

  • Musieliśmy być bardzo zręczni, jeśli chodzi o ograniczanie wyników w kwerendzie findAll() (np. Patrząc na/użytkowników) - zasadniczo tylko administratorzy mogli wymieniać wszystkie wrażliwe rzeczy. W przeciwnym razie ograniczeni użytkownicy musieli stosować metody zapytań dla określonych elementów.

  • Stworzyliśmy kilka klas wielokrotnego użytku związane autoryzacji-i używać tych w niektórych scenariuszach - zwłaszcza niestandardowych zapytań, np:

    @PreAuthorize ("@ authorizations.systemAdminRead()") @query ("wybierz u Od użytkownika r gdzie ... ") List findAll();

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

W sumie działa - ale czuje się bardzo przyzwoicie i łatwo jest przeoczyć rzeczy. Chciałabym, żeby to było wtopione w strukturę, nawet gdyby była w stanie dynamicznie dostosować domyślne zapytania, byłoby użyteczne (gdy próbowałem tego, nie byłem w stanie odpowiednio zaktualizować zapytań przy pomocy @Query).

Używamy PostgreSQL, więc nadchodzące bezpieczeństwo na poziomie wiersza (http://michael.otacoo.com/postgresql-2/postgres-9-5-feature-highlight-row-level-security/) dobrze by pasowało do rachunku, zakładając, że możemy podać mu odpowiednie szczegóły autoryzacji za pośrednictwem połączenia DB.