Interfejs API REST oparty na rolach?

27

Buduję interfejs API REST, dla którego kilku użytkowników z różnymi rolami będzie miało dostęp do zasobów w nim zawartych.

Aby uprościć zakres, weźmy domenę „uczeń / nauczyciel / klasa”:

GET /students jest zasobem do uzyskania dostępu.

Użytkownicy mogą pełnić role takie jak Student i / lub Nauczyciel

Studenci będą mieli dostęp tylko do uczniów swoich klas. Nauczyciele będą mieli dostęp do uczniów klas, których nauczają. Niektóre zastosowania mogą być uczniem ORAZ uczyć także innych klas. Muszą mieć dostęp do uczniów swoich klas ORAZ do uczniów klas, których nauczają.

Idealnie chcę zaimplementować to jako dwie funkcje - po jednej na rolę, a następnie „unii”, jeśli użytkownik ma wiele ról.

Moje pytanie brzmi: jakiego wzoru należy użyć do wdrożenia tego?

Zewnętrznie

  • Czy powinienem podzielić mój interfejs API na rolę? GET /teacher/studentsi GET /student/studentsnie wydaje mi się to właściwe.
  • Zachowaj wszystko, jestem jednym zasobem (preferowane)

Wewnętrznie

Jak należy to wdrożyć wewnętrznie?

  • Czy każda metoda powinna zaczynać się od BIG switch / if per role?
  • Czy powinienem wdrożyć repozytorium według roli?
  • Czy istnieje wzór, który pomoże mi to osiągnąć?

Na marginesie: używam ASP.NET Web API i Entity Framework 6 , ale tak naprawdę nie ma to znaczenia dla implementacji koncepcyjnej.

Casper Jensen
źródło
3
„to świetne pytanie, chciałbym wiedzieć, czy przyszedłeś z rozwiązaniem, ponieważ próbuję zrobić coś podobnego. Myślę, że tak powinno być: Po pierwsze, wdrożymy interfejs API, który zwraca wszystkie potrzebne dane , wtedy każdy klient nie połączy się bezpośrednio z interfejsem API, ale z serwerem proxy, który będzie odpowiedzialny za filtrowanie danych według ról od tego użytkownika ”
Cleiton

Odpowiedzi:

11

Interfejs API należy budować wokół zasobów, a nie ról, np .:

/rest/students

powinny być dostępne dla każdego, kto ma rolę, która pozwala im widzieć uczniów.

Wewnętrznie wdrażasz zabezpieczenia oparte na rolach. To, jak sobie z tym poradzisz, zależy od szczegółów aplikacji, ale załóżmy, że masz tabelę ról, każda osoba ma jedną lub więcej ról, a te role określają, do czego każda osoba może uzyskać dostęp. Już określiłeś zasady dostępu do studentów:

  • studenci mają dostęp do uczniów na zajęciach, które biorą
  • nauczyciele mają dostęp do uczniów w klasach, których uczą

Kiedy więc osoba dzwoni:

/rest/students

nazywasz metodę dostępną dla uczniów, przekazując rolę osoby. Oto pseudo kod:

roles = person.roles; //array
students = getStudents( roles );
return students;

i w tej metodzie możesz zdobyć uczniów dla każdej roli z osobnymi wywołaniami, np .:

factory = getFactory();
classes= [];
students = [];
for( role in roles ){
    service = factory.getService( role );
    // implementation details of how you get classes for student/teacher are hidden in the service
    classes = classes.merge( service.getClasses( person ) );
    // classes[] has class.students[]
    // loop on classes and add each student to students, or send back classes with nested students? depends on use case
  }
}

To bardzo trudny pomysł na to, co możesz zrobić i niekoniecznie będzie pasował do twoich konkretnych potrzeb, ale powinien dać ci poczucie zaangażowanych elementów. Jeśli chcesz zwrócić zajęcia z każdym uczniem na liście, jest to dobre podejście. Jeśli chcesz tylko uczniów, możesz wyodrębnić ich z każdej klasy i połączyć w zbiór uczniów.

Nie, nie powinieneś mieć osobnego repozytorium dla każdej roli. Jedyną rolą jest ustalenie, w jaki sposób otrzymujesz dane i być może to, co możesz zrobić z danymi (np. Nauczyciele mogą wprowadzać oceny uczniów). Same dane są takie same.

Jeśli chodzi o wzorce, w tym podejściu stosuje się wzorzec fabryczny, aby wyodrębnić usługę, która pobiera dane na podstawie roli. Wydzielenie usług według roli może być lub może nie być właściwe. Podoba mi się to podejście, ponieważ minimalizuje ilość kodu na każdym etapie programu i sprawia, że ​​jest bardziej czytelny niż przełącznik lub blok.

Robert Munn
źródło
1
Dziękuję za odpowiedź. Skończyło się na zrobieniu czegoś takiego, co zasugerowałeś. Używając LINQ2SQL (C #), mogłem przekazać zapytanie do każdej „roli” i zastosować miejsce dla każdej roli, którą otrzymał użytkownik. Wynikiem byłaby instrukcja sql z warunkiem „LUB” dla każdej roli, do której użytkownik ma dostęp. Jeśli użytkownikowi nie przypisano żadnych ról, zwracam po prostu Enumarable.Empty () wywołującego.
Casper Jensen
0

Znajdź długopis i papier i zacznij modelować swój system.

Przekonasz się, że prawdopodobnie potrzebujesz podmiotu o nazwie PERSON. Ponieważ zarówno UCZNIOWIE, jak i NAUCZYCIEL „OSOBA”, można utworzyć abstrakcyjny byt o nazwie OSOBA o ogólnych atrybutach, takich jak imię, nazwisko itp. NAUCZYCIEL -> to-a -> Osoba. Teraz możesz spróbować znaleźć cechy NAUCZYCIELA, które nie dotyczą UCZNIÓW; np. NAUCZYCIEL prowadzi zajęcia (klasy) dotyczące jednego lub więcej TEMATÓW.

Wymuszanie bezpieczeństwa jest uważane za niefunkcjonalny aspekt twojej aplikacji. Jest to problem przekrojowy, którym należy się zająć poza „logiką biznesową”. Jak zauważa @Robert Munn, ROLĘ należy zachować w jednym miejscu. Korzystanie z ról w celu ograniczenia dostępu do niektórych funkcji jest raczej gruboziarniste, a koncepcja nazywa się kontrolą dostępu opartą na rolach (RBAC).

Aby sprawdzić, czy nauczyciel powinien mieć możliwość zobaczenia ocen uczniów, należy je wyrazić w modelu domeny. Powiedzmy, że nauczyciel ma zajęcia z programowania przedmiotów. Prawdopodobnie wyraziłbyś w swoim modelu, że uczniowie uczęszczają na zajęcia z różnych przedmiotów. Tutaj zaczyna się logika aplikacji / biznesu. Logikę tę można zweryfikować za pomocą programowania testowego.

Państwo powinno podzielić swoje zasoby, aby aplikacja sprawdzalne i modułowe.

W każdym razie najlepszym sposobem, aby naprawdę pokazać, co mam na myśli, jest pokazanie go za pomocą kodu :) Oto strona GitHub: https://github.com/thomasandersen77/role-based-rest-api

Powodzenia :)

Thomas Andersen
źródło
3
twój link zniknął ...
Cleiton,