Sie haben also an dieser neuen und schicken Webanwendung gearbeitet. Sei es eine Rezept-App, ein Dokumentenmanager oder sogar Ihre private Cloud, Sie sind jetzt an dem Punkt angelangt, an dem Sie mit Benutzern und Berechtigungen arbeiten. Nehmen wir den Dokumentenmanager als Beispiel: Sie möchten nicht nur Admins; vielleicht möchten Sie Gäste mit Lesezugriff einladen oder Personen, die Ihre Dateien bearbeiten, aber nicht löschen dürfen. Wie handhaben Sie diese Logik im Frontend, ohne Ihren Code mit zu vielen komplizierten Bedingungen und Prüfungen zu überladen?
In diesem Artikel werden wir eine Beispielimplementierung durchgehen, wie Sie diese Art von Situation elegant und sauber handhaben könnten. Nehmen Sie es mit einer Prise Salz – Ihre Bedürfnisse mögen unterschiedlich sein, aber ich hoffe, Sie können daraus einige Ideen gewinnen.
Nehmen wir an, Sie haben das Backend bereits erstellt, eine Tabelle für alle Benutzer in Ihrer Datenbank hinzugefügt und vielleicht eine eigene Spalte oder Eigenschaft für Rollen bereitgestellt. Die Implementierungsdetails sind Ihnen völlig überlassen (abhängig von Ihrem Stack und Ihrer Präferenz). Der Einfachheit halber verwenden wir für diese Demo die folgenden Rollen
- Admin: Kann alles tun, wie eigene oder fremde Dokumente erstellen, löschen und bearbeiten.
- Editor: Kann Dateien erstellen, anzeigen und bearbeiten, aber nicht löschen.
- Gast: Kann Dateien anzeigen, ganz einfach.
Wie die meisten modernen Webanwendungen verwendet Ihre App wahrscheinlich eine RESTful API zur Kommunikation mit dem Backend, also verwenden wir dieses Szenario für die Demo. Selbst wenn Sie sich für etwas anderes wie GraphQL oder serverseitiges Rendering entscheiden, können Sie dasselbe Muster anwenden, das wir uns ansehen werden.
Der Schlüssel ist, die Rolle (oder Berechtigung, wenn Sie diesen Namen bevorzugen) des aktuell angemeldeten Benutzers beim Abrufen von Daten zurückzugeben.
{
id: 1,
title: "My First Document",
authorId: 742,
accessLevel: "ADMIN",
content: {...}
}
Hier rufen wir ein Dokument mit einigen Eigenschaften ab, einschließlich einer Eigenschaft namens accessLevel für die Rolle des Benutzers. So wissen wir, was der angemeldete Benutzer tun darf und was nicht. Unsere nächste Aufgabe ist es, im Frontend etwas Logik hinzuzufügen, um sicherzustellen, dass Gäste nichts sehen, was sie nicht sehen sollen, und umgekehrt.
Idealerweise verlassen Sie sich nicht nur auf das Frontend, um Berechtigungen zu überprüfen. Jemand, der mit Webtechnologien erfahren ist, könnte immer noch eine Anfrage ohne UI an den Server senden, um Daten zu manipulieren. Daher sollte Ihr Backend die Dinge ebenfalls überprüfen.
Übrigens ist dieses Muster framework-agnostisch; es spielt keine Rolle, ob Sie mit React, Vue oder sogar mit reinem JavaScript arbeiten.
Konstanten definieren
Der allererste (optionale, aber dringend empfohlene) Schritt ist die Erstellung einiger Konstanten. Dies sind einfache Objekte, die alle Aktionen, Rollen und andere wichtige Teile enthalten, aus denen die App bestehen kann. Ich lege sie gerne in eine eigene Datei, nennen wir sie vielleicht constants.js
const actions = {
MODIFY_FILE: "MODIFY_FILE",
VIEW_FILE: "VIEW_FILE",
DELETE_FILE: "DELETE_FILE",
CREATE_FILE: "CREATE_FILE"
};
const roles = {
ADMIN: "ADMIN",
EDITOR: "EDITOR",
GUEST: "GUEST"
};
export { actions, roles };
Wenn Sie den Vorteil von TypeScript nutzen, können Sie Enums verwenden, um eine etwas sauberere Syntax zu erzielen.
Die Erstellung einer Sammlung von Konstanten für Ihre Aktionen und Rollen hat einige Vorteile
- Eine einzige Quelle der Wahrheit. Anstatt Ihren gesamten Code durchsuchen zu müssen, öffnen Sie einfach
constants.js, um zu sehen, was in Ihrer App möglich ist. Dieser Ansatz ist auch sehr erweiterbar, z. B. wenn Sie Aktionen hinzufügen oder entfernen. - Keine Tippfehler. Anstatt jedes Mal manuell eine Rolle oder Aktion einzugeben, was fehleranfällig ist und unangenehme Debugging-Sitzungen zur Folge hat, importieren Sie das Objekt und erhalten dank der Magie Ihres bevorzugten Editors kostenlos Vorschläge und Autovervollständigung. Wenn Sie dennoch einen Namen falsch schreiben, wird ESLint oder ein anderes Tool höchstwahrscheinlich meckern, bis Sie es beheben.
- Dokumentation. Arbeiten Sie im Team? Neue Teammitglieder werden die Einfachheit zu schätzen wissen, nicht Dutzende von Dateien durchsuchen zu müssen, um zu verstehen, welche Berechtigungen oder Aktionen existieren. Sie kann auch leicht mit JSDoc dokumentiert werden.
Die Verwendung dieser Konstanten ist ziemlich unkompliziert; importieren und verwenden Sie sie wie folgt
import { actions } from "./constants.js";
console.log(actions.CREATE_FILE);
Berechtigungen definieren
Weiter zum spannenden Teil: Modellierung einer Datenstruktur, um unsere Aktionen auf Rollen abzubilden. Es gibt viele Möglichkeiten, dieses Problem zu lösen, aber diese gefällt mir am besten. Erstellen wir eine neue Datei, nennen wir sie permissions.js, und schreiben wir etwas Code hinein
import { actions, roles } from "./constants.js";
const mappings = new Map();
mappings.set(actions.MODIFY_FILE, [roles.ADMIN, roles.EDITOR]);
mappings.set(actions.VIEW_FILE, [roles.ADMIN, roles.EDITOR, roles.GUEST]);
mappings.set(actions.DELETE_FILE, [roles.ADMIN]);
mappings.set(actions.CREATE_FILE, [roles.ADMIN, roles.EDITOR]);
Gehen wir das Schritt für Schritt durch
- Zuerst müssen wir unsere Konstanten importieren.
- Dann erstellen wir eine neue JavaScript Map namens
mappings. Wir hätten auch eine andere Datenstruktur verwenden können, wie Objekte, Arrays, was auch immer. Ich verwende gerne Maps, da sie praktische Methoden wie.has(),.get()usw. bieten. - Als Nächstes fügen wir (oder besser gesagt, setzen) einen neuen Eintrag für jede Aktion hinzu, die unsere App hat. Die Aktion dient als Schlüssel, über den wir dann die für die Ausführung dieser Aktion erforderlichen Rollen abrufen. Als Wert definieren wir ein Array der erforderlichen Rollen.
Dieser Ansatz mag zunächst seltsam erscheinen (tat er mir), aber ich habe gelernt, ihn im Laufe der Zeit zu schätzen. Die Vorteile sind offensichtlich, insbesondere in größeren Anwendungen mit vielen Aktionen und Rollen
- Wieder nur eine Quelle der Wahrheit. Müssen Sie wissen, welche Rollen zum Bearbeiten einer Datei erforderlich sind? Kein Problem, gehen Sie zu
permissions.jsund suchen Sie nach dem Eintrag. - Die Geschäftslogik zu ändern ist überraschend einfach. Sagen wir, Ihr Produktmanager beschließt, dass Redakteure ab morgen Dateien löschen dürfen; fügen Sie einfach ihre Rolle zum Eintrag
DELETE_FILEhinzu und fertig. Das Gleiche gilt für das Hinzufügen neuer Rollen: Fügen Sie weitere Einträge zurmappings-Variable hinzu, und Sie sind bereit. - Testbar. Sie können Snapshot-Tests verwenden, um sicherzustellen, dass sich in diesen Zuordnungen nichts unerwartet ändert. Es ist auch bei Code-Reviews übersichtlicher.
Das obige Beispiel ist eher einfach und könnte erweitert werden, um kompliziertere Fälle abzudecken. Wenn Sie zum Beispiel verschiedene Dateitypen mit unterschiedlichen Rollenzugängen haben. Mehr dazu am Ende dieses Artikels.
Berechtigungen in der Benutzeroberfläche prüfen
Wir haben alle unsere Aktionen und Rollen definiert und eine Zuordnung erstellt, die erklärt, wer was tun darf. Es ist an der Zeit, eine Funktion zu implementieren, die wir in unserer Benutzeroberfläche zur Überprüfung dieser Rollen verwenden können.
Wenn ich ein solches neues Verhalten entwickle, beginne ich immer damit, wie die API aussehen soll. Danach implementiere ich die eigentliche Logik hinter dieser API.
Nehmen wir an, wir haben eine React-Komponente, die ein Dropdown-Menü rendert
function Dropdown() {
return (
<ul>
<li><button type="button">Refresh</button><li>
<li><button type="button">Rename</button><li>
<li><button type="button">Duplicate</button><li>
<li><button type="button">Delete</button><li>
</ul>
);
}
Offensichtlich wollen wir nicht, dass Gäste die Option „Löschen“ oder „Umbenennen“ sehen oder anklicken, aber wir wollen, dass sie „Aktualisieren“ sehen. Auf der anderen Seite sollten Redakteure alles außer „Löschen“ sehen. Ich stelle mir eine API wie diese vor
hasPermission(file, actions.DELETE_FILE);
Das erste Argument ist die Datei selbst, wie sie von unserer REST-API abgerufen wurde. Sie sollte die accessLevel-Eigenschaft von früher enthalten, die entweder ADMIN, EDITOR oder GUEST sein kann. Da derselbe Benutzer möglicherweise unterschiedliche Berechtigungen für verschiedene Dateien hat, müssen wir dieses Argument immer angeben.
Als zweites Argument übergeben wir eine Aktion, wie z. B. das Löschen der Datei. Die Funktion sollte dann einen booleschen Wert true zurückgeben, wenn der aktuell angemeldete Benutzer die Berechtigung für diese Aktion hat, oder false, wenn nicht.
import hasPermission from "./permissions.js";
import { actions } from "./constants.js";
function Dropdown() {
return (
<ul>
{hasPermission(file, actions.VIEW_FILE) && (
<li><button type="button">Refresh</button></li>
)}
{hasPermission(file, actions.MODIFY_FILE) && (
<li><button type="button">Rename</button></li>
)}
{hasPermission(file, actions.CREATE_FILE) && (
<li><button type="button">Duplicate</button></li>
)}
{hasPermission(file, actions.DELETE_FILE) && (
<li><button type="button">Delete</button></li>
)}
</ul>
);
}
Sie möchten vielleicht einen weniger ausführlichen Funktionsnamen finden oder sogar eine andere Möglichkeit, die gesamte Logik zu implementieren (Currying fällt einem ein), aber für mich hat dies gute Dienste geleistet, selbst in Anwendungen mit sehr komplexen Berechtigungen. Sicher, das JSX sieht etwas unübersichtlicher aus, aber das ist ein kleiner Preis, den man zahlen muss. Dieses Muster konsistent in der gesamten App zu verwenden, macht Berechtigungen viel sauberer und intuitiver verständlich.
Falls Sie noch nicht überzeugt sind, sehen wir uns an, wie es ohne die Hilfsfunktion hasPermission aussehen würde
return (
<ul>
{['ADMIN', 'EDITOR', 'GUEST'].includes(file.accessLevel) && (
<li><button type="button">Refresh</button></li>
)}
{['ADMIN', 'EDITOR'].includes(file.accessLevel) && (
<li><button type="button">Rename</button></li>
)}
{['ADMIN', 'EDITOR'].includes(file.accessLevel) && (
<li><button type="button">Duplicate</button></li>
)}
{file.accessLevel == "ADMIN" && (
<li><button type="button">Delete</button></li>
)}
</ul>
);
Sie mögen sagen, dass das nicht schlecht aussieht, aber denken Sie darüber nach, was passiert, wenn weitere Logik hinzugefügt wird, wie Lizenzprüfungen oder granularere Berechtigungen. In unserem Beruf geraten Dinge schnell außer Kontrolle.
Fragen Sie sich, warum wir die erste Berechtigungsprüfung benötigen, wenn jeder den „Aktualisieren“-Button sowieso sehen kann? Ich mag es dort zu haben, weil man nie weiß, was sich in Zukunft ändern könnte. Eine neue Rolle könnte eingeführt werden, die den Button vielleicht nicht einmal sieht. In diesem Fall müssen Sie nur Ihre permissions.js aktualisieren und die Komponente in Ruhe lassen, was zu einem saubereren Git-Commit und weniger Fehlermöglichkeiten führt.
Die Berechtigungsprüfung implementieren
Schließlich ist es an der Zeit, die Funktion zu implementieren, die alles zusammenfügt: Aktionen, Rollen und die Benutzeroberfläche. Die Implementierung ist ziemlich geradlinig
import mappings from "./permissions.js";
function hasPermission(file, action) {
if (!file?.accessLevel) {
return false;
}
if (mappings.has(action)) {
return mappings.get(action).includes(file.accessLevel);
}
return false;
}
export default hasPermission;
export { actions, roles };
Sie können den obigen Code in eine separate Datei oder sogar in permissions.js legen. Ich persönlich bewahre sie in einer Datei auf, aber hey, ich sage Ihnen nicht, wie Sie leben sollen. :-)
Lassen Sie uns verdauen, was hier passiert
- Wir definieren eine neue Funktion,
hasPermission, mit derselben API-Signatur, die wir zuvor festgelegt haben. Sie nimmt die Datei (die vom Backend kommt) und die Aktion, die wir ausführen möchten. - Als Sicherheitsnetz, falls die Datei aus irgendeinem Grund
nullist oder keineaccessLevel-Eigenschaft enthält, geben wirfalsezurück. Besser vorsichtig sein, um dem Benutzer keine „geheimen“ Informationen aufgrund eines Glitches oder eines Fehlers im Code preiszugeben. - Im Kern prüfen wir, ob
mappingsdie gesuchte Aktion enthält. Wenn ja, können wir ihren Wert sicher abrufen (denken Sie daran, es ist ein Array von Rollen) und prüfen, ob unser aktuell angemeldeter Benutzer die für diese Aktion erforderliche Rolle hat. Dies gibt entwedertrueoderfalsezurück. - Schließlich, wenn
mappingsdie gesuchte Aktion nicht enthielt (könnte ein Fehler im Code oder ein weiterer Glitch sein), geben wir zur Sicherheitfalsezurück. - In den letzten beiden Zeilen exportieren wir nicht nur die Funktion
hasPermission, sondern reexportieren auch unsere Konstanten zur Erleichterung der Entwickler. Auf diese Weise können wir alle Dienstprogramme in einer Zeile importieren.
import hasPermission, { actions } from "./permissions.js";
Weitere Anwendungsfälle
Der gezeigte Code ist zu Demonstrationszwecken recht einfach. Dennoch können Sie ihn als Basis für Ihre App nehmen und ihn entsprechend gestalten. Ich denke, er ist ein guter Ausgangspunkt für jede JavaScript-gesteuerte Anwendung, um Benutzerrollen und Berechtigungen zu implementieren.
Mit ein wenig Refactoring können Sie dieses Muster sogar wiederverwenden, um etwas anderes zu prüfen, z. B. Lizenzen
import { actions, licenses } from "./constants.js";
const mappings = new Map();
mappings.set(actions.MODIFY_FILE, [licenses.PAID]);
mappings.set(actions.VIEW_FILE, [licenses.FREE, licenses.PAID]);
mappings.set(actions.DELETE_FILE, [licenses.FREE, licenses.PAID]);
mappings.set(actions.CREATE_FILE, [licenses.PAID]);
function hasLicense(user, action) {
if (mappings.has(action)) {
return mappings.get(action).includes(user.license);
}
return false;
}
Anstelle der Rolle eines Benutzers überprüfen wir seine license-Eigenschaft: gleiche Eingabe, gleiche Ausgabe, völlig anderer Kontext.
In meinem Team mussten wir sowohl Benutzerrollen als auch Lizenzen überprüfen, entweder zusammen oder getrennt. Als wir dieses Muster wählten, erstellten wir verschiedene Funktionen für verschiedene Prüfungen und kombinierten sie in einem Wrapper. Was wir am Ende verwendeten, war ein hasAccess-Dienstprogramm
function hasAccess(file, user, action) {
return hasPermission(file, action) && hasLicense(user, action);
}
Es ist nicht ideal, jedes Mal drei Argumente an hasAccess zu übergeben, und Sie finden vielleicht einen Weg, dies in Ihrer App zu umgehen (wie Currying oder globaler Zustand). In unserer App verwenden wir globale Stores, die die Benutzerinformationen enthalten, sodass wir das zweite Argument einfach entfernen und es stattdessen aus einem Store beziehen können.
Sie können auch tiefer in die Berechtigungsstruktur gehen. Haben Sie verschiedene Dateitypen (oder allgemeiner: Entitäten)? Möchten Sie bestimmte Dateitypen basierend auf der Lizenz des Benutzers aktivieren? Nehmen wir das obige Beispiel und machen es etwas leistungsfähiger
const mappings = new Map();
mappings.set(
actions.EXPORT_FILE,
new Map([
[types.PDF, [licenses.FREE, licenses.PAID]],
[types.DOCX, [licenses.PAID]],
[types.XLSX, [licenses.PAID]],
[types.PPTX, [licenses.PAID]]
])
);
Dies fügt unserer Berechtigungsprüfung eine ganz neue Ebene hinzu. Jetzt können wir verschiedene Arten von Entitäten für eine einzige Aktion haben. Nehmen wir an, Sie möchten einen Exporter für Ihre Dateien bereitstellen, aber Sie möchten, dass Ihre Benutzer für diesen super-schicken Microsoft Office-Konverter bezahlen, den Sie entwickelt haben (und wer könnte Ihnen das verübeln?). Anstatt direkt ein Array bereitzustellen, verschachteln wir eine zweite Map in der Aktion und übergeben alle Dateitypen, die wir abdecken wollen. Warum eine Map verwenden, fragen Sie? Aus demselben Grund, den ich zuvor erwähnt habe: Sie bietet einige nützliche Methoden wie .has(). Sie können aber auch etwas anderes verwenden.
Mit der letzten Änderung reicht unsere hasLicense-Funktion nicht mehr aus, daher ist es an der Zeit, sie leicht zu aktualisieren
function hasLicense(user, file, action) {
if (!user || !file) {
return false;
}
if (mappings.has(action)) {
const mapping = mappings.get(action);
if (mapping.has(file.type)) {
return mapping.get(file.type).includes(user.license);
}
}
return false;
}
Ich weiß nicht, ob es nur mir so geht, aber sieht das nicht immer noch super leserlich aus, obwohl die Komplexität zugenommen hat?
Testen
Wenn Sie sicherstellen möchten, dass Ihre App wie erwartet funktioniert, auch nach Code-Refactorings oder der Einführung neuer Funktionen, sollten Sie eine Testabdeckung bereithalten. In Bezug auf das Testen von Benutzerberechtigungen können Sie verschiedene Ansätze verwenden
- Erstellen Sie Snapshot-Tests für Zuordnungen, Aktionen, Typen usw. Dies kann in Jest oder anderen Test-Runnern leicht erreicht werden und stellt sicher, dass nichts unerwartet durch die Code-Review rutscht. Es kann jedoch mühsam werden, diese Snapshots zu aktualisieren, wenn sich Berechtigungen ständig ändern.
- Fügen Sie Unit-Tests für
hasLicenseoderhasPermissionhinzu und stellen Sie sicher, dass die Funktion wie erwartet funktioniert, indem Sie einige realistische Testfälle festlegen. Unit-Tests für Funktionen sind meistens, wenn nicht immer, eine gute Idee, da Sie sicherstellen möchten, dass der richtige Wert zurückgegeben wird. - Neben der Sicherstellung, dass die interne Logik funktioniert, können Sie zusätzliche Snapshot-Tests in Kombination mit Ihren Konstanten verwenden, um jedes einzelne Szenario abzudecken. Mein Team verwendet etwas Ähnliches
Object.values(actions).forEach((action) => {
describe(action.toLowerCase(), function() {
Object.values(licenses).forEach((license) => {
it(license.toLowerCase(), function() {
expect(hasLicense({ type: 'PDF' }, { license }, action)).toMatchSnapshot();
expect(hasLicense({ type: 'DOCX' }, { license }, action)).toMatchSnapshot();
expect(hasLicense({ type: 'XLSX' }, { license }, action)).toMatchSnapshot();
expect(hasLicense({ type: 'PPTX' }, { license }, action)).toMatchSnapshot();
});
});
});
});
Aber auch hier gibt es viele persönliche Präferenzen und Möglichkeiten, es zu testen.
Fazit
Und das war's! Ich hoffe, Sie konnten einige Ideen oder Inspiration für Ihr nächstes Projekt gewinnen und dass dieses Muster etwas ist, das Sie anstreben möchten. Zusammenfassend einige seiner Vorteile
- Keine komplizierten Bedingungen oder Logik mehr in Ihrer Benutzeroberfläche (Komponenten). Sie können sich auf den Rückgabewert der Funktion
hasPermissionverlassen und Elemente basierend darauf bequem ein- und ausblenden. Die Möglichkeit, Geschäftslogik von Ihrer Benutzeroberfläche zu trennen, trägt zu einer saubereren und wartbareren Codebasis bei. - Eine einzige Quelle der Wahrheit für Ihre Berechtigungen. Anstatt viele Dateien zu durchsuchen, um herauszufinden, was ein Benutzer sehen kann und was nicht, gehen Sie zu den Berechtigungszuordnungen und schauen Sie dort nach. Dies erleichtert die Erweiterung und Änderung von Benutzerberechtigungen, da Sie möglicherweise nicht einmal Markup ändern müssen.
- Sehr gut testbar. Egal, ob Sie sich für Snapshot-Tests, Integrationstests mit anderen Komponenten oder etwas anderes entscheiden, die zentralisierten Berechtigungen sind schmerzfrei zu testen.
- Dokumentation. Sie müssen Ihre App nicht in TypeScript schreiben, um von Autovervollständigung oder Codevalidierung zu profitieren; die Verwendung vordefinierter Konstanten für Aktionen, Rollen, Lizenzen usw. kann Ihnen das Leben erleichtern und ärgerliche Tippfehler reduzieren. Außerdem können andere Teammitglieder leicht erkennen, welche Aktionen, Rollen oder was auch immer verfügbar sind und wo sie verwendet werden.
Wenn Sie eine vollständige Demonstration dieses Musters sehen möchten, gehen Sie zu diesem CodeSandbox, das die Idee mit React umsetzt. Es enthält verschiedene Berechtigungsprüfungen und sogar einige Testabdeckungen.
Was denken Sie? Haben Sie einen ähnlichen Ansatz für solche Dinge und halten Sie ihn für die Mühe wert? Ich bin immer daran interessiert, was andere Leute entwickelt haben, fühlen Sie sich frei, Feedback im Kommentarbereich zu hinterlassen. Passen Sie auf!
Schöne Implementierung.
Da es damit zusammenhängt, habe ich darüber nachgedacht, wie wir ein Backend-System entwerfen/implementieren könnten, z. B. in Node.js, wo jemand wie ein Superadmin all diese Berechtigungen und Aktionen basierend auf Rollen ändern kann, wie sie von Giganten wie AWS verwendet werden, um Berechtigungen zu verwalten.
Danke! Die Implementierung in diesem Artikel kann auch dafür verwendet werden, zumindest theoretisch. Das Problem bei Berechtigungen ist, dass jede App sie anders behandelt. Einige verwenden Express, andere GraphQL, Sie verstehen schon. Es ist immer etwas schwierig, allgemeine Annahmen für diese Muster zu treffen. Ich schaue gerne, was andere Apps ähnlicher Größe bereits getan haben, aber auch hier gilt: Spezifische Plattformen, spezifische Lösungen.
Sehr eleganter Code. Glückwunsch.
Aber da alles in JS von jedem gelesen werden kann, was empfehlen Sie, um diese Logik zu verbergen? Danke.
Danke! Meinen Sie das Verbergen dieser Logik für Benutzer Ihrer Anwendung, z. B. aus Sicherheitsgründen? Es gibt nicht viel, was Sie tun können, wenn es im Frontend ist. Dinge werden idealerweise minifiziert und gebündelt, was es viel schwieriger, aber nicht unmöglich macht, zu „entschlüsseln“. Aber es muss an den Browser gesendet werden, wo es möglicherweise analysiert werden kann.
Abgesehen davon sollte Ihr Backend diese Berechtigungen immer unabhängig überprüfen. Sie sollten sich niemals nur auf das Frontend verlassen, genau aus diesem Grund – Entdeckung durch den Benutzer. Selbst wenn ein bösartiger Benutzer die Logik aus dem Frontend dekodiert, kann er keine Daten ändern, da das Backend eingreift.
Wenn Sie verhindern möchten, dass Benutzer erraten, welche Rollen, Berechtigungen oder Lizenzen es gibt, ist eine Idee, die Werte beim Build zu kodieren, zum Beispiel mit einem Babel-Plugin. Dies könnte Strings von z. B.
OWNERin etwas Zufälliges ändern, wie z. B.l334erdca2.Sie müssen Sicherheitsbedenken erwähnen, dies ist NICHT für Sicherheitszwecke verwendbar. Es ist nur Komfort und Benutzeroberfläche.
Und alle API-Aufrufe müssen zu 100 % die Zugriffsberechtigung des Benutzers prüfen. Sie müssen das einfügen, sonst kopieren/fügen Leute diesen [zensierten Inhalt] ein, ohne die Sicherheitsmodelle/Bedrohungen grundlegend zu verstehen.
Danke für das Feedback. Können Sie bitte näher erläutern, warum Sie denken, dass es aus Sicherheitsgründen nicht verwendbar ist?
Ich bin zu 100 % Ihrer Meinung: Das Frontend sollte niemals die einzige Instanz zur Überprüfung dieser Berechtigungen sein. Es ist entscheidend, diese Dinge auch im Backend abzudecken. Ich erwähne dies zu Beginn des Artikels, obwohl es klarer hätte herausgestellt werden können, um Sicherheit zu betonen.
Meiner Meinung nach (und Erfahrung) ist diese Lösung jedoch definitiv für produktive Anwendungen nutzbar. Wenn Sie wichtige Sicherheitsprobleme sehen, die ich nicht bedacht habe, würde ich sie gerne hören :)
Vielen Dank für diesen Artikel. Viele Konzepte aufgefrischt und einige gute Lektionen gelernt!
Sehr guter Artikel! Ich habe nur eine Sache anzumerken: die Tests, die Sie hier geschrieben haben https://css-tricks.de/handling-user-permissions-in-javascript/#testing sind meiner Meinung nach schwer zu lesen. Wenn Sie
jestverwenden, könnten Sie die Funktionalitätendescribe.eachundjest.eachverwenden.Danke für das Feedback. Ich stimme zu, diese Tests könnten besser strukturiert sein. Da ich bisher nicht mit parametrisierten Tests und
.eachin Jest gearbeitet habe, habe ich es in diesem Artikel nicht aufgenommen, um keine schlechten Praktiken zu verbreiten, aber ich werde es mir bald ansehen :)Oh mein Gott! Niemals, niemals, niemals tun Sie dies für irgendetwas anderes als den Benutzerkomfort, und selbst dann, holen Sie sich Ihre einzige Wahrheitsquelle vom Backend. Was auch immer das Backend implementiert, ist die einzige sichere und genaue Wahrheitsquelle. Das Erstellen von Konstanten-Dateien, um Zugriffssteuerungsregeln zuzuordnen, garantiert, dass Sie irgendwann vom Backend abweichen. Ansonsten schöne Implementierung… aber seien Sie ehrlich, dies sollte niemals für etwas anderes als den Benutzerkomfort sein – z. B. einen Button ausgrauen, für den der Benutzer keine Berechtigung hat.
Ja, Sie haben absolut Recht, das Backend sollte die wichtigste Rolle bei Berechtigungen spielen. Sich ausschließlich auf das Frontend zu verlassen, führt zu hohen Sicherheitsrisiken.
Wie bereits in einer anderen Antwort erwähnt, habe ich im Einführungsteil dieses Artikels einen Kommentar dazu abgegeben, aber er hätte klarer sein können, um die Sicherheit zu betonen.
Letztendlich geht es, wie bei jeder reaktiven SPA, die wir bauen, um Benutzerkomfort, ja. In dem Unternehmen, in dem ich arbeite, verwenden wir dieses Konzept, um UI-Elemente in Echtzeit ein- und auszublenden, z. B. wenn ein Benutzer aus einem Projekt entfernt wurde. Dennoch validiert das Backend alle eingehenden Anfragen, und wenn jemand die Berechtigungsprüfungen des Frontends umgehen kann, wird er am Endpunkt scheitern, wenn er versucht, Daten abzurufen oder zu manipulieren. Und ich denke, so sollte es sein, denn die meisten unserer Benutzer (ohne böse Absicht) profitieren von dieser Benutzeroberfläche.