streszczenie
Czy autoryzacja w CQRS / DDD powinna być realizowana na polecenie / zapytanie, czy nie?
Po raz pierwszy opracowuję aplikację online, wykorzystującą mniej więcej ściśle wzorzec DDD CQRS. Wpadłem na jakiś problem, którego tak naprawdę nie mogę rozwiązać.
Aplikacja, którą tworzę, jest aplikacją do tworzenia ksiąg rachunkowych, umożliwiającą tworzenie ksiąg rachunkowych, a także umożliwiającą ich przeglądanie / edytowanie / usuwanie, np. Pracownikom. Twórca księgi powinien mieć możliwość edytowania praw dostępu do księgi, którą utworzył. Może nawet zmienić właściciela. Domena ma dwa agregaty TLedger i TUser .
Przeczytałem wiele postów ze słowem kluczowym DDD / CQRS dotyczących bezpieczeństwa, autoryzacji itp. Większość z nich stwierdziła, że autoryzacja jest poddomeną ogólną , chyba że budowana jest aplikacja zabezpieczająca.
W tym przypadku domeną podstawową jest z pewnością domena księgowa zainteresowana transakcjami, bilansowaniem i rachunkami. Wymagana jest także funkcjonalność umożliwiająca zarządzanie drobnym dostępem do ksiąg rachunkowych. Zastanawiam się, jak zaprojektować to w kategoriach DDD / CQRS.
W samouczkach DDD w całym miejscu stwierdzono, że polecenia są częścią wszechobecnego języka. Mają znaczenie. Są to konkretne działania, które reprezentują „rzeczywistość”.
Ponieważ wszystkie te polecenia i zapytania są faktycznymi działaniami, które użytkownicy wykonaliby w „prawdziwym życiu”, czy wdrożenie autoryzacji powinno być połączone ze wszystkimi tymi „poleceniami” i „zapytaniami”? Użytkownik miałby uprawnienia do wykonania TLedger.addTransaction (), ale nie na przykład TLedger.removeTransaction (). Lub użytkownik może wykonać zapytanie „getSummaries ()”, ale nie „getTransactions ()”.
Trójwymiarowe mapowanie istniałoby w formie polecenia-księgi użytkownika lub zapytania księgi użytkownika w celu ustalenia praw dostępu.
Lub w odsprzężony sposób nazwane „uprawnienia” zostaną zarejestrowane dla użytkownika. Uprawnienia, które byłyby następnie mapowane dla określonych poleceń. Na przykład pozwolenie „ManageTransactions” pozwoliłoby użytkownikowi na wykonanie polecenia „AddTransaction ()”, „RemoveTransaction ()” itp.
Użytkownik mapujący uprawnienia -> księga główna -> polecenie / zapytanie
Użytkownik mapujący uprawnienia -> księga główna -> pozwolenie -> polecenie / zapytanie
To pierwsza część pytania. Czy w skrócie, czy autoryzacja w CQRS / DDD powinna być realizowana dla polecenia lub zapytania? Czy też należy oddzielić autoryzację od poleceń?
Po drugie, dotyczy autoryzacji na podstawie uprawnień. Użytkownik powinien mieć możliwość zarządzania uprawnieniami do swoich ksiąg rachunkowych lub ksiąg, którymi może zarządzać.
- Komendy zarządzania autoryzacją występują w księdze głównej
Myślałem o dodaniu zdarzeń / poleceń / procedur obsługi do agregacji Księgi głównej, takich jak grantPermission (), revokePermission () itp. W takim przypadku wymuszenie tych reguł miałoby miejsce w procedurach obsługi komend. Wymagałoby to jednak, aby wszystkie polecenia zawierały identyfikator użytkownika, który je wydał. Następnie sprawdziłbym TLedger, czy istnieje uprawnienie dla tego użytkownika do wykonania tego polecenia.
Na przykład :
class TLedger{
function addTransactionCmdHandler(cmd){
if (!this.permissions.exist(user, 'addTransaction')
throw new Error('Not Authorized');
}
}
- Komendy zarządzania autoryzacją w Użytkowniku
Odwrotnym rozwiązaniem byłoby włączenie uprawnień do TUser. TUser miałby zestaw uprawnień. Następnie w modułach obsługi poleceń TLedger pobierałbym użytkownika i sprawdzał, czy ma on uprawnienia do wykonania polecenia. Wymagałoby to jednak pobrania agregatu TUser dla każdego polecenia TLedger.
class TAddTransactionCmdHandler(cmd) {
this.userRepository.find(cmd.userId)
.then(function(user){
if (!user.can(cmd)){
throw new Error('Not authorized');
}
return this.ledgerRepository.find(cmd.ledgerId);
})
.then(function(ledger){
ledger.addTransaction(cmd);
})
}
- Kolejna domena z usługą
Inną możliwością byłoby całkowite modelowanie innej domeny autoryzacji. Ta domena byłaby zainteresowana prawami dostępu, autoryzacją itp. Subdomena księgowości użyłaby następnie usługi, aby uzyskać dostęp do tej domeny autoryzacji w formie AuthorizationService.isAuthorized(user, command)
.
class TAddTransactionCmdHandler(cmd) {
authService.isAuthorized(cmd)
.then(function(authorized){
if (!authorized) throw new Error('Not authorized');
return this.ledgerRepository.find(cmd.ledgerId)
})
.then(function(){
ledger.addTransaction(cmd);
})
}
Jaka decyzja byłaby najbardziej „DDD / CQRS”?
źródło
Odpowiedzi:
W przypadku pierwszego pytania walczyłem z czymś podobnym. Coraz bardziej skłaniam się ku trójfazowemu schematowi autoryzacji:
1) Autoryzacja na poziomie polecenia / zapytania „czy ten użytkownik ma kiedykolwiek uprawnienia do wykonania tego polecenia?” W aplikacji MVC można to prawdopodobnie obsłużyć na poziomie kontrolera, ale wybieram ogólny moduł obsługi wstępnej, który zapyta o magazyn uprawnień na podstawie bieżącego użytkownika i wykonującego polecenia.
2) Autoryzacja w ramach usługi aplikacji „czy ten użytkownik” kiedykolwiek * ma uprawnienia dostępu do tego podmiotu? ”W moim przypadku prawdopodobnie będzie to kontrola niejawna po prostu za pomocą filtrów w repozytorium - w mojej domenie jest to w zasadzie TenantId z nieco większą szczegółowością OrganizationId.
3) Autoryzacja oparta na przejściowych właściwościach twoich podmiotów (takich jak Status) będzie obsługiwana wewnątrz domeny. (Np. „Tylko niektóre osoby mogą modyfikować zamkniętą księgę.”) Zdecydowałem się umieścić to w domenie, ponieważ w dużej mierze opiera się ona na domenie i logice biznesowej i nie bardzo czuję się dobrze, ujawniając to w innych miejscach.
Chciałbym usłyszeć reakcje innych osób na ten pomysł - jeśli chcesz, rozerwij go na strzępy (jeśli to możliwe, podaj kilka alternatyw :))
źródło
Wdrożyłbym autoryzację jako część Twojej autoryzacji BC, ale wdrożyłbym ją jako filtr akcji w systemie Ledger. W ten sposób można je logicznie oddzielić od siebie - kod księgi głównej nie powinien wywoływać kodu autoryzacji - ale nadal uzyskujesz wysoką wydajność autoryzacji w trakcie procesu dla każdego przychodzącego żądania.
źródło