Beispiel einer Autorisierungsüberprüfungsmethode von Nest.js

Beispiel einer Autorisierungsüberprüfungsmethode von Nest.js

0x0 Einführung

Unter Systemautorisierung versteht man den Vorgang, bei dem angemeldete Benutzer Vorgänge ausführen. Beispielsweise können Administratoren Benutzervorgänge am System ausführen und Website-Beiträge verwalten, während Nicht-Administratoren Vorgänge wie das autorisierte Lesen von Beiträgen ausführen können. Daher ist zur Implementierung der Systemautorisierung ein Mechanismus zur Identitätsauthentifizierung erforderlich. Im Folgenden wird eine Implementierung des grundlegendsten rollenbasierten Zugriffskontrollsystems beschrieben.

0x1 RBAC-Implementierung

Die rollenbasierte Zugriffskontrolle (RBAC) ist ein Zugriffskontrollmechanismus, der unabhängig von Rollenberechtigungen und definierten Richtlinien ist. Erstellen Sie zunächst eine Datei role.enum.ts, die die Aufzählungsinformationen der Systemrolle enthält:

export enum Rolle {
 Benutzer = "Benutzer",
 Admin = "Administrator"
}

Handelt es sich um ein komplexeres System, empfiehlt es sich, die Rolleninformationen zur besseren Verwaltung in einer Datenbank zu speichern.

Erstellen Sie dann einen Dekorator und verwenden Sie @Roles(), um die angegebenen Ressourcenrollen auszuführen, die für den Zugriff erforderlich sind. Erstellen Sie roles.decorator.ts:

importiere { SetMetadata } von '@nestjs/common'
importiere { Rolle } aus './role.enum'

export const ROLES_KEY = "Rollen"
export const Roles = (...Rollen: Rolle[]) => SetMetadata(ROLES_KEY, Rollen)

Das Obige erstellt einen Dekorator namens @Roles(), der zum Dekorieren jedes Routen-Controllers, wie etwa der Benutzererstellung, verwendet werden kann:

@Post()
@Rollen(Rolle.Admin)
erstellen(@Body() createUserDto: CreateUserDto): Promise<UserEntity> {
 gib dies zurück.userService.create(createUserDto)
}

Erstellen Sie abschließend eine RolesGuard-Klasse, die die dem aktuellen Benutzer zugewiesene Rolle mit der vom aktuellen Routing-Controller benötigten Rolle vergleicht. Um auf die Routing-Rolle (benutzerdefinierte Metadaten) zuzugreifen, wird die Tool-Klasse Reflector verwendet. Erstellen Sie eine neue Datei „roles.guard.ts“:

importiere { Injectable, CanActivate, ExecutionContext } von '@nestjs/common'
importiere { Reflektor } von '@nestjs/core'

importiere { Rolle } aus './role.enum'
importiere { ROLES_KEY } aus './roles.decorator'

@Injizierbar()
Exportklasse RolesGuard implementiert CanActivate {
 Konstruktor (privater Reflektor: Reflektor) {}

 kannAktivieren(Kontext: Ausführungskontext): boolean {
 const requireRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [context.getHandler(), context.getClass()])
 wenn (!erfordernRollen) {
  returniere wahr
 }
 const { Benutzer } = Kontext.switchToHttp().getRequest()
 returniere requireRoles.some(Rolle => Benutzer.Rollen?.includes(Rolle))
 }
}

Angenommen, request.user enthält das Rollenattribut:

Klasse Benutzer {
 // ...andere Eigenschaften
 Rollen: Rolle[]
}

Anschließend wird RolesGuard global im Controller registriert:

Anbieter:
 {
 bieten: APP_GUARD,
 useClass: Rollenwächter
 }
]

Wenn ein Benutzer auf eine Anforderung zugreift, die über den Umfang der Rolle hinausgeht:

{
 "Statuscode": 403,
 "message": "Verbotene Ressource",
 "error": "Verboten"
}

0x2 Anspruchsbasierte Autorisierung

Nach dem Erstellen einer Identität kann das System der Identität eine oder mehrere deklarative Berechtigungen zuweisen, was bedeutet, dass dem aktuellen Benutzer mitgeteilt wird, was er tun soll, anstatt wer der aktuelle Benutzer ist. Im Nest-System wird die deklarative Autorisierung ähnlich wie RBAC oben implementiert, es gibt jedoch einen Unterschied. Anstatt eine bestimmte Rolle zu beurteilen, müssen Berechtigungen verglichen werden. Jedem Benutzer wird ein Satz von Berechtigungen zugewiesen, z. B. durch Definieren eines @RequirePermissions()-Dekorators und anschließendem Zugriff auf die erforderlichen Berechtigungsattribute:

@Post()
@RequirePermissions(Berechtigung.CREATE_USER)
erstellen(@Body() createUserDto: CreateUserDto): Promise<UserEntity> {
 gib dies zurück.userService.create(createUserDto)
}

Die Berechtigung ähnelt der Rollenaufzählung in PRAC, die die Berechtigungsgruppen enthält, auf die das System zugreifen kann:

export enum Rolle {
 CREATE_USER = ['hinzufügen', 'lesen', 'aktualisieren', 'löschen'],
 READ_USER = ['lesen']
}

0x3 Integriertes CASL

CASL ist eine homogene Autorisierungsbibliothek, die den Zugriff der Clients auf Routing-Controller-Ressourcen einschränken kann. Installationsabhängigkeiten:

Garn hinzufügen @casl/ability

Das Folgende ist das einfachste Beispiel zur Implementierung des CASL-Mechanismus und zum Erstellen von zwei Entitätsklassen: Benutzer und Artikel:

Klasse Benutzer {
 ID: Nummer
 isAdmin: Boolescher Wert
}

Die Entitätsklasse „Benutzer“ verfügt über zwei Attribute, nämlich die Benutzer-ID und ob der Benutzer über Administratorrechte verfügt.

Klasse Artikel {
 ID: Nummer
 isPublished: Boolescher Wert
 authorId: Zeichenfolge
}

Die Entitätsklasse „Artikel“ verfügt über drei Attribute, nämlich die Artikelnummer, den Artikelstatus (ob er veröffentlicht wurde) und die Nummer des Autors, der den Artikel geschrieben hat.

Basierend auf den beiden einfachsten Beispielen oben können wir die einfachste Funktion erstellen:

  • Benutzer mit Administratorrechten können alle Entitäten verwalten (erstellen, lesen, aktualisieren und löschen).
  • Der Benutzer hat nur Lesezugriff auf alle Inhalte
  • Benutzer können ihre eigenen Artikel aktualisieren authorId === userId
  • Veröffentlichte Artikel können nicht gelöscht werden. article.isPublished === true

Für die oben genannten Funktionen können Sie eine Aktionsaufzählung erstellen, um die Vorgänge des Benutzers für die Entität darzustellen:

export enum Aktion {
 Verwalten = "verwalten",
 Erstellen = "erstellen",
 Lesen = 'lesen',
 Update = "Aktualisieren",
 Löschen = "löschen",
}

„Manage“ ist ein spezielles Schlüsselwort in CASL, das bedeutet, dass jede beliebige Operation ausgeführt werden kann.

Um die Funktion zu implementieren, müssen Sie die CASL-Bibliothek zweimal kapseln. Führen Sie nest-cli aus, um das erforderliche Geschäft zu erstellen:

Nest G-Modul casl
nest g Klasse casl/casl-ability.factory

Definieren Sie die Methode createForUser() von CaslAbilityFactory, um Objekte für Benutzer zu erstellen:

Typ Themen = InferSubjects<Typ des Artikels | Typ des Benutzers> | „alle“

Exporttyp AppAbility = Fähigkeit<[Aktion, Themen]>

@Injizierbar()
Exportklasse CaslAbilityFactory {
 createForUser(Benutzer: Benutzer) {
 const { kann, kann nicht, bauen } = neuer AbilityBuilder<
  Fähigkeit<[Aktion, Subjekte]>
 >(Fähigkeit als Fähigkeitsklasse<Anwendungsfähigkeit>);

 if (Benutzer.istAdmin) {
  can(Action.Manage, 'all') // Alle Lese- und Schreibvorgänge zulassen } else {
  kann(Action.Read, 'all') // schreibgeschützter Vorgang}

 kann(Aktion.Update, Artikel, { authorId: user.id })
 kann nicht(Aktion.Löschen, Artikel, { ist veröffentlicht: true })

 Rückgabebuild({
  // Details: https://casl.js.org/v5/en/guide/subject-type-detection#use-classes-as-subject-types
  detectSubjectType: item => item.constructor als ExtractSubjectType<Subjekte>
 })
 }
}

Importieren Sie es dann in CaslModule:

importiere { Modul } von '@nestjs/common'
importiere { CaslAbilityFactory } aus './casl-ability.factory'

@Modul({
 Anbieter: [CaslAbilityFactory],
 Exporte: [CaslAbilityFactory]
})
Exportklasse CaslModule {}

Importieren Sie dann CaslModule in ein beliebiges Unternehmen und fügen Sie es in den Konstruktor ein, um es zu verwenden:

Konstruktor (private caslAbilityFactory: CaslAbilityFactory) {}

const Fähigkeit = this.caslAbilityFactory.createForUser(Benutzer)
wenn (Fähigkeit.kann(Aktion.Lesen, 'alle')) {
 // "Benutzer" kann sämtliche Inhalte lesen und schreiben}

Wenn der aktuelle Benutzer kein Administrator ist und über normale Berechtigungen verfügt, kann er Artikel lesen, aber keine neuen Artikel erstellen oder vorhandene Artikel löschen:

const Benutzer = neuer Benutzer()
Benutzer.isAdmin = false

const Fähigkeit = this.caslAbilityFactory.createForUser(Benutzer)
Fähigkeit.kann(Aktion.Lesen, Artikel) // wahr
Fähigkeit.kann(Aktion.Löschen, Artikel) // false
Fähigkeit.kann(Aktion.Erstellen, Artikel) // false

Dies ist offensichtlich problematisch. Wenn der aktuelle Benutzer der Autor des Artikels ist, sollte er Folgendes tun können:

const Benutzer = neuer Benutzer()
Benutzer-ID = 1

const Artikel = neuer Artikel()
Artikel.AutorID = Benutzer-ID

const Fähigkeit = this.caslAbilityFactory.createForUser(Benutzer)
Fähigkeit.kann(Aktion.Update, Artikel) // wahr

Artikel.Autoren-ID = 2
Fähigkeit.kann(Aktion.Update, Artikel) // false

0x4 Polizeiwache

Die obige einfache Implementierung genügt nicht den komplexeren Anforderungen in komplexen Systemen. Daher verwenden wir den vorherigen Authentifizierungsartikel, um den Autorisierungsstrategiemodus auf Klassenebene zu erweitern und die ursprüngliche CaslAbilityFactory-Klasse zu erweitern:

importiere { AppAbility } von '../casl/casl-ability.factory'

Schnittstelle IPolicyHandler {
 handle(Fähigkeit: AppAbility): Boolesch
}

Typ PolicyHandlerCallback = (Fähigkeit: AppAbility) => Boolesch

Exporttyp PolicyHandler = IPolicyHandler | PolicyHandlerCallback

Bietet Unterstützungsobjekte und Funktionen zur Richtlinienprüfung auf jedem Routing-Controller: IPolicyHandler und PolicyHandlerCallback.

Erstellen Sie dann einen @CheckPolicies()-Decorator, um die angegebene Zugriffsrichtlinie für eine bestimmte Ressource auszuführen:

exportiere const CHECK_POLICIES_KEY = "check_policy"
export const CheckPolicies = (...handlers: PolicyHandler[]) => SetMetadata(CHECK_POLICIES_KEY, handlers)

Erstellen Sie eine PoliciesGuard-Klasse, um alle an den Routing-Controller gebundenen Richtlinien zu extrahieren und auszuführen:

@Injizierbar()
Exportklasse PoliciesGuard implementiert CanActivate {
 Konstruktor(
 privater Reflektor: Reflektor,
 private caslAbilityFactory: CaslAbilityFactory,
 ) {}

 async kann aktivieren(Kontext: Ausführungskontext): Promise<boolean> {
 const policyHandlers =
  dies.reflector.get<PolicyHandler[]>(
  CHECK_POLICIES_KEY,
  Kontext.getHandler()
  ) || []

 const { Benutzer } = Kontext.switchToHttp().getRequest()
 const Fähigkeit = this.caslAbilityFactory.createForUser(Benutzer)

 RückgaberichtlinieHandlers.every((handler) =>
  this.execPolicyHandler(Handler, Fähigkeit)
 )
 }

 private execPolicyHandler(handler: PolicyHandler, Fähigkeit: AppAbility) {
 wenn (Typ des Handlers === 'Funktion') {
  Rückgabehandler (Fähigkeit)
 }
 returniere Handler.Handle(Fähigkeit)
 }
}

Vorausgesetzt, request.user enthält eine Benutzerinstanz, werden policyHandler über den Dekorator @CheckPolicies() zugewiesen. Dabei wird aslAbilityFactory#create verwendet, um eine Ability-Objektmethode zu konstruieren, die überprüft, ob der Benutzer über ausreichende Berechtigungen zum Ausführen einer bestimmten Aktion verfügt. Anschließend wird dieses Objekt an die Methode zur Richtlinienbehandlung übergeben, die eine Implementierungsfunktion oder eine Instanz der Klasse IPolicyHandler sein kann. Außerdem wird eine handle()-Methode bereitgestellt, die einen booleschen Wert zurückgibt.

@Erhalten()
@UseGuards(RichtlinienGuard)
@CheckPolicies((Fähigkeit: AppAbility) => Fähigkeit.kann(Aktion.Lesen, Artikel))
findeAlle() {
 gib this.articlesService.findAll() zurück
}

Sie können auch die Schnittstellenklasse IPolicyHandler definieren:

Exportklasse ReadArticlePolicyHandler implementiert IPolicyHandler {
 handle(Fähigkeit: AppAbility) {
 Rückgabefähigkeit.kann(Aktion.Lesen, Artikel)
 }
}

Verwenden Sie es wie folgt:

@Erhalten()
@UseGuards(RichtlinienGuard)
@CheckPolicies(neuer ReadArticlePolicyHandler())
findeAlle() {
 gibt this.articlesService.findAll() zurück
}

Dies ist das Ende dieses Artikels über das Beispiel der Autorisierungsüberprüfungsmethode von Nest.js. Weitere verwandte Inhalte zur Autorisierungsüberprüfung von Nest.js finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Detaillierte Erläuterung der Konfiguration und Serialisierung von Nest.js-Umgebungsvariablen
  • So verwenden Sie nest.js, um mehrere statische Verzeichnisse mit Express bereitzustellen

<<:  Zusammenfassung der Installation der grünen Version von MySQL5 unter Windows (empfohlen)

>>:  Lösung für das Problem, dass bei der Installation von Centos7 mit VmWare kein Zugriff auf das Internet möglich ist

Artikel empfehlen

Formel und Berechnungsmethode zur Schätzung der Server-Parallelität

Vor Kurzem musste ich den Server erneut einem Str...

JavaScript Snake-Implementierungscode

In diesem Artikelbeispiel wird der spezifische Ja...

Implementierung der Breakpoint-Wiederaufnahme in Node.js

Vorwort Normaler Geschäftsbedarf: Hochladen von B...

Drei nützliche Codes, damit sich Besucher an Ihre Website erinnern

Drei nützliche Codes, die Besuchern dabei helfen,...

So simulieren Sie Netzwerkpaketverlust und -verzögerung in Linux

netem und tc: netem ist ein Netzwerksimulationsmo...

Der DOCTYPE-Modusauswahlmechanismus bekannter Browser

Dokumentumfang Dieser Artikel behandelt den Modus...

Häufige Fehler und Gründe für MySQL-Verbindungsfehler

=================================================...

js zum Aufrufen der Netzwerkkamera und Behandeln häufiger Fehler

Vor kurzem musste ich aus geschäftlichen Gründen ...

Ein Designer beschwert sich erneut über die offizielle Website von Hammer

Letztes Jahr war der offene Brief ein großer Erfo...