我正在制作一个更好的演示,并将其中一些服务清理成一个可用的模块,但以下是我的 idea .这是一个复杂的过程,需要解决一些警告,所以要坚持下go .你需要把它分解成几个部分.
Take a look at this plunk
首先,您需要一个服务来存储用户的身份.我称之为principal
.可以判断用户是否已登录,并根据请求解析表示用户身份基本信息的对象.这可以是您需要的任何内容,但基本要素是显示名称、用户名、可能的邮箱,以及用户所属的角色(如果这适用于您的应用程序).Principal也有进行角色判断的方法.
.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;
}
};
}
])
其次,你需要一个服务来判断用户想要进入的状态,确保他们已登录(如果需要;不需要登录、密码重置等),然后进行角色判断(如果你的应用程序需要).如果未通过身份验证,请将其发送到登录页面.如果它们经过身份验证,但角色判断失败,请将它们发送到拒绝访问页面.我称之为服务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');
}
}
});
}
};
}
])
现在你需要做的就是收听ui-router
的$stateChangeStart
.这使您有机会判断当前状态,即他们想要进入的状态,并插入授权判断.如果失败,您可以取消路由转换,或更改为其他路由.
.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();
});
}
]);
跟踪用户身份的棘手之处在于,如果您已经进行了身份验证(例如,您在前一个会话之后访问页面,并将身份验证令牌保存在cookie中,或者您可能硬刷新了页面,或者从链接拖放到URL上),则需要进行查找.由于ui-router
的工作方式,您需要在身份验证判断之前进行一次身份解析.您可以使用状态配置中的resolve
选项来完成此操作.我有一个站点的父状态,所有状态都继承自该父状态,它强制在发生任何其他事情之前解析主体.
$stateProvider.state('site', {
'abstract': true,
resolve: {
authorize: ['authorization',
function(authorization) {
return authorization.authorize();
}
]
},
template: '<div ui-view />'
})
这里还有一个问题...resolve
只会被呼叫一次.一旦完成身份查找promise ,它就不会再次运行resolve委托.因此,我们必须在两个地方对你的身份进行验证:一次是根据你的身份promise 在resolve
年内解析,这涵盖了你的应用程序首次加载的时间;另一次是在$stateChangeStart
年内解析完成的时间,这涵盖了你在各州的任何时间.
好的,到目前为止我们做了什么?
- 如果用户已登录,我们会判断应用程序何时加载.
- 我们跟踪有关登录用户的信息.
- 我们将它们重定向到需要用户登录的状态的登录状态.
- 如果他们没有访问权限,我们会将他们重定向到拒绝访问状态.
- 如果我们需要用户登录,我们有一种机制可以将用户重定向回他们请求的原始状态.
- 我们可以注销用户(需要与管理您的身份验证票证的任何客户端或服务器代码连接).
- 每当用户重新加载浏览器或点击链接时,我们don't都需要将用户送回登录页面.
我们从这里go 哪儿?嗯,您可以将您的州组织成需要登录的区域.您可以通过将data
和roles
添加到这些状态(如果要使用继承,则将它们的父级)添加到这些状态来要求经过身份验证/授权的用户.在这里,我们将资源限制为管理员:
.state('restricted', {
parent: 'site',
url: '/restricted',
data: {
roles: ['Admin']
},
views: {
'content@': {
templateUrl: 'restricted.html'
}
}
})
现在您可以逐个州地控制哪些用户可以访问路由.还有其他顾虑吗?也许只根据他们是否登录来改变视图的一部分?没问题.可以通过多种方式中的任何一种来使用principal.isAuthenticated()
甚至principal.isInRole()
,您可以有条件地显示模板或元素.
首先,将principal
注入控制器或任何东西,并将其固定在范围内,以便在视图中轻松使用:
.scope('HomeCtrl', ['$scope', 'principal',
function($scope, principal)
{
$scope.principal = principal;
});
显示或隐藏元素:
<div ng-show="principal.isAuthenticated()">
I'm logged in
</div>
<div ng-hide="principal.isAuthenticated()">
I'm not logged in
</div>
等等,等等,等等.无论如何,在你的示例应用中,你的主页会有一个状态,允许未经身份验证的用户访问.他们可以有登录或注册状态的链接,或者在该页面中内置这些表单.随便你喜欢什么.
仪表板页面都可以继承自要求用户登录并成为User
角色成员的状态.我们讨论过的所有授权内容都将从那里开始.