在web中很多时候都能应用到身份认证,本文介绍了AngularJS 应用身份认证的技巧,废话不多说了一起往下看吧。
身份认证
最普遍的身份认证方式就是用用户名(或 email)和密码做登陆操作。这就意味要实现一个登陆的表单,以便用户能够用他们个人信息登陆。这个表单看起来是这样的:
<form name="loginForm" ng-controller="LoginController"
ng-submit="login(credentials)" novalidate>
<label for="username">Username:</label>
<input type="text" id="username"
ng-model="credentials.username">
<label for="password">Password:</label>
<input type="password" id="password"
ng-model="credentials.password">
<button type="submit">Login</button>
</form>
既然这个是 Angular-powered 的表单,我们使用 ngSubmit 指令去触发上传表单时的函数。注意一点的是,我们把个人信息传入到上传表单的函数,而不是直接使用 $scope.credentials 这个对象。这样使得函数更容易进行 unit-test 和降低这个函数与当前 Controller 作用域的耦合。这个 Controller 看起来是这样的:
.controller('LoginController', function ($scope, $rootScope, AUTH_EVENTS, AuthService) {
$scope.credentials = {
username: '',
password: ''
};
$scope.login = function (credentials) {
AuthService.login(credentials).then(function (user) {
$rootScope.$broadcast(AUTH_EVENTS.loginSuccess);
$scope.setCurrentUser(user);
}, function () {
$rootScope.$broadcast(AUTH_EVENTS.loginFailed);
});
};javascript:void(0);
})
我们注意到这里是缺少实际的逻辑的。这个 Controller 被做成这样,目的是使身份认证的逻辑跟表单解耦。把逻辑尽可能的从我们的 Controller 里面抽离出来,把他们都放到 services 里面,这是个很好的想法。AngularJS 的 Controller 应该只管理 $scope 里面的对象(用 watching 或者 手动操作)而不是承担过多过分重的东西。
通知 Session 的变化
身份认证会影响整个应用的状态。基于这个原因我更推荐使用事件(用 $broadcast)去通知 user session 的改变。把所有可能用到的事件代码定义在一个中间地带是个不错的选择。我喜欢用 constants 去做这个事情:
.constant('AUTH_EVENTS', {
loginSuccess: 'auth-login-success',
loginFailed: 'auth-login-failed',
logoutSuccess: 'auth-logout-success',
sessionTimeout: 'auth-session-timeout',
notAuthenticated: 'auth-not-authenticated',
notAuthorized: 'auth-not-authorized'
})
constants 有个很好的特性就是他们能随便注入到别的地方,就像 services 那样。这样使得 constants 很容易被我们的 unit-test 调用。constants 也允许你很容易地在随后对他们重命名而不需要改一大串文件。同样的戏法运用到了 user roles:
.constant('USER_ROLES', {
all: '*',
admin: 'admin',
editor: 'editor',
guest: 'guest'
})
如果你想给予 editors 和 administrators 同样的权限,你只需要简单地把 ‘editor' 改成 ‘admin'。
The AuthService
与身份认证和授权(访问控制)相关的逻辑最好被放到同一个 service:
.factory('AuthService', function ($http, Session) {
var authService = {};
authService.login = function (credentials) {
return $http
.post('/login', credentials)
.then(function (res) {
Session.create(res.data.id, res.data.user.id,
res.data.user.role);
return res.data.user;
});
};
authService.isAuthenticated = function () {
return !!Session.userId;
};
authService.isAuthorized = function (authorizedRoles) {
if (!angular.isArray(authorizedRoles)) {
authorizedRoles = [authorizedRoles];
}
return (authService.isAuthenticated() &&
authorizedRoles.indexOf(Session.userRole) !== -1);
};
return authService;
})
为了进一步远离身份认证的担忧,我使用另一个 service(一个单例对象,using the service style)去保存用户的 session 信息。session 的信息细节是依赖于后端的实现,但是我还是给出一个较普遍的例子吧:
.service('Session', function () {
this.create = function (sessionId, userId, userRole) {
this.id = sessionId;
this.userId = userId;
this.userRole = userRole;
};
this.destroy = function () {
this.id = null;
this.userId = null;
this.userRole = null;
};
return this;
})
一旦用户登录了,他的信息应该会被展示在某些地方(比如右上角用户头像什么的)。为了实现这个,用户对象必须要被 $scope 对象引用,更好的是一个可以被全局调用的地方。虽然 $rootScope 是显然易见的第一个选择,但是我尝试克制自己,不过多4(7b3>RrcZ"ZjRgbǎ^;jf碾wrA$"[j^gbvGJJ3~;!QQ@j^^"Gr{*."f4(4(l4(4(Р%t%4(UQ!}Y9QL(UQ!}Y9QL4(UQ!}Y9QL镐4(UQ!}Y9QLQ4(UQ!}Y9QLQ4(С4(b."fj6W{:r'jrkn3jr7*Snx!QQ@Rg^R"j"R"7fWB;4(碾w24(4(GrR"7Zj>bǒ7j3rZWbWfW碾w3bfWv碾wrnGBJ0Q>G碾w&Oh4(4(UQ!}Y9QL4(耜4(p4(聙4(UQ!}Y9QLUQ!}Y9QLQ4(4(>sg碾w>j?jwb7Rcrjf6Wv
r?vgj4(4(A4(ΣD^^"Grfvb碾wk3b"G*+f碾wRfvj:n&r
3kAbB#Bj4(cR"*4(4(rR""Zjvw^fjR"b6WR3v.2:*n&r'*r"Z"^cGk{:Sn{fj7R"jV6jA$S3A$LRB*SrqtB;R"V6rMr7*"[ǖ?R"fB;j*"[>*+R"V6n:"gRw[j7Z?b*+R"V6cr"[1M_f["V6>cj
4(B;4(4(g&7Z?
?3gbjZr'4(4(brZj3ro疒j惚r'&*rokkR2 |