Viele Projekte haben Authentifizierungsprozesse (bis zu dem einen oder anderen Grad). Viele Best Practices wurden in allen bekannten Technologien usw. geschrieben. usw.
Aber der Benutzer hat sich angemeldet und? Immerhin kann er nicht alles machen. Wie man feststellt, was er sehen kann und was nicht. Welche Schaltflächen es zu drücken hat, welche können ändern, was erstellt oder gelöscht werden soll.
In diesem Artikel möchte ich einen Ansatz zur Lösung dieser Probleme in Webanwendungen betrachten.

Eine echte / effektive Autorisierung kann zunächst nur auf dem Server erfolgen. Im Front End können wir nur die Benutzeroberfläche verbessern (hmm, die russische Sprache ist mächtig, aber nicht immer ...), es ist auch die Benutzererfahrung jenseits von UX. Wir können die Schaltflächen ausblenden, auf die der Benutzer nicht klicken darf, oder ihn daran hindern, auf die Seite zuzugreifen, oder eine Meldung anzeigen, dass er keine Rechte an dieser oder jener Aktion hat.
Und hier stellt sich die Frage, wie man es so richtig wie möglich macht.
Beginnen wir mit der Definition des Problems.
Wir haben die Todo App erstellt und sie hat verschiedene Benutzertypen:
USER - kann alle Aufgaben anzeigen und ändern (V setzen und entfernen), aber nicht löschen oder erstellen und keine Statistiken anzeigen.
ADMIN - kann alle Aufgaben anzeigen und neue erstellen, sieht jedoch keine Statistiken.
SUPER_ADMIN - kann alle Aufgaben anzeigen , neue erstellen und löschen, kann auch Statistiken anzeigen .
| Aufgabe anzeigen | Aufgabe erstellen | Aufgabe aktivieren / deaktivieren (Update) | Aufgabe löschen | Statistiken anzeigen |
---|
USER | V. | X. | V. | X. | X. |
Admin | V. | V. | V. | X. | X. |
SUPER_ADMIN | V. | V. | V. | V. | V. |
In einer solchen Situation können wir mit den Rollen leicht auskommen. Die Situation kann sich jedoch stark ändern. Stellen Sie sich vor, es gibt einen Benutzer, der die gleichen Rechte wie ADMIN haben und Aufgaben löschen muss. Oder einfach USER mit der Fähigkeit, Statistiken zu sehen.
Eine einfache Lösung besteht darin, neue Rollen zu erstellen.
Auf großen Systemen mit einer relativ großen Anzahl von Benutzern verlieren wir uns jedoch schnell in einer großen Anzahl von Rollen ... Und hier erinnern wir uns an die Berechtigungen für Benutzerrechte. Zur einfacheren Verwaltung können wir Gruppen mit mehreren Berechtigungen erstellen und diese an den Benutzer anhängen. Es besteht immer die Möglichkeit, einem bestimmten Benutzer eine bestimmte Berechtigung hinzuzufügen.
Ähnliche Lösungen finden sich in vielen großartigen Diensten. AWS, Google Cloud, SalesForce usw.
Ähnliche Lösungen sind bereits in vielen Frameworks implementiert, beispielsweise in Django (Python).
Ich möchte ein Beispiel für die Implementierung für Angular-Anwendungen geben. (Am Beispiel derselben ToDo-App).
Zuerst müssen Sie alle möglichen Berechtigungen ermitteln.
In Funktionen unterteilt
- Wir haben Aufgaben und Statistiken.
- Wir bestimmen die möglichen Aktionen mit jedem von ihnen.
- Aufgabe: erstellen, lesen, aktualisieren, löschen
- Statistik: Lesen (in unserem Beispiel nur Ansicht)
- Erstellen Sie eine Karte (MAP) mit Rollen und Berechtigungen
export const permissionsMap = { todos:{ create:'*', read:'*', update:'*', delete:'*' }, stats:'*' }
Meiner Meinung nach ist dies die beste Option, aber in den meisten Fällen gibt der Server Folgendes zurück:
export permissions = [ 'todos_create', 'todos_read', 'todos_update', 'todos_delete', 'stas' ]
Weniger lesbar, aber auch sehr gut.
So sieht unsere Anwendung aus, wenn der Benutzer über alle möglichen Berechtigungen verfügt.

Beginnen wir mit USER. So sehen die Berechtigungen aus:
export const USERpermissionsMap = { todos:{ read:'*', update:'*', } }
1) Der BENUTZER kann keine Statistiken sehen, dh er kann im Prinzip nicht zur Statistikseite gehen.
In solchen Situationen verfügt Angular über Guards, die auf Routenebene registriert sind ( Dokumentation ).
In unserem Fall sieht es so aus:
const routes: Routes = [
Sie sollten auf das Objekt in den Daten achten.
peremissions = 'stats', dies sind die Berechtigungen, über die der Benutzer verfügen muss, um auf diese Seite zugreifen zu können.
Basierend auf den erforderlichen data.permissions und den Berechtigungen, die der PermissionsGuardService-Server uns erteilt hat, wird entschieden, ob USER auf die Seite '/ stats' gehen soll oder nicht.
@Injectable({ providedIn: 'root' }) export class PermissionsGuardService implements CanActivate { constructor( private store: Store<any>) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
Diese Funktion enthält die Entscheidungslogik - Gibt es eine unersetzliche Berechtigung (erforderlich) für alle Berechtigungen des USER (userPerms)?
export function checkPermissions(required: string, userPerms) {

Und so. Unser BENUTZER kann nicht zur Statistikseite gelangen. Er kann jedoch weiterhin Aufgaben erstellen und löschen.
Damit USER die Aufgabe nicht entfernen kann, reicht es aus, das rote (X) aus der Aufgabenzeile zu entfernen.
Zu diesem Zweck verwenden wir die Strukturrichtlinie .
<button *appPermissions="'todos_delete'" class="delete-button" (click)="removeSingleTodo()"> X </button>
@Directive({ selector: '[appPermissions]' }) export class PermissionsDirective { private _required: string; private _viewRef: EmbeddedViewRef<any> | null = null; private _templateRef: TemplateRef<any> | null = null; @Input() set appPermissions(permission: string) { this._required = permission; this._viewRef = null; this.init(); } constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef) { this._templateRef = templateRef; } init() { const isPermitted = checkPermissions(this._required, this.getPermissions()); if (isPermitted) { this._viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef); } else { console.log('PERMISSIONS DIRECTIVE says \n You don\'t have permissions to see it'); } } getPermissions() { localStorage.get('userPermissions') } }

Jetzt sieht USER die Schaltfläche LÖSCHEN nicht, kann aber dennoch neue Aufgaben hinzufügen.
Ein Eingabefeld zu entfernen - wird alle Arten unserer Anwendung verderben. Die richtige Lösung in dieser Situation besteht darin disable
Eingabefelder zu disable
.
Wir werden Rohr verwenden.
<!-- as a value `permissions` pipe will get required permissions `permissions` pipe return true or false, that's why we have !('todos_create' | permissions) to set disable=true if pipe returns false --> <input class="centered-block" [disabled]="!('todos_create' | permissions)" placeholder="What needs to be done?" autofocus/>
@Pipe({ name: 'permissions' }) export class PermissionsPipe implements PipeTransform { constructor(private store: Store<any>) { } transform(required: any, args?: any): any { const isPermitted = checkPermissions(required, this.getPermissions()); if (isPermitted) { return true; } else { console.log('[PERMISSIONS PIPE] You don\'t have permissions'); return false; } } getPermissions() { return localStorage.get('userPermissions') } }
Und jetzt kann USER nur Aufgaben sehen und ändern (V / X). Wir haben jedoch noch eine weitere Schaltfläche mit dem Namen "Löschen abgeschlossen".
Angenommen, wir haben die folgenden Anforderungen an den Produktmanager:
- Die Schaltfläche "Abgeschlossen löschen" sollte für alle und immer sichtbar sein.
- Es sollte auch anklickbar sein.
- Wenn USER ohne die entsprechenden Berechtigungen die Schaltfläche drückt, sollte eine Meldung angezeigt werden.
Bauanweisung wird uns nicht helfen, ebenso wie Rohr.
Es ist auch nicht sehr bequem, Berechtigungsprüfungen in Komponentenfunktionen zu schreiben.
Wir müssen lediglich eine Berechtigungsprüfung zwischen dem Klick und der Ausführung der gebundenen Funktion durchführen.
Meiner Meinung nach lohnt es sich, Dekorateure einzusetzen.
export function Permissions(required) { return (classProto, propertyKey, descriptor) => { const originalFunction = descriptor.value; descriptor.value = function (...args: any[]) { const userPerms = localStorage.get('userPermissions') const isPermitted = checkPermissions(required, userPerms); if (isPermitted) { originalFunction.apply(this, args); } else { alert('you have no permissions \n [PERMISSIONS DECORATOR]'); } }; return descriptor; }; }
@Component({ selector: 'app-actions', templateUrl: './actions.component.html', styleUrls: ['./actions.component.css'] }) export class ActionsComponent implements OnInit { @Output() deleteCompleted = new EventEmitter(); constructor() { } @Permissions('todos_delete') public deleteCompleted() { this.deleteCompleted.emit(); } }
Die Redner verdienen einen separaten Artikel.
Unser Endergebnis:

Das Ergebnis:
Dieser Ansatz ermöglicht es uns, unsere UX einfach und dynamisch an die Berechtigungen des Benutzers anzupassen.
Und am wichtigsten ist, dass wir dafür nicht alle Komponenten in Services verschieben oder die '* ngIf'-Vorlagen einschlagen müssen.
Der gesamte Antrag ist vollständig .
Ich werde gerne einen Kommentar abgeben.