Estoy en el proceso de hacer una demostración más agradable y de limpiar algunos de estos servicios en un módulo utilizable, pero esto es lo que se me ocurrió. Este es un proceso complejo para evitar algunas advertencias, así que aguarde allí. Tendrás que dividir esto en varias partes.
Echa un vistazo a este golpe .
Primero, necesita un servicio para almacenar la identidad del usuario. Yo llamo a esto principal
. Se puede verificar para ver si el usuario ha iniciado sesión y, si lo solicita, puede resolver un objeto que representa la información esencial sobre la identidad del usuario. Esto puede ser lo que necesite, pero lo esencial sería un nombre para mostrar, un nombre de usuario, posiblemente un correo electrónico y los roles a los que pertenece un usuario (si esto se aplica a su aplicación). El director también tiene métodos para hacer verificaciones de roles.
.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;
}
};
}
])
En segundo lugar, necesita un servicio que verifique el estado al que quiere ir el usuario, se asegure de que haya iniciado sesión (si es necesario; no es necesario para iniciar sesión, restablecer la contraseña, etc.) y luego realice una verificación de roles (si su aplicación necesita esto). Si no están autenticados, envíelos a la página de inicio de sesión. Si están autenticados, pero fallan una verificación de roles, envíelos a una página de acceso denegado. Yo llamo a este servicio 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');
}
}
});
}
};
}
])
Ahora todo lo que necesitas hacer es escuchar en ui-router
's $stateChangeStart
. Esto le brinda la oportunidad de examinar el estado actual, el estado al que desean ir e insertar su verificación de autorización. Si falla, puede cancelar la transición de ruta o cambiar a una ruta 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();
});
}
]);
La parte difícil de rastrear la identidad de un usuario es buscarlo si ya se ha autenticado (por ejemplo, está visitando la página después de una sesión anterior y ha guardado un token de autenticación en una cookie, o tal vez ha actualizado una página, o cayó en una URL desde un enlace). Debido a la forma en que ui-router
funciona, debe resolver su identidad una vez, antes de verificar su autenticación. Puede hacerlo utilizando la resolve
opción en la configuración de su estado. Tengo un estado principal para el sitio del que todos los estados heredan, lo que obliga al director a resolverse antes de que ocurra cualquier otra cosa.
$stateProvider.state('site', {
'abstract': true,
resolve: {
authorize: ['authorization',
function(authorization) {
return authorization.authorize();
}
]
},
template: '<div ui-view />'
})
Hay otro problema aquí ... resolve
solo se llama una vez. Una vez que su promesa de búsqueda de identidad se complete, no volverá a ejecutar el delegado de resolución. Por lo tanto, tenemos que hacer sus comprobaciones de autenticación en dos lugares: una vez de acuerdo con la resolución de su promesa de identidad resolve
, que cubre la primera vez que se carga su aplicación, y una vez $stateChangeStart
si la resolución se ha realizado, que cubre cualquier momento que navegue por los estados.
Bien, ¿qué hemos hecho hasta ahora?
- Verificamos cuándo se carga la aplicación si el usuario ha iniciado sesión.
- Rastreamos información sobre el usuario conectado.
- Los redirigimos al estado de inicio de sesión para los estados que requieren que el usuario inicie sesión.
- Los redirigimos a un estado de acceso denegado si no tienen autorización para acceder a él.
- Tenemos un mecanismo para redirigir a los usuarios al estado original que solicitaron, si necesitáramos que inicien sesión.
- Podemos cerrar la sesión de un usuario (debe conectarse por cable con cualquier código de cliente o servidor que administre su ticket de autenticación).
- Nosotros no tenemos que enviar a los usuarios volver a la página de registro cada vez que vuelva a cargar el navegador o la abandonan en un enlace.
¿A dónde vamos desde aquí? Bueno, se puede organizar sus estados en regiones que requieren inicio de sesión. Puede requerir que los usuarios autenticados / autorizados mediante la adición data
de roles
estos estados (o uno de los padres de ellos, si desea utilizar la herencia). Aquí, restringimos un recurso a los administradores:
.state('restricted', {
parent: 'site',
url: '/restricted',
data: {
roles: ['Admin']
},
views: {
'content@': {
templateUrl: 'restricted.html'
}
}
})
Ahora puede controlar estado por estado a qué usuarios pueden acceder una ruta. ¿Alguna otra inquietud? ¿Tal vez variar solo una parte de una vista en función de si están o no conectados? No hay problema. Use principal.isAuthenticated()
o incluso principal.isInRole()
con cualquiera de las numerosas formas en que puede mostrar condicionalmente una plantilla o un elemento.
Primero, inyecte principal
en un controlador o lo que sea, y adhiéralo al alcance para que pueda usarlo fácilmente desde su punto de vista:
.scope('HomeCtrl', ['$scope', 'principal',
function($scope, principal)
{
$scope.principal = principal;
});
Mostrar u ocultar un 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 todos modos, en su aplicación de ejemplo, tendría un estado para la página de inicio que permitiría la visita de usuarios no autenticados. Podrían tener enlaces a los estados de inicio o inicio de sesión, o tener esos formularios integrados en esa página. Lo que te venga bien.
Todas las páginas del panel podrían heredarse de un estado que requiere que los usuarios inicien sesión y, digamos, sean User
miembros del rol. Todas las cosas de autorización que hemos discutido fluirían desde allí.