Sto preparando una demo più bella e ripulendo alcuni di questi servizi in un modulo utilizzabile, ma ecco cosa ho pensato. Questo è un processo complesso per aggirare alcuni avvertimenti, quindi resta lì. Dovrai scomporlo in più pezzi.
Dai un'occhiata a questo plunk .
Innanzitutto, è necessario un servizio per memorizzare l'identità dell'utente. Io chiamo questo principal
. Può essere verificato per vedere se l'utente ha effettuato l'accesso e, su richiesta, può risolvere un oggetto che rappresenta le informazioni essenziali sull'identità dell'utente. Questo può essere ciò di cui hai bisogno, ma gli elementi essenziali sarebbero un nome visualizzato, un nome utente, possibilmente un'e-mail e i ruoli a cui appartiene un utente (se questo si applica alla tua app). Principal ha anche metodi per eseguire i controlli di ruolo.
.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;
}
};
}
])
In secondo luogo, è necessario un servizio che controlli lo stato in cui l'utente vuole recarsi, assicurandosi che siano connessi (se necessario; non necessario per l'accesso, reimpostazione della password, ecc.), Quindi esegue un controllo del ruolo (se l'app ne ha bisogno). Se non sono autenticati, inviarli alla pagina di accesso. Se sono autenticati, ma non riescono a controllare i ruoli, inviarli a una pagina di accesso negato. Chiamo questo servizio 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');
}
}
});
}
};
}
])
Ora tutto quello che dovete fare è ascoltare in su ui-router
s'$stateChangeStart
. Questo ti dà la possibilità di esaminare lo stato corrente, lo stato in cui vogliono andare e inserire il tuo controllo di autorizzazione. In caso contrario, è possibile annullare la transizione del percorso o passare a un percorso diverso.
.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 parte difficile del tracciamento dell'identità di un utente è cercarla se hai già effettuato l'autenticazione (ad esempio, stai visitando la pagina dopo una sessione precedente e hai salvato un token di autenticazione in un cookie, o forse hai aggiornato una pagina o rilasciato su un URL da un collegamento). A causa del modo in cui ui-router
funziona, è necessario che la tua identità venga risolta una volta, prima di verificare l'autenticazione. Puoi farlo usando ilresolve
opzione nella tua configurazione di stato. Ho uno stato genitore per il sito da cui tutti gli stati ereditano, il che forza il principale ad essere risolto prima che accada qualsiasi altra cosa.
$stateProvider.state('site', {
'abstract': true,
resolve: {
authorize: ['authorization',
function(authorization) {
return authorization.authorize();
}
]
},
template: '<div ui-view />'
})
C'è un altro problema qui ... resolve
viene chiamato solo una volta. Una volta completata la promessa di ricerca dell'identità, non verrà eseguito nuovamente il delegato di risoluzione. Quindi dobbiamo fare i tuoi controlli di autenticazione in due posti: una volta in base alla tua promessa di identità risolvendoresolve
, che copre la prima volta che si carica l'app, e una volta dentro $stateChangeStart
se la risoluzione è stata eseguita, che copre ogni volta che si naviga negli stati.
OK, quindi cosa abbiamo fatto finora?
- Verifichiamo quando l'app viene caricata se l'utente ha effettuato l'accesso.
- Tracciamo informazioni sull'utente che ha effettuato l'accesso.
- Li reindirizziamo per accedere allo stato per gli stati che richiedono l'accesso dell'utente.
- Li reindirizziamo a uno stato di accesso negato se non dispongono dell'autorizzazione per accedervi.
- Abbiamo un meccanismo per reindirizzare gli utenti allo stato originale richiesto, se ne avevamo bisogno per accedere.
- Siamo in grado di disconnettere un utente (deve essere collegato di concerto con qualsiasi codice client o server che gestisce il tuo ticket di autenticazione).
- Non è necessario inviare nuovamente gli utenti alla pagina di accesso ogni volta che ricaricano il browser o rilasciano un collegamento.
Dove andiamo da qui? Beh, è possibile organizzare i vostri stati in regioni che necessitano di accedere. E 'possibile richiedere agli utenti autenticati / autorizzati con l'aggiunta data
di roles
questi stati (o un genitore di loro, se si vuole utilizzare l'ereditarietà). Qui, limitiamo una risorsa agli amministratori:
.state('restricted', {
parent: 'site',
url: '/restricted',
data: {
roles: ['Admin']
},
views: {
'content@': {
templateUrl: 'restricted.html'
}
}
})
Ora puoi controllare stato per stato quali utenti possono accedere a un percorso. Altre preoccupazioni? Forse variando solo una parte di una vista in base al fatto che siano o meno connessi? Nessun problema. Usa il principal.isAuthenticated()
o ancheprincipal.isInRole()
con uno dei numerosi modi in cui è possibile visualizzare condizionalmente un modello o un elemento.
Innanzitutto, iniettare principal
in un controller o altro e attenersi all'ambito in modo da poterlo utilizzare facilmente nella vista:
.scope('HomeCtrl', ['$scope', 'principal',
function($scope, principal)
{
$scope.principal = principal;
});
Mostra o nascondi un elemento:
<div ng-show="principal.isAuthenticated()">
I'm logged in
</div>
<div ng-hide="principal.isAuthenticated()">
I'm not logged in
</div>
Ecc., Così via, così via. Ad ogni modo, nella tua app di esempio, avresti uno stato per la home page che consentirebbe agli utenti non autenticati di passare. Potrebbero avere collegamenti agli stati di accesso o di iscrizione o avere quei moduli integrati in quella pagina. Qualunque cosa ti vada bene.
Le pagine del dashboard potrebbero ereditare da uno stato che richiede l'accesso degli utenti e, per esempio, essere un User
membro del ruolo. Tutta la roba di autorizzazione che abbiamo discusso dovrebbe fluire da lì.