Je suis en train de faire une démo plus jolie et de nettoyer certains de ces services pour en faire un module utilisable, mais voici ce que j'ai trouvé. Il s'agit d'un processus complexe pour contourner certains obstacles, alors accrochez-vous. Vous devrez décomposer le tout en plusieurs parties.
Jetez un coup d'œil à ce plunk .
Tout d'abord, vous avez besoin d'un service pour stocker l'identité de l'utilisateur. J'appelle ce service 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. Cet objet peut être ce dont vous avez besoin, mais l'essentiel serait un nom d'affichage, un nom d'utilisateur, éventuellement un courriel, et les rôles auxquels l'utilisateur appartient (si cela s'applique à votre application). Principal dispose également de méthodes pour vérifier les rôles.
.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. S'ils ne sont pas authentifiés, envoyez-les à la page de connexion. S'il est authentifié, mais que la vérification de son rôle échoue, il est renvoyé à une page de refus d'accès. 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');
}
}
});
}
};
}
])
Il ne vous reste plus qu'à écouter l'émission ui-router
's $stateChangeStart
. Cela vous donne l'occasion d'examiner l'état actuel, l'état auquel ils veulent aller, et d'insérer votre contrôle d'autorisation. En cas d'échec, vous pouvez annuler la transition d'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, si vous visitez la page après une session précédente et que vous avez enregistré un jeton d'authentification dans un cookie, ou si vous avez actualisé la page ou si vous êtes tombé sur l'URL à partir d'un lien). En raison de la façon dont ui-router
fonctionne, vous devez résoudre votre problème d'identité une fois, avant vos vérifications d'identité. Vous pouvez le faire en utilisant la fonction resolve
dans la configuration de votre état. J'ai un état parent pour le site dont tous les états héritent, ce qui force le principal à être résolu avant toute autre chose.
$stateProvider.state('site', {
'abstract': true,
resolve: {
authorize: ['authorization',
function(authorization) {
return authorization.authorize();
}
]
},
template: '<div ui-view />'
})
Il y a un autre problème... resolve
n'est appelé qu'une seule fois. Une fois que votre promesse de recherche d'identité est terminée, le délégué resolve ne sera plus exécuté. Nous devons donc effectuer les vérifications d'authentification à deux endroits : une fois en vertu de votre promesse d'identité qui se résout en resolve
qui couvre la première fois que votre application se charge, et une fois en $stateChangeStart
si la résolution a été effectuée, ce qui couvre tout le temps où vous naviguez dans les États.
Alors, qu'avons-nous fait jusqu'à présent ?
- Nous vérifions, lors du chargement de l'application, si l'utilisateur est connecté.
- Nous enregistrons des informations sur l'utilisateur connecté.
- Nous les redirigeons vers l'état de connexion pour les états qui exigent que l'utilisateur soit connecté.
- Nous les redirigeons vers un état de refus d'accès s'ils n'ont pas l'autorisation d'y accéder.
- Nous disposons d'un mécanisme permettant de rediriger les utilisateurs vers l'état initial qu'ils ont demandé, si nous avons besoin qu'ils se connectent.
- Nous pouvons signer la sortie d'un utilisateur (il faut le faire de concert avec tout code client ou serveur qui gère votre ticket d'authentification).
- Nous ne de renvoyer les utilisateurs à la page de connexion chaque fois qu'ils rechargent leur navigateur ou qu'ils cliquent sur un lien.
Quelle est la suite des événements ? Vous pouvez organiser vos États en régions qui requièrent une connexion. Vous pouvez exiger des utilisateurs authentifiés/autorisés en ajoutant data
con roles
à ces états (ou à l'un de leurs parents, 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, les utilisateurs qui peuvent accéder à un itinéraire. D'autres questions se posent ? Peut-être faire varier une partie seulement d'une vue selon que l'utilisateur est connecté ou non ? Aucun problème. Utilisez la fonction principal.isAuthenticated()
ou même principal.isInRole()
avec l'une des nombreuses façons d'afficher conditionnellement un modèle ou un élément.
Tout d'abord, injecter principal
dans un contrôleur ou autre, et le coller au scope pour 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. 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 s'y rendre. Ils pourraient avoir des liens vers les états d'inscription ou d'enregistrement, ou avoir ces formulaires intégrés dans cette page. Tout ce qui vous convient.
Les pages du tableau de bord pourraient toutes hériter d'un état qui exige que l'utilisateur soit connecté et, par exemple, qu'il soit un User
membre du rôle. Toutes les autorisations dont nous avons parlé en découlent.
1 votes
Pouvez-vous partager un peu de code avec nous ?
6 votes
@Chancho Je pense que ce n'est pas une question de code, vraiment je ne sais pas quel code je devrais partager.
0 votes
Oui, merci de partager le code, question très générique...