这是另一种可能的解决方案,使用$stateProvider
或$routeProvider
的resolve
属性。例如用$stateProvider
:
.config(["$stateProvider", function ($stateProvider) {
$stateProvider
.state("forbidden", {
/* ... */
})
.state("signIn", {
/* ... */
resolve: {
access: ["Access", function (Access) { return Access.isAnonymous(); }],
}
})
.state("home", {
/* ... */
resolve: {
access: ["Access", function (Access) { return Access.isAuthenticated(); }],
}
})
.state("admin", {
/* ... */
resolve: {
access: ["Access", function (Access) { return Access.hasRole("ROLE_ADMIN"); }],
}
});
}])
Access
做出决议或拒绝根据当前用户权限的承诺:
.factory("Access", ["$q", "UserProfile", function ($q, UserProfile) {
var Access = {
OK: 200,
// "we don't know who you are, so we can't say if you're authorized to access
// this resource or not yet, please sign in first"
UNAUTHORIZED: 401,
// "we know who you are, and your profile does not allow you to access this resource"
FORBIDDEN: 403,
hasRole: function (role) {
return UserProfile.then(function (userProfile) {
if (userProfile.$hasRole(role)) {
return Access.OK;
} else if (userProfile.$isAnonymous()) {
return $q.reject(Access.UNAUTHORIZED);
} else {
return $q.reject(Access.FORBIDDEN);
}
});
},
hasAnyRole: function (roles) {
return UserProfile.then(function (userProfile) {
if (userProfile.$hasAnyRole(roles)) {
return Access.OK;
} else if (userProfile.$isAnonymous()) {
return $q.reject(Access.UNAUTHORIZED);
} else {
return $q.reject(Access.FORBIDDEN);
}
});
},
isAnonymous: function() {
return UserProfile.then(function (userProfile) {
if (userProfile.$isAnonymous()) {
return Access.OK;
} else {
return $q.reject(Access.FORBIDDEN);
}
});
},
isAuthenticated: function() {
return UserProfile.then(function (userProfile) {
if (userProfile.$isAuthenticated()) {
return Access.OK;
} else {
return $q.reject(Access.UNAUTHORIZED);
}
});
}
};
return Access;
}])
UserProfile
复制当前用户的属性,并执行$hasRole
,$hasAnyRole
,$isAnonymous
和$isAuthenticated
方法逻辑(加上$refresh
方法,稍后解释):
.factory("UserProfile", ["Auth", function (Auth) {
var userProfile = {};
var clearUserProfile = function() {
for (var prop in userProfile) {
if (userProfile.hasOwnProperty(prop)) {
delete userProfile[prop];
}
}
};
var fetchUserProfile = function() {
return Auth.getProfile().then(function (response) {
clearUserProfile();
return angular.extend(userProfile, response.data, {
$refresh: fetchUserProfile,
$hasRole: function (role) {
return userProfile.roles.indexOf(role) >= 0;
},
$hasAnyRole: function (roles) {
return !!userProfile.roles.filter(function (role) {
return roles.indexOf(role) >= 0;
}).length;
},
$isAnonymous: function() {
return userProfile.anonymous;
},
$isAuthenticated: function() {
return !userProfile.anonymous;
}
});
});
};
return fetchUserProfile();
}])
Auth
负责请求服务器,知道用户简档(与连接到例如该请求接入令牌)的:
.service("Auth", ["$http", function ($http) {
this.getProfile = function() {
return $http.get("api/auth");
};
}])
服务器预期返回这样一个JSON对象请求时GET api/auth
:
{
"name": "John Doe", // plus any other user information
"roles": ["ROLE_ADMIN", "ROLE_USER"], // or any other role (or no role at all, i.e. an empty array)
"anonymous": false // or true
}
最后,当Access
拒绝一个承诺,如果使用ui.router
,该$stateChangeError
事件将被解雇:
.run(["$rootScope", "Access", "$state", "$log", function ($rootScope, Access, $state, $log) {
$rootScope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) {
switch (error) {
case Access.UNAUTHORIZED:
$state.go("signIn");
break;
case Access.FORBIDDEN:
$state.go("forbidden");
break;
default:
$log.warn("$stateChangeError event catched");
break;
}
});
}])
如果使用ngRoute
,所述$routeChangeError
事件将被触发:
.run(["$rootScope", "Access", "$location", "$log", function ($rootScope, Access, $location, $log) {
$rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
switch (rejection) {
case Access.UNAUTHORIZED:
$location.path("/signin");
break;
case Access.FORBIDDEN:
$location.path("/forbidden");
break;
default:
$log.warn("$stateChangeError event catched");
break;
}
});
}])
用户简档还可以在控制器访问:
.state("home", {
/* ... */
controller: "HomeController",
resolve: {
userProfile: "UserProfile"
}
})
UserProfile
于是包含返回的属性由服务器在请求时提供GET api/auth
:
要刷新种
.controller("HomeController", ["$scope", "userProfile", function ($scope, userProfile) {
$scope.title = "Hello " + userProfile.name; // "Hello John Doe" in the example
}])
UserProfile
需求,当用户迹象或缩小,从而使Access
可以处理与新的用户配置文件的路径。您可以重新加载整个页面,或致电UserProfile.$refresh()
。
.service("Auth", ["$http", function ($http) {
/* ... */
this.signIn = function (credentials) {
return $http.post("api/auth", credentials).then(function (response) {
// authentication succeeded, store the response access token somewhere (if any)
});
};
}])
.state("signIn", {
/* ... */
controller: "SignInController",
resolve: {
/* ... */
userProfile: "UserProfile"
}
})
.controller("SignInController", ["$scope", "$state", "Auth", "userProfile", function ($scope, $state, Auth, userProfile) {
$scope.signIn = function() {
Auth.signIn($scope.credentials).then(function() {
// user successfully authenticated, refresh UserProfile
return userProfile.$refresh();
}).then(function() {
// UserProfile is refreshed, redirect user somewhere
$state.go("home");
});
};
}])
好的。什么应该在我的loginController?我的意思是我应该设置什么? – iCode
loginController将允许用户从登录页面登录。它将处理登录表单。表单必须调用一个submit方法,它是loginController的一部分。此方法将更新(如果表单正确且用户必须登录)使用我描述的Auth服务的用户的状态。 – gab
对不起,您可以展示如何编写该服务以及如何在LoginController中设置该服务? – iCode