Hier gibt es viele Fragen, die sich mit den Mechanismen der Authentifizierung und Autorisierung von RESTful-APIs befassen, aber keine von ihnen scheint Einzelheiten zur Implementierung sicherer Dienste auf Anwendungsebene zu enthalten.
Angenommen, meine Webanwendung (ich habe Java im Sinn, dies gilt jedoch für jedes Backend) verfügt über ein sicheres Authentifizierungssystem, mit dem sich Benutzer der API mit einem Benutzernamen und einem Kennwort anmelden können. Wenn der Benutzer eine Anfrage stellt, kann ich zu jedem Zeitpunkt während der Pipeline für die Anforderungsverarbeitung eine getAuthenticatedUser()
Methode aufrufen , die entweder den Nullbenutzer zurückgibt, wenn der Benutzer nicht angemeldet ist, oder ein Benutzerdomänenobjekt, das den angemeldeten Benutzer darstellt.
Die API ermöglicht authentifizierten Benutzern den Zugriff auf ihre Daten, z. B. ein GET, /api/orders/
um die Auftragsliste dieses Benutzers zurückzugeben. In ähnlicher Weise gibt ein GET to /api/tasks/{task_id}
Daten zurück, die sich auf diese bestimmte Aufgabe beziehen.
Nehmen wir an, dass es eine Reihe verschiedener Domänenobjekte gibt, die einem Benutzerkonto zugeordnet werden können (Bestellungen und Aufgaben sind zwei Beispiele, wir könnten auch Kunden, Rechnungen usw. haben). Wir möchten nur, dass authentifizierte Benutzer auf Daten zu ihren eigenen Objekten zugreifen können. Wenn ein Benutzer einen Anruf tätigt, müssen /api/invoices/{invoice_id}
wir überprüfen, ob der Benutzer berechtigt ist, auf diese Ressource zuzugreifen, bevor wir sie bereitstellen.
Meine Frage ist dann, ob es Muster oder Strategien gibt, um dieses Autorisierungsproblem zu lösen. Eine Option, die ich in Betracht ziehe, ist das Erstellen einer Hilfsschnittstelle (dh SecurityUtils.isUserAuthorized(user, object)
), die während der Anforderungsverarbeitung aufgerufen werden kann, um sicherzustellen, dass der Benutzer zum Abrufen des Objekts berechtigt ist. Dies ist nicht ideal, da es den Anwendungsendpunktcode mit vielen dieser Aufrufe verschmutzt, z
Object someEndpoint(int objectId) {
if (!SecurityUtils.isUserAuthorized(loggedInUser, objectDAO.get(objectId)) {
throw new UnauthorizedException();
}
...
}
... und dann stellt sich die Frage, ob diese Methode für jeden Domänentyp implementiert werden kann, was ein bisschen schmerzhaft sein kann. Dies könnte die einzige Option sein, aber ich würde mich über Ihre Vorschläge freuen!
Antworten:
Bitte für die Liebe Gottes keine
SecurityUtils
Klasse schaffen !Ihre Klasse wird in wenigen Monaten zu 10.000 Zeilen Spaghetti-Code! Sie müssten einen
Action
Typ (Erstellen, Lesen, Aktualisieren, Zerstören, Auflisten usw.) an IhreisUserAuthorized()
Methode übergeben, der schnell zu einer tausend Zeilen langenswitch
Anweisung mit immer komplexer werdender Logik wird, die sich nur schwer in einem Unit-Test testen lässt. Tu es nicht.Im Allgemeinen muss zumindest in Ruby on Rails jedes Domänenobjekt für seine eigenen Zugriffsrechte verantwortlich sein, indem für jedes Modell eine Richtlinienklasse festgelegt wird . Anschließend fragt der Controller die Richtlinienklasse, ob der aktuelle Benutzer für die Anforderung Zugriff auf die Ressource hat oder nicht. Hier ist ein Beispiel in Ruby, da ich so etwas noch nie in Java implementiert habe, aber die Idee sollte sauber rüberkommen:
Selbst wenn Sie über komplexe verschachtelte Ressourcen verfügen, muss eine Ressource die verschachtelten Ressourcen "besitzen", damit die Logik der obersten Ebene von Natur aus heruntersprudelt. Diese verschachtelten Ressourcen benötigen jedoch ihre eigenen Richtlinienklassen, falls sie unabhängig von der übergeordneten Ressource aktualisiert werden können.
In meiner Bewerbung für die Abteilung meiner Universität dreht sich alles um das
Course
Objekt. Es ist keineUser
zentrierte Anwendung. Da jedochUser
s eingeschrieben sindCourse
, kann ich einfach sicherstellen, dass:@course.users.include? current_user && (whatever_other_logic_I_need)
für jede Ressource, die eine bestimmte
User
Person ändern muss, da fast alle Ressourcen an a gebunden sindCourse
. Dies erfolgt in der Richtlinienklasse in derowns_whatever
Methode.Ich habe nicht viel Java-Architektur gemacht, aber es scheint, dass Sie eine
Policy
Schnittstelle erstellen könnten , in der die verschiedenen Ressourcen, die authentifiziert werden müssen, die Schnittstelle implementieren müssen. Dann haben Sie alle notwendigen Methoden , die so komplex werden können , wie Sie sie brauchen , um pro Domain - Objekt . Wichtig ist, die Logik an das Modell selbst zu binden, es aber gleichzeitig in einer separaten Klasse zu halten (Single Responsibility Principle (SRP)).Ihre Controller-Aktionen könnten ungefähr so aussehen:
quelle
Eine bequemere Lösung besteht darin, Anmerkungen zu verwenden, um Methoden zu markieren, für die eine Autorisierung erforderlich ist. Dies unterscheidet sich von Ihrem Geschäftscode und kann von Spring Security oder benutzerdefiniertem AOP-Code verarbeitet werden. Wenn Sie diese Anmerkungen für Ihre Geschäftsmethoden anstelle von Endpunkten verwenden, können Sie sicher sein, dass eine Ausnahme auftritt, wenn ein nicht autorisierter Benutzer versucht, sie unabhängig vom Einstiegspunkt aufzurufen.
quelle
@PreAuthorization("hasRole('ADMIN') and #requestingUser.company.uuid == authentication.details.companyUuid")
Annotation definieren, in der das#requestingUser
Segment auf ein pased Objekt mit dem Feldnamen verweist,requestingUser
der eine Methode hat, für diegetCompany()
das zurückgegebene Objekt eine weitere Methode hatgetUuid()
. Dasauthentication
bezieht sich auf dasAuthentication
im Sicherheitskontext gespeicherte Objekt.@PreAuthorize("hasPermission(#user, 'allowDoSomething')")
und implementieren Sie entweder Ihren benutzerdefinierten Berechtigungsauswerter oder schreiben Sie einen benutzerdefinierten Ausdruckshandler und root . Wenn Sie das Verhalten der verfügbaren Anmerkungen ändern möchten, schauen Sie sich diesen Thread anVerwenden Sie funktionsbasierte Sicherheit.
Eine Fähigkeit ist ein nicht fälschbares Objekt, das als Beweis dafür dient, dass man eine bestimmte Aktion ausführen kann. In diesem Fall:
Dies macht es unmöglich, etwas zu versuchen, zu dem der aktuelle Benutzer nicht berechtigt ist.
Auf diese Weise ist es unmöglich
quelle