Estou no processo de criar uma demonstração melhor, além de limpar alguns desses serviços em um módulo utilizável, mas aqui está o que eu propus. Este é um processo complexo para contornar algumas advertências, então fique aí. Você precisará dividir isso em vários pedaços.
Dê uma olhada neste plunk .
Primeiro, você precisa de um serviço para armazenar a identidade do usuário. Eu chamo isso principal
. Ele pode ser verificado para ver se o usuário está logado e, mediante solicitação, ele pode resolver um objeto que representa as informações essenciais sobre a identidade do usuário. Pode ser o que você precisar, mas o essencial seria um nome para exibição, um nome de usuário, possivelmente um email e as funções às quais um usuário pertence (se isso se aplica ao seu aplicativo). O Principal também possui métodos para fazer verificações de função.
.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;
}
};
}
])
Segundo, você precisa de um serviço que verifique o estado em que o usuário deseja ir, verifique se está logado (se necessário; não necessário para entrar, redefinição de senha etc.) e faça uma verificação de função (se seu aplicativo precisa disso). Se eles não estiverem autenticados, envie-os para a página de entrada. Se eles estiverem autenticados, mas falharem na verificação de uma função, envie-os para uma página de acesso negado. Eu chamo esse serviço 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');
}
}
});
}
};
}
])
Agora tudo que você precisa fazer é ouvir em ui-router
's $stateChangeStart
. Isso permite examinar o estado atual, o estado para o qual eles desejam ir e inserir sua verificação de autorização. Se falhar, você pode cancelar a transição da rota ou mudar para uma rota diferente.
.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();
});
}
]);
A parte complicada de rastrear a identidade de um usuário é procurar se você já se autenticou (por exemplo, você está visitando a página após uma sessão anterior e salvou um token de autenticação em um cookie, ou talvez você tenha atualizado uma página ou caiu para um URL a partir de um link). Por causa da maneira como ui-router
funciona, você precisa resolver sua identidade uma vez, antes de verificar sua autenticação. Você pode fazer isso usando a resolve
opção na sua configuração de estado. Eu tenho um estado pai para o site que todos os estados herdam, o que força o principal a ser resolvido antes que qualquer outra coisa aconteça.
$stateProvider.state('site', {
'abstract': true,
resolve: {
authorize: ['authorization',
function(authorization) {
return authorization.authorize();
}
]
},
template: '<div ui-view />'
})
Há outro problema aqui ... resolve
só é chamado uma vez. Quando sua promessa de pesquisa de identidade for concluída, ela não executará o delegado de resolução novamente. Portanto, precisamos fazer suas verificações de autenticação em dois locais: uma vez de acordo com a promessa de identidade que está sendo resolvida resolve
, que abrange a primeira vez que o aplicativo é carregado e outra$stateChangeStart
se a resolução foi concluída, que abrange qualquer momento em que você navega pelos estados.
OK, então o que fizemos até agora?
- Verificamos quando o aplicativo carrega se o usuário está logado.
- Nós rastreamos informações sobre o usuário conectado.
- Nós os redirecionamos para entrar no estado para estados que exigem que o usuário esteja logado.
- Nós os redirecionamos para um estado de acesso negado se eles não tiverem autorização para acessá-lo.
- Temos um mecanismo para redirecionar os usuários de volta ao estado original que eles solicitaram, se precisávamos deles para fazer login.
- Podemos desconectar um usuário (precisa ser conectado em conjunto com qualquer código de cliente ou servidor que gerencia seu tíquete de autenticação).
- Nós não precisa enviar os usuários de volta para a página de login cada vez que recarregar seu navegador ou cair em um link.
Para onde vamos daqui? Bem, você pode organizar seus estados em regiões que necessitam de sinal. Você pode exigir que os usuários autenticados / autorizados pela adição data
com roles
a estes estados (ou um pai deles, se você quiser usar a herança). Aqui, restringimos um recurso aos administradores:
.state('restricted', {
parent: 'site',
url: '/restricted',
data: {
roles: ['Admin']
},
views: {
'content@': {
templateUrl: 'restricted.html'
}
}
})
Agora você pode controlar estado por estado o que os usuários podem acessar uma rota. Alguma outra preocupação? Talvez variar apenas parte de uma visualização com base em se eles estão ou não conectados? Sem problemas. Use principal.isAuthenticated()
ou mesmo principal.isInRole()
com qualquer uma das várias maneiras pelas quais você pode exibir condicionalmente um modelo ou elemento.
Primeiro, injete principal
em um controlador ou o que seja, e cole-o no escopo para que você possa usá-lo facilmente em sua visão:
.scope('HomeCtrl', ['$scope', 'principal',
function($scope, principal)
{
$scope.principal = principal;
});
Mostrar ou ocultar um elemento:
<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 qualquer forma, em seu aplicativo de exemplo, você teria um estado para a página inicial que permitiria a exibição de usuários não autenticados. Eles podem ter links para os estados de entrada ou inscrição ou ter esses formulários incorporados nessa página. O que mais lhe convier.
Todas as páginas do painel podem herdar de um estado que exige que os usuários estejam conectados e, digamos, sejam User
membros da função. Todo o material de autorização que discutimos fluiria a partir daí.