Je suis nouveau dans AngularJS et je suis un peu confus quant à la façon dont je peux utiliser angular- "ui-router" dans le scénario suivant:
Je construis une application Web qui se compose de deux sections. La première section est la page d'accueil avec ses vues de connexion et d'inscription, et la deuxième section est le tableau de bord (après une connexion réussie).
J'ai créé un index.html
pour la section home avec son application angular et ui-router
config pour gérer /login
et /signup
, et il existe un autre fichier. dashboard.html
pour la section du tableau de bord avec son application et ui-router
config pour gérer de nombreuses sous-vues.
Maintenant, j'ai terminé la section du tableau de bord et je ne sais pas comment combiner les deux sections avec leurs différentes applications angular. Comment pourrais-je dire à l'application d'accueil de rediriger vers l'application du tableau de bord?
Je suis en train de faire une démo plus agréable et de nettoyer certains de ces services pour en faire un module utilisable, mais voici ce que j'ai proposé. Il s'agit d'un processus complexe pour contourner certaines mises en garde, alors accrochez-vous. Vous aurez besoin de le décomposer en plusieurs morceaux.
Tout d'abord, vous avez besoin d'un service pour stocker l'identité de l'utilisateur. J'appelle cela principal
. Vous pouvez vérifier si l'utilisateur est connecté et, sur demande, résoudre un objet représentant les informations essentielles sur l'identité de l'utilisateur. Cela peut être ce dont vous avez besoin, mais l'essentiel serait un nom d'affichage, un nom d'utilisateur, éventuellement un courrier électronique, ainsi que les rôles auxquels un utilisateur appartient (si cela s'applique à votre application). Principal dispose également de méthodes pour effectuer des contrôles 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 auquel l’utilisateur souhaite accéder, s’assure qu’il est connecté (si nécessaire; inutile pour la connexion, la réinitialisation du mot de passe, etc.), puis effectue une vérification des rôles (si votre application est activée). a besoin de cela). S'ils ne sont pas authentifiés, envoyez-les à la page de connexion. S'ils sont authentifiés mais échouent à la vérification du rôle, envoyez-les à 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');
}
}
});
}
};
}
])
_
Il ne vous reste plus qu'à écouter les _ui-router
_ $stateChangeStart
. Cela vous donne l'occasion d'examiner l'état actuel, l'état dans lequel ils souhaitent aller et d'insérer votre contrôle d'autorisation. Si cela échoue, vous pouvez annuler la transition d’itinéraire ou changer d’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 à le 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é une page, ou déposé sur une URL depuis un lien). En raison du fonctionnement de _ui-router
_, vous devez procéder à la résolution de votre identité une fois, avant votre vérification d'authentification. Vous pouvez le faire en utilisant l'option resolve
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 toute autre situation.
_$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 fois. Une fois que votre promesse de recherche d'identité est terminée, le délégué de résolution ne sera plus exécuté. Nous devons donc effectuer vos vérifications d’authentification à deux endroits: une fois conformément à votre promesse d’identité, résolue dans resolve
, qui couvre le premier chargement de votre application, et une fois dans _$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?
Où allons-nous à partir d'ici? Eh bien, vous pouvez organiser vos états en régions nécessitant une connexion. Vous pouvez exiger des utilisateurs authentifiés/autorisés en ajoutant data
avec roles
à ces états (ou à 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 maintenant contrôler, état par état, quels utilisateurs peuvent accéder à un itinéraire. D'autres préoccupations? Peut-être que vous ne modifiez qu'une partie d'une vue en fonction de leur connexion ou non? Aucun problème. Utilisez la principal.isAuthenticated()
ou même principal.isInRole()
avec l’un des nombreux moyens d’afficher de manière conditionnelle un modèle ou un élément.
Commencez par injecter principal
dans un contrôleur ou autre, puis collez-le sur l’oscilloscope pour pouvoir l’utiliser facilement à vos yeux:
_.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. De toute façon, dans votre exemple d'application, vous auriez un état pour la page d'accueil qui laisserait passer des utilisateurs non authentifiés. Ils pourraient avoir des liens vers les états de connexion ou d'inscription, ou intégrer ces formulaires 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, être un membre du rôle User
. Toutes les autorisations dont nous avons parlé découleraient de là.
Les solutions affichées jusqu'ici sont inutilement compliquées, à mon avis. Il y a un moyen plus simple. Le documentation de ui-router
indique d'écouter $locationChangeSuccess
et d'utiliser $urlRouter.sync()
pour vérifier une transition d'état, l'arrêter ou la reprendre. Mais même cela ne fonctionne pas réellement.
Cependant, voici deux alternatives simples. Choisissez-en un:
$locationChangeSuccess
Vous pouvez écouter $locationChangeSuccess
et vous pouvez y exécuter une logique, même asynchrone. Sur la base de cette logique, vous pouvez laisser la fonction retourner non définie, ce qui entraînera la transition d'état comme d'habitude, ou bien vous pouvez utiliser $state.go('logInPage')
si l'utilisateur doit être authentifié. Voici un exemple:
angular.module('App', ['ui.router'])
// In the run phase of your Angular application
.run(function($rootScope, user, $state) {
// Listen to '$locationChangeSuccess', not '$stateChangeStart'
$rootScope.$on('$locationChangeSuccess', function() {
user
.logIn()
.catch(function() {
// log-in promise failed. Redirect to log-in page.
$state.go('logInPage')
})
})
})
N'oubliez pas que cela n'empêche pas le chargement de l'état cible, mais qu'il redirige vers la page de connexion si l'utilisateur n'est pas autorisé. Ce n'est pas grave, car la véritable protection est sur le serveur.
resolve
Dans cette solution, vous utilisez ui-router
fonctionnalité de résolution .
En gros, vous refusez la promesse dans resolve
si l'utilisateur n'est pas authentifié, puis vous les redirigez vers la page de connexion.
Voici comment ça se passe:
angular.module('App', ['ui.router'])
.config(
function($stateProvider) {
$stateProvider
.state('logInPage', {
url: '/logInPage',
templateUrl: 'sections/logInPage.html',
controller: 'logInPageCtrl',
})
.state('myProtectedContent', {
url: '/myProtectedContent',
templateUrl: 'sections/myProtectedContent.html',
controller: 'myProtectedContentCtrl',
resolve: { authenticate: authenticate }
})
.state('alsoProtectedContent', {
url: '/alsoProtectedContent',
templateUrl: 'sections/alsoProtectedContent.html',
controller: 'alsoProtectedContentCtrl',
resolve: { authenticate: authenticate }
})
function authenticate($q, user, $state, $timeout) {
if (user.isAuthenticated()) {
// Resolve the promise successfully
return $q.when()
} else {
// The next bit of code is asynchronously tricky.
$timeout(function() {
// This code runs after the authentication promise has been rejected.
// Go to the log-in page
$state.go('logInPage')
})
// Reject the authentication promise to prevent the state from loading
return $q.reject()
}
}
}
)
Contrairement à la première solution, cette solution empêche en réalité le chargement de l'état cible.
La solution la plus simple consiste à utiliser $stateChangeStart
et event.preventDefault()
pour annuler le changement d'état lorsque l'utilisateur n'est pas authentifié et le rediriger vers l'état auth qui correspond à la page de connexion.
angular
.module('myApp', [
'ui.router',
])
.run(['$rootScope', 'User', '$state',
function ($rootScope, User, $state) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
if (toState.name !== 'auth' && !User.authenticaded()) {
event.preventDefault();
$state.go('auth');
}
});
}]
);
Je pense que vous avez besoin d’un service
qui gère le processus d’authentification (et son stockage).
Dans ce service, vous aurez besoin de méthodes de base:
isAuthenticated()
login()
logout()
Ce service devrait être injecté dans vos contrôleurs de chaque module:
service.isAuthenticated()
). sinon, rediriger vers/loginservice.login()
.Le projet angular-app et plus particulièrement module de sécurité , basé sur le génial HTTP Auth Interceptor Module , constitue un bon exemple de ce comportement.
J'espère que cela t'aides
J'ai créé ce module pour aider à rendre ce processus un morceau de gâteau
Vous pouvez faire des choses comme:
$routeProvider
.state('secret',
{
...
permissions: {
only: ['admin', 'god']
}
});
Ou aussi
$routeProvider
.state('userpanel',
{
...
permissions: {
except: ['not-logged-in']
}
});
C'est tout neuf mais mérite le détour!
Je voulais partager une autre solution fonctionnant avec le routeur ui 1.0.0.X
Comme vous le savez peut-être, stateChangeStart et stateChangeSuccess sont maintenant obsolètes. https://github.com/angular-ui/ui-router/issues/2655
Au lieu de cela, vous devriez utiliser $ transitions http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html
Voici comment je l'ai réalisé:
D'abord j'ai et AuthService avec quelques fonctions utiles
angular.module('myApp')
.factory('AuthService',
['$http', '$cookies', '$rootScope',
function ($http, $cookies, $rootScope) {
var service = {};
// Authenticates throug a rest service
service.authenticate = function (username, password, callback) {
$http.post('api/login', {username: username, password: password})
.success(function (response) {
callback(response);
});
};
// Creates a cookie and set the Authorization header
service.setCredentials = function (response) {
$rootScope.globals = response.token;
$http.defaults.headers.common['Authorization'] = 'Bearer ' + response.token;
$cookies.put('globals', $rootScope.globals);
};
// Checks if it's authenticated
service.isAuthenticated = function() {
return !($cookies.get('globals') === undefined);
};
// Clear credentials when logout
service.clearCredentials = function () {
$rootScope.globals = undefined;
$cookies.remove('globals');
$http.defaults.headers.common.Authorization = 'Bearer ';
};
return service;
}]);
Ensuite, j'ai cette configuration:
angular.module('myApp', [
'ui.router',
'ngCookies'
])
.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/resumen');
$stateProvider
.state("dashboard", {
url: "/dashboard",
templateUrl: "partials/dashboard.html",
controller: "dashCtrl",
data: {
authRequired: true
}
})
.state("login", {
url: "/login",
templateUrl: "partials/login.html",
controller: "loginController"
})
}])
.run(['$rootScope', '$transitions', '$state', '$cookies', '$http', 'AuthService',
function ($rootScope, $transitions, $state, $cookies, $http, AuthService) {
// keep user logged in after page refresh
$rootScope.globals = $cookies.get('globals') || {};
$http.defaults.headers.common['Authorization'] = 'Bearer ' + $rootScope.globals;
$transitions.onStart({
to: function (state) {
return state.data != null && state.data.authRequired === true;
}
}, function () {
if (!AuthService.isAuthenticated()) {
return $state.target("login");
}
});
}]);
Vous pouvez voir que j'utilise
data: {
authRequired: true
}
pour marquer l'état accessible uniquement si est authentifié.
puis, sur le . run , j'utilise les transitions pour vérifier l'état de l'autheticated
$transitions.onStart({
to: function (state) {
return state.data != null && state.data.authRequired === true;
}
}, function () {
if (!AuthService.isAuthenticated()) {
return $state.target("login");
}
});
Je construis cet exemple en utilisant du code trouvé dans la documentation de $ transitions. Je suis assez nouveau avec le routeur UI, mais cela fonctionne.
J'espère que ça peut aider n'importe qui.
Voici comment nous sommes sortis de la boucle de routage infinie et avons toujours utilisé $state.go
au lieu de $location.path
if('401' !== toState.name) {
if (principal.isIdentityResolved()) authorization.authorize();
}
J'ai une autre solution: cette solution fonctionne parfaitement lorsque vous avez uniquement le contenu à afficher lorsque vous êtes connecté. Définissez une règle permettant de vérifier si vous êtes connecté et son chemin d'accès aux itinéraires de liste blanche.
$urlRouterProvider.rule(function ($injector, $location) {
var UserService = $injector.get('UserService');
var path = $location.path(), normalized = path.toLowerCase();
if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
$location.path('/login/signin');
}
});
Dans mon exemple, je demande si je ne suis pas connecté et si l'itinéraire actuel que je souhaite acheminer ne fait pas partie de `/ login ', car mes itinéraires dans la liste blanche sont les suivants:
/login/signup // registering new user
/login/signin // login to app
j'ai donc un accès instantané à ces deux itinéraires et tous les autres itinéraires seront vérifiés si vous êtes en ligne.
Voici tout mon fichier de routage pour le module de connexion
export default (
$stateProvider,
$locationProvider,
$urlRouterProvider
) => {
$stateProvider.state('login', {
parent: 'app',
url: '/login',
abstract: true,
template: '<ui-view></ui-view>'
})
$stateProvider.state('signin', {
parent: 'login',
url: '/signin',
template: '<login-signin-directive></login-signin-directive>'
});
$stateProvider.state('lock', {
parent: 'login',
url: '/lock',
template: '<login-lock-directive></login-lock-directive>'
});
$stateProvider.state('signup', {
parent: 'login',
url: '/signup',
template: '<login-signup-directive></login-signup-directive>'
});
$urlRouterProvider.rule(function ($injector, $location) {
var UserService = $injector.get('UserService');
var path = $location.path();
if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
$location.path('/login/signin');
}
});
$urlRouterProvider.otherwise('/error/not-found');
}
() => { /* code */ }
est la syntaxe ES6, utilisez à la place function() { /* code */ }
Tout d'abord, vous aurez besoin d'un service que vous pouvez injecter dans vos contrôleurs et qui donne une idée de l'état d'authentification de l'application. La persistance des détails d'authentification avec le stockage local est une façon décente de s'en approcher.
Ensuite, vous devrez vérifier l'état de l'authentification juste avant que l'état ne change. Dans la mesure où votre application contient des pages à authentifier et d'autres non, créez un itinéraire parent vérifiant l'authentification et définissez toutes les autres pages le nécessitant comme un enfant de ce parent.
Enfin, vous aurez besoin d'un moyen de savoir si votre utilisateur actuellement connecté peut effectuer certaines opérations. Ceci peut être réalisé en ajoutant une fonction 'can' à votre service d'authentification. Can prend deux paramètres: - action - obligatoire - (c'est-à-dire "manage_dashboards" ou "create_new_dashboard") - objet - facultatif - objet en cours d'utilisation. Par exemple, si vous avez un objet de tableau de bord, vous souhaiterez peut-être vérifier si dashboard.ownerId === logsInUser.id. (Bien sûr, les informations transmises par le client ne doivent jamais être approuvées et vous devez toujours vérifier cela sur le serveur avant de les écrire dans votre base de données).
angular.module('myApp', ['ngStorage']).config([
'$stateProvider',
function(
$stateProvider
) {
$stateProvider
.state('home', {...}) //not authed
.state('sign-up', {...}) //not authed
.state('login', {...}) //not authed
.state('authed', {...}) //authed, make all authed states children
.state('authed.dashboard', {...})
}])
.service('context', [
'$localStorage',
function(
$localStorage
) {
var _user = $localStorage.get('user');
return {
getUser: function() {
return _user;
},
authed: function() {
return (_user !== null);
},
// server should return some kind of token so the app
// can continue to load authenticated content without having to
// re-authenticate each time
login: function() {
return $http.post('/login.json').then(function(reply) {
if (reply.authenticated === true) {
$localStorage.set(_userKey, reply.user);
}
});
},
// this request should expire that token, rendering it useless
// for requests outside of this session
logout: function() {
return $http.post('logout.json').then(function(reply) {
if (reply.authenticated === true) {
$localStorage.set(_userKey, reply.user);
}
});
},
can: function(action, object) {
if (!this.authed()) {
return false;
}
var user = this.getUser();
if (user && user.type === 'admin') {
return true;
}
switch(action) {
case 'manage_dashboards':
return (user.type === 'manager');
}
return false;
}
}
}])
.controller('AuthCtrl', [
'context',
'$scope',
function(
context,
$scope
) {
$scope.$root.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
//only require auth if we're moving to another authed page
if (toState && toState.name.indexOf('authed') > -1) {
requireAuth();
}
});
function requireAuth() {
if (!context.authed()) {
$state.go('login');
}
}
}]
** AVERTISSEMENT: Le code ci-dessus est un pseudo-code et ne comporte aucune garantie **
Utilisez $ http Interceptor
En utilisant un intercepteur $ http, vous pouvez envoyer des en-têtes à Back-end ou inversement et effectuer vos vérifications de cette façon.
Excellent article sur $ http interceptors
Exemple:
$httpProvider.interceptors.Push(function ($q) {
return {
'response': function (response) {
// TODO Create check for user authentication. With every request send "headers" or do some other check
return response;
},
'responseError': function (reject) {
// Forbidden
if(reject.status == 403) {
console.log('This page is forbidden.');
window.location = '/';
// Unauthorized
} else if(reject.status == 401) {
console.log("You're not authorized to view this page.");
window.location = '/';
}
return $q.reject(reject);
}
};
});
Mettez ceci dans votre fonction .config ou .run.