Ich bin gerade dabei, eine schönere Demo zu erstellen und einige dieser Dienste in ein verwendbares Modul zu bereinigen, aber hier ist, was ich mir ausgedacht habe. Dies ist ein komplexer Prozess, um einige Einschränkungen zu umgehen. Sie müssen dies in mehrere Teile zerlegen.
Schauen Sie sich dieses Plunk an .
Zunächst benötigen Sie einen Dienst zum Speichern der Benutzeridentität. Ich nenne das principal
. Es kann überprüft werden, ob der Benutzer angemeldet ist, und auf Anfrage kann ein Objekt aufgelöst werden, das die wesentlichen Informationen zur Identität des Benutzers darstellt. Dies kann alles sein, was Sie benötigen, aber das Wesentliche sind ein Anzeigename, ein Benutzername, möglicherweise eine E-Mail und die Rollen, zu denen ein Benutzer gehört (sofern dies für Ihre App gilt). Der Principal verfügt auch über Methoden zum Durchführen von Rollenprüfungen.
.factory('principal', ['$q', '$http', '$timeout',
function($q, $http, $timeout) {
var _identity = undefined,
_authenticated = false;
return {
isIdentityResolved: function() {
return angular.isDefined(_identity);
},
isAuthenticated: function() {
return _authenticated;
},
isInRole: function(role) {
if (!_authenticated || !_identity.roles) return false;
return _identity.roles.indexOf(role) != -1;
},
isInAnyRole: function(roles) {
if (!_authenticated || !_identity.roles) return false;
for (var i = 0; i < roles.length; i++) {
if (this.isInRole(roles[i])) return true;
}
return false;
},
authenticate: function(identity) {
_identity = identity;
_authenticated = identity != null;
},
identity: function(force) {
var deferred = $q.defer();
if (force === true) _identity = undefined;
// check and see if we have retrieved the
// identity data from the server. if we have,
// reuse it by immediately resolving
if (angular.isDefined(_identity)) {
deferred.resolve(_identity);
return deferred.promise;
}
// otherwise, retrieve the identity data from the
// server, update the identity object, and then
// resolve.
// $http.get('/svc/account/identity',
// { ignoreErrors: true })
// .success(function(data) {
// _identity = data;
// _authenticated = true;
// deferred.resolve(_identity);
// })
// .error(function () {
// _identity = null;
// _authenticated = false;
// deferred.resolve(_identity);
// });
// for the sake of the demo, fake the lookup
// by using a timeout to create a valid
// fake identity. in reality, you'll want
// something more like the $http request
// commented out above. in this example, we fake
// looking up to find the user is
// not logged in
var self = this;
$timeout(function() {
self.authenticate(null);
deferred.resolve(_identity);
}, 1000);
return deferred.promise;
}
};
}
])
Zweitens benötigen Sie einen Dienst, der den Status überprüft, zu dem der Benutzer wechseln möchte, sicherstellt, dass er angemeldet ist (falls erforderlich; nicht erforderlich für Anmeldung, Zurücksetzen des Kennworts usw.), und dann eine Rollenprüfung durchführt (falls Ihre App braucht das). Wenn sie nicht authentifiziert sind, senden Sie sie an die Anmeldeseite. Wenn sie authentifiziert sind, aber eine Rollenprüfung nicht bestehen, senden Sie sie an eine Seite, auf die der Zugriff verweigert wurde. Ich rufe diesen Service an authorization
.
.factory('authorization', ['$rootScope', '$state', 'principal',
function($rootScope, $state, principal) {
return {
authorize: function() {
return principal.identity()
.then(function() {
var isAuthenticated = principal.isAuthenticated();
if ($rootScope.toState.data.roles
&& $rootScope.toState
.data.roles.length > 0
&& !principal.isInAnyRole(
$rootScope.toState.data.roles))
{
if (isAuthenticated) {
// user is signed in but not
// authorized for desired state
$state.go('accessdenied');
} else {
// user is not authenticated. Stow
// the state they wanted before you
// send them to the sign-in state, so
// you can return them when you're done
$rootScope.returnToState
= $rootScope.toState;
$rootScope.returnToStateParams
= $rootScope.toStateParams;
// now, send them to the signin state
// so they can log in
$state.go('signin');
}
}
});
}
};
}
])
Nun sind alle brauchen, ist in abgehört zu tun ui-router
ist $stateChangeStart
. Auf diese Weise können Sie den aktuellen Status und den Status, in den sie wechseln möchten, überprüfen und Ihre Berechtigungsprüfung einfügen. Wenn dies fehlschlägt, können Sie den Routenübergang abbrechen oder zu einer anderen Route wechseln.
.run(['$rootScope', '$state', '$stateParams',
'authorization', 'principal',
function($rootScope, $state, $stateParams,
authorization, principal)
{
$rootScope.$on('$stateChangeStart',
function(event, toState, toStateParams)
{
// track the state the user wants to go to;
// authorization service needs this
$rootScope.toState = toState;
$rootScope.toStateParams = toStateParams;
// if the principal is resolved, do an
// authorization check immediately. otherwise,
// it'll be done when the state it resolved.
if (principal.isIdentityResolved())
authorization.authorize();
});
}
]);
Der schwierige Teil beim Verfolgen der Identität eines Benutzers besteht darin, ihn nachzuschlagen, wenn Sie sich bereits authentifiziert haben (z. B. wenn Sie die Seite nach einer vorherigen Sitzung besuchen und ein Authentifizierungstoken in einem Cookie gespeichert haben oder wenn Sie eine Seite hart aktualisiert haben oder von einem Link auf eine URL abgelegt). Aufgrund der Funktionsweise ui-router
müssen Sie Ihre Identitätsauflösung einmal durchführen, bevor Ihre Authentifizierung überprüft wird. Sie können dies mit der resolve
Option in Ihrer Statuskonfiguration tun . Ich habe einen übergeordneten Status für die Site, von dem alle Status erben, wodurch der Principal aufgelöst werden muss, bevor etwas anderes passiert.
$stateProvider.state('site', {
'abstract': true,
resolve: {
authorize: ['authorization',
function(authorization) {
return authorization.authorize();
}
]
},
template: '<div ui-view />'
})
Hier gibt es ein anderes Problem ... wird resolve
nur einmal angerufen. Sobald Ihr Versprechen für die Identitätssuche erfüllt ist, wird der Auflösungsdelegierte nicht mehr ausgeführt. Daher müssen wir Ihre Authentifizierungsprüfungen an zwei Stellen durchführen: einmal gemäß Ihrem Identitätsversprechen resolve
, das das erste Mal, $stateChangeStart
wenn Ihre App geladen wird, auflöst , und einmal, wenn die Auflösung durchgeführt wurde, das jede Zeit abdeckt, in der Sie durch Staaten navigieren.
OK, was haben wir bisher gemacht?
- Wir überprüfen, wann die App geladen wird, wenn der Benutzer angemeldet ist.
- Wir verfolgen Informationen über den angemeldeten Benutzer.
- Wir leiten sie weiter, um sich für Zustände anzumelden, bei denen der Benutzer angemeldet sein muss.
- Wir leiten sie in den Status "Zugriff verweigert" um, wenn sie nicht berechtigt sind, darauf zuzugreifen.
- Wir haben einen Mechanismus, um Benutzer in den ursprünglichen Status zurückzuleiten, den sie angefordert haben, wenn wir sie zum Anmelden benötigen.
- Wir können einen Benutzer abmelden (muss zusammen mit jedem Client- oder Servercode verkabelt werden, der Ihr Authentifizierungsticket verwaltet).
- Wir müssen Benutzer nicht jedes Mal zur Anmeldeseite zurückschicken, wenn sie ihren Browser neu laden oder auf einen Link klicken.
Was machen wir jetzt? Nun können Sie Ihre Staaten in Regionen organisieren , die in Zeichen benötigen. Sie können authentifiziert / autorisierte Benutzer erfordern durch Zugabe von data
mit roles
diesen Staaten (oder ein Elternteil von ihnen, wenn Sie die Vererbung verwenden möchten). Hier beschränken wir eine Ressource auf Administratoren:
.state('restricted', {
parent: 'site',
url: '/restricted',
data: {
roles: ['Admin']
},
views: {
'content@': {
templateUrl: 'restricted.html'
}
}
})
Jetzt können Sie Status für Status steuern, welche Benutzer auf eine Route zugreifen können. Irgendwelche anderen Bedenken? Vielleicht nur einen Teil einer Ansicht variieren, je nachdem, ob sie angemeldet sind oder nicht? Kein Problem. Verwenden Sie die principal.isAuthenticated()
oder sogar principal.isInRole()
eine der zahlreichen Möglichkeiten, eine Vorlage oder ein Element bedingt anzuzeigen.
Spritzen Sie zuerst principal
in einen Controller oder was auch immer und kleben Sie ihn an das Zielfernrohr, damit Sie ihn aus Ihrer Sicht problemlos verwenden können:
.scope('HomeCtrl', ['$scope', 'principal',
function($scope, principal)
{
$scope.principal = principal;
});
Ein- oder Ausblenden eines Elements:
<div ng-show="principal.isAuthenticated()">
I'm logged in
</div>
<div ng-hide="principal.isAuthenticated()">
I'm not logged in
</div>
Usw. so weiter, so weiter. In Ihrer Beispiel-App hätten Sie jedenfalls einen Status für die Startseite, über den nicht authentifizierte Benutzer vorbeischauen könnten. Sie können Links zu den Anmelde- oder Anmeldezuständen haben oder diese Formulare in diese Seite integriert haben. Was immer dir passt.
Die Dashboard-Seiten können alle von einem Status erben, bei dem die Benutzer angemeldet sein und beispielsweise ein User
Rollenmitglied sein müssen. Alle Autorisierungsmaterialien, die wir besprochen haben, würden von dort fließen.