
Irgendwann benötigt eine Anwendung Autorisierung. Das bedeutet, dass sich verschiedene Zugriffsebenen auf einer Website (oder bei allem anderen) unterschiedlich verhalten. Es kann alles sein, von der Anzeige von Daten bis hin zu ganzen Bereichen, die für eine Gruppe von Benutzern nicht zugänglich sind.
In Nicht-Single-Page-Anwendungen (SPA) wird ein Claim oder eine Rolle mit Daten oder einem Bereich der Anwendung verknüpft - entweder der Benutzer hat diese Rolle oder diesen Claim oder er hat sie nicht. In einer SPA ist es dasselbe, aber mit einem großen Vorbehalt. Eine SPA wird in den Browser heruntergeladen. Ab diesem Punkt hat der Browser die vollständige Kontrolle über den Code. Eine böswillige Person kann den Code ändern, um ihn für ihre Zwecke zu nutzen.
Da SPAs nicht gesichert werden können, sind Authentifizierung und Autorisierung in einer SPA lediglich Benutzererfahrung. Alle bedeutsame Sicherheit muss auf dem Webserver erfolgen. Dieser Artikel behandelt nicht die Absicherung Ihrer API gegen Angriffe. Ich empfehle, ein Video von Pluralsight anzusehen oder ein Dokument zu lesen, das sich mit der Sicherheit für Ihre Servertechnologie befasst.
Die Absicht dieses Artikels ist es, Ihnen zu zeigen, wie ich eine Autorisierungs-Benutzererfahrung zu meiner Angular 1.x SPA hinzugefügt habe.
Sicherheitsbereiche
Ich habe 3 Bereiche der Benutzeroberfläche identifiziert, die Autorisierung benötigen: Elemente (HTML), Routen und Daten.
Nur zur Erinnerung: Die Absicherung einer SPA ist kein Ersatz für die Absicherung des Servers. Berechtigungen auf dem Client dienen lediglich dazu, ehrliche Menschen ehrlich zu halten und dem Benutzer eine gute Erfahrung zu bieten.
Die 3 Bereiche im Detail:
Elemente
Sie müssen bestimmte HTML-Elemente ausblenden. Es könnte ein Label, eine Tabelle mit Daten, ein Button oder jedes Element auf der Seite sein.
Routen
Sie möchten ganze Routen ausblenden. In bestimmten Fällen möchten Sie nicht, dass der Benutzer auf eine Ansicht zugreift. Durch die Absicherung der Route kann ein Benutzer nicht zu der Ansicht navigieren. Stattdessen wird ihm eine Meldung “Sie sind nicht berechtigt, zu dieser Ansicht zu navigieren” angezeigt.
Daten
Manchmal reicht es nicht aus, die Elemente in der Ansicht zu verstecken. Ein aufmerksamer Benutzer kann einfach den Quellcode anzeigen und die versteckten Daten im HTML-Quellcode sehen oder beobachten, wie sie in den Browser gestreamt werden. Was wir wollen, ist, dass die Daten gar nicht erst abgerufen werden.
Das Hinzufügen von Sicherheit ist knifflig. Zunächst versuchte ich, den Zugriff auf der HTTP-API (auf dem Client) zu beschränken. Ich erkannte schnell, dass das nicht funktionieren würde. Ein Benutzer hat möglicherweise keinen direkten Zugriff auf die Daten, aber das bedeutet nicht, dass er keinen indirekten Zugriff auf die Daten hat. Auf der HTTP-API-Ebene (normalerweise eine der niedrigsten in der Anwendung) können wir den Kontext des Aufrufs nicht erkennen und daher keine Sicherheitsbedenken darauf anwenden.
Unten habe ich Codebeispiele bereitgestellt:
Code
Ich habe einen Service für den Autorisierungsprüfungscode erstellt. Dies ist das Herzstück der Autorisierung. Alle Autorisierungsanfragen verwenden diesen Service, um zu prüfen, ob der Benutzer für die jeweilige Aktion autorisiert ist.
angular.module('services')
.service('AuthorizationContext',function(_, Session){
this.authorizedExecution = function(key, action){
//Looking for the claim key that was passed in. If it exists in the claim set, then execute the action.
Session.claims(function(claims){
var claim = findKey(key, claims);
//If Claim was found then execute the call.
//If it was not found, do nothing
if(claim !== undefined){
action();
}
});
};
this.authorized = function(key, callback){
//Looking for the claim key that was passed in. If it exists in the claim set, then execute the action.
Session.claims(function(claims){
var claim = findKey(key, claims);
//If they don't have any security key, then move forward and authorization.
var valid = claim !== undefined;
callback(valid);
});
};
//this.agencyViewKey = '401D91E7-6EA0-46B4-9A10-530E3483CE15';
function findKey(key, claims){
var claim = _.find(claims, function(item){
return item.value === key;
});
return claim;
}
});
Authorize Directive
Die Authorize-Direktive kann auf jedes HTML-Element angewendet werden, das Sie vor Benutzern ohne eine bestimmte Zugriffsstufe verbergen möchten. Wenn der Benutzer das Zugriffstoken als Teil seiner Claims hat, darf er das Element sehen. Wenn nicht, wird es vor ihm verborgen.
angular.module('directives')
.directive('authorize', ['$compile', 'AuthorizationContext', function($compile, AuthorizationContext) {
return {
restrict: 'A',
replace: true,
//can't have isolated the scope in a shared directive
link:function ($scope, element, attributes) {
var securityKey = attributes.authorize;
AuthorizationContext.authorized(securityKey, function(authorized){
var el = angular.element(element);
el.attr('ng-show', authorized);
//remove the attribute, otherwise it creates an infinite loop.
el.removeAttr('authorize');
$compile(el)($scope);
});
}
};
}]);
Elemente
Ich verwende in meiner Anwendung stark Tabs. Ich wende die Authorize-Direktive auf den Tab an, den ich vor Benutzern ohne die entsprechenden Claims verbergen möchte.
<tabset>
<tab ng-cloak heading="Users" authorize="{{allowUserManagement}}">
...html content
</tab>
</tabset>
Routen
Ich verwende den ui-router
. Leider habe ich für diejenigen, die das nicht tun, keinen Code für den standardmäßigen AngularJS-Router.
Im $stateChangeStart
authentifiziere ich die Route. Dies ist der Code in diesem Event.
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams){
AuthenticationManager.authenticate(event, toState, toParams);
});
Die Funktion, die die Route autorisiert. Wenn sie autorisiert ist, darf die Route fortfahren. Wenn sie nicht autorisiert ist, wird dem Benutzer eine Nachricht angezeigt und er wird zur Startseite weitergeleitet.
function authorizedRoute(toState, location, toaster, breadCrumbs){
if(toState.authorization !== undefined){
AuthorizationContext.authorized(toState.authorization, function(authorized){
if(!authorized){
toaster.pop('error', 'Error', 'You are not authorized to view this page.');
location.path("/search");
} else {
breadCrumbs();
}
});
} else{
breadCrumbs();
}
}
In dieser Router-Definition werden Sie eine Eigenschaft namens ‘authorization’ bemerken. Wenn der Benutzer diesen Claim hat, darf er fortfahren.
angular.module('agency',
[
'ui.router',
'services'
])
.config(function config($stateProvider){
$stateProvider.state( 'agency', {
url: '/agency',
controller: 'agency.index',
templateUrl: 'agency/agency.tpl.html',
authenticate: true,
authorization:'401d91e7-6ea0-46b4-9a10-530e3483ce15',
data:{ pageTitle: 'Agency' }
});
});
Daten
In einigen Fällen möchten Sie keine Anfrage an den Server für die Daten stellen. Wenn der Benutzer den Claim hat, wird ihm erlaubt, die Anfrage zu stellen.
Der obige AuthorizationContext
am Anfang des Artikels zeigt den Code für authorizedExecution
. Hier sehen Sie seine Verwendung.
AuthorizationContext.authorizedExecution(Keys.authorization.allowUserManagement, function(){
//execute code, if the loggedin user has rights.
});
Wie ich oben erwähnt habe, ist dies kein Ersatz für die Absicherung des Servers. Dieser Code funktioniert für die Bereitstellung einer wunderbaren Benutzererfahrung.
Autor: Chuck Conway ist spezialisiert auf Software-Engineering und Generative KI. Verbinden Sie sich mit ihm in den sozialen Medien: X (@chuckconway) oder besuchen Sie ihn auf YouTube.