Je suis en train de faire une démo plus agréable ainsi que de nettoyer certains de ces services en un module utilisable, mais voici ce que j'ai trouvé. Il s'agit d'un processus complexe pour contourner certaines mises en garde, alors accrochez-vous. Vous devrez le décomposer en plusieurs morceaux.
Jetez un oeil à ce plunk .
Tout d'abord, vous avez besoin d'un service pour stocker l'identité de l'utilisateur. J'appelle ça principal
. Il peut être vérifié pour voir si l'utilisateur est connecté et, sur demande, il peut résoudre un objet qui représente les informations essentielles sur l'identité de l'utilisateur. Cela peut être tout ce dont vous avez besoin, mais l'essentiel serait un nom d'affichage, un nom d'utilisateur, éventuellement un e-mail et les rôles auxquels un utilisateur appartient (si cela s'applique à votre application). Le directeur dispose également de méthodes pour effectuer des vérifications de rôle.
.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;
}
};
}
])
Deuxièmement, vous avez besoin d'un service qui vérifie l'état dans lequel l'utilisateur veut aller, s'assure qu'il est connecté (si nécessaire; pas nécessaire pour la connexion, la réinitialisation du mot de passe, etc.), puis effectue une vérification des rôles (si votre application en a besoin). S'ils ne sont pas authentifiés, envoyez-les sur la page de connexion. S'ils sont authentifiés, mais échouent à une vérification de rôle, envoyez-les vers une page d'accès refusé. J'appelle ce service 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');
}
}
});
}
};
}
])
Maintenant , tout ce que vous devez faire est d' écouter dans le ui-router
« s $stateChangeStart
. Cela vous donne la possibilité d'examiner l'état actuel, l'état dans lequel ils veulent aller et d'insérer votre vérification d'autorisation. En cas d'échec, vous pouvez annuler la transition de l'itinéraire ou passer à un autre itinéraire.
.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();
});
}
]);
La partie délicate du suivi de l'identité d'un utilisateur consiste à la rechercher si vous vous êtes déjà authentifié (par exemple, vous visitez la page après une session précédente et avez enregistré un jeton d'authentification dans un cookie, ou vous avez peut-être actualisé une page, ou déposé sur une URL à partir d'un lien). En raison de la façon dont cela ui-router
fonctionne, vous devez résoudre votre identité une fois, avant vos vérifications d'authentification. Vous pouvez le faire en utilisant l' resolve
option dans votre configuration d'état. J'ai un état parent pour le site dont tous les états héritent, ce qui oblige le principal à être résolu avant que quoi que ce soit d'autre se produise.
$stateProvider.state('site', {
'abstract': true,
resolve: {
authorize: ['authorization',
function(authorization) {
return authorization.authorize();
}
]
},
template: '<div ui-view />'
})
Il y a un autre problème ici ... resolve
n'est appelé qu'une seule fois. Une fois votre promesse de recherche d'identité terminée, elle ne réexécutera pas le délégué de résolution. Nous devons donc effectuer vos vérifications d'authentification à deux endroits: une fois conformément à votre promesse d'identité résolue resolve
, qui couvre la première fois que votre application se charge, et une fois $stateChangeStart
si la résolution a été effectuée, qui couvre chaque fois que vous naviguez dans les États.
OK, qu'avons-nous fait jusqu'à présent?
- Nous vérifions pour voir quand l'application se charge si l'utilisateur est connecté.
- Nous suivons les informations sur l'utilisateur connecté.
- Nous les redirigeons vers l'état de connexion pour les États qui nécessitent que l'utilisateur soit connecté.
- Nous les redirigeons vers un état d'accès refusé s'ils ne sont pas autorisés à y accéder.
- Nous avons un mécanisme pour rediriger les utilisateurs vers l'état d'origine qu'ils ont demandé, si nous en avions besoin pour se connecter.
- Nous pouvons déconnecter un utilisateur (doit être connecté de concert avec tout code client ou serveur qui gère votre ticket d'authentification).
- Nous n'avons pas besoin de renvoyer les utilisateurs vers la page de connexion chaque fois qu'ils rechargent leur navigateur ou déposent un lien.
Où allons-nous à partir d'ici? Eh bien, vous pouvez organiser vos états en régions qui nécessitent une connexion. Vous pouvez exiger des utilisateurs authentifiés / autorisés en ajoutant data
avec roles
à ces états (ou un parent d'entre eux, si vous souhaitez utiliser l'héritage). Ici, nous limitons une ressource aux administrateurs:
.state('restricted', {
parent: 'site',
url: '/restricted',
data: {
roles: ['Admin']
},
views: {
'content@': {
templateUrl: 'restricted.html'
}
}
})
Vous pouvez désormais contrôler état par état quels utilisateurs peuvent accéder à un itinéraire. D'autres soucis? Peut-être varier seulement une partie d'une vue selon qu'ils sont connectés ou non? Aucun problème. Utilisez le principal.isAuthenticated()
ou même principal.isInRole()
avec l'une des nombreuses façons dont vous pouvez conditionnellement afficher un modèle ou un élément.
Tout d'abord, injectez-le principal
dans un contrôleur ou autre, et collez-le sur la portée afin de pouvoir l'utiliser facilement dans votre vue:
.scope('HomeCtrl', ['$scope', 'principal',
function($scope, principal)
{
$scope.principal = principal;
});
Afficher ou masquer un élément:
<div ng-show="principal.isAuthenticated()">
I'm logged in
</div>
<div ng-hide="principal.isAuthenticated()">
I'm not logged in
</div>
Etc., etc., etc. Quoi qu'il en soit, dans votre exemple d'application, vous auriez un état pour la page d'accueil qui permettrait aux utilisateurs non authentifiés de passer. Ils peuvent avoir des liens vers les états de connexion ou d'inscription, ou avoir ces formulaires intégrés dans cette page. Tout ce qui vous convient.
Les pages du tableau de bord peuvent toutes hériter d'un état qui requiert que les utilisateurs soient connectés et, par exemple, User
membres du rôle. Tous les éléments d'autorisation dont nous avons discuté en découleraient.