如何使用AngularJS进行身份验证的代码实例详解

伊谢尔伦
Lepaskan: 2017-07-20 10:46:10
asal
1559 orang telah melayarinya

身份认证

最普遍的身份认证方式就是用用户名(或 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>
Salin selepas log masuk

既然这个是 Angular-powered 的表单,我们使用 ngSubmit 指令去触发上传表单时的函数。注意一点的是,我们把个人信息传入到上传表单的函数,而不是直接使用 $scope.credentials 这个对象。这样使得函数更容易进行 unit-test 和降低这个函数与当前 Controller 作用域的耦合。这个 Controller 看起来是这样的:

.controller(&#39;LoginController&#39;, function ($scope, $rootScope, AUTH_EVENTS, AuthService) {
 $scope.credentials = {
  username: &#39;&#39;,
  password: &#39;&#39;
 };
 $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);
})
Salin selepas log masuk

我们注意到这里是缺少实际的逻辑的。这个 Controller 被做成这样,目的是使身份认证的逻辑跟表单解耦。把逻辑尽可能的从我们的 Controller 里面抽离出来,把他们都放到 services 里面,这是个很好的想法。AngularJS 的 Controller 应该只管理 $scope 里面的对象(用 watching 或者 手动操作)而不是承担过多过分重的东西。

通知 Session 的变化

身份认证会影响整个应用的状态。基于这个原因我更推荐使用事件(用 $broadcast)去通知 user session 的改变。把所有可能用到的事件代码定义在一个中间地带是个不错的选择。我喜欢用 constants 去做这个事情:

.constant(&#39;AUTH_EVENTS&#39;, {
 loginSuccess: &#39;auth-login-success&#39;,
 loginFailed: &#39;auth-login-failed&#39;,
 logoutSuccess: &#39;auth-logout-success&#39;,
 sessionTimeout: &#39;auth-session-timeout&#39;,
 notAuthenticated: &#39;auth-not-authenticated&#39;,
 notAuthorized: &#39;auth-not-authorized&#39;
})
Salin selepas log masuk

constants 有个很好的特性就是他们能随便注入到别的地方,就像 services 那样。这样使得 constants 很容易被我们的 unit-test 调用。constants 也允许你很容易地在随后对他们重命名而不需要改一大串文件。同样的戏法运用到了 user roles:

.constant(&#39;USER_ROLES&#39;, {
 all: &#39;*&#39;,
 admin: &#39;admin&#39;,
 editor: &#39;editor&#39;,
 guest: &#39;guest&#39;
})
Salin selepas log masuk

如果你想给予 editors 和 administrators 同样的权限,你只需要简单地把 ‘editor' 改成 ‘admin'。

The AuthService

与身份认证和授权(访问控制)相关的逻辑最好被放到同一个 service:

.factory(&#39;AuthService&#39;, function ($http, Session) {
 var authService = {};

 authService.login = function (credentials) {
  return $http
   .post(&#39;/login&#39;, 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;
})
Salin selepas log masuk

为了进一步远离身份认证的担忧,我使用另一个 service(一个单例对象,using the service style)去保存用户的 session 信息。session 的信息细节是依赖于后端的实现,但是我还是给出一个较普遍的例子吧:

.service(&#39;Session&#39;, 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;
})
Salin selepas log masuk

一旦用户登录了,他的信息应该会被展示在某些地方(比如右上角用户头像什么的)。为了实现这个,用户对象必须要被 $scope 对象引用,更好的是一个可以被全局调用的地方。虽然 $rootScope 是显然易见的第一个选择,但是我尝试克制自己,不过多地使用 $rootScope(实际上我只在全局事件广播使用 $rootScope)。用我所喜欢的方式去做这个事情,就是在应用的根节点,或者在别的至少高于 Dom 树的地方,定义一个 controller 。 标签是个很好的选择:

<body ng-controller="ApplicationController">
 ...
</body>
Salin selepas log masuk

ApplicationController 是应用的全局逻辑的容器和一个用于运行 Angular 的 run 方法的选择。因此它要处于 $scope 树的根,所有其他的 scope 会继承它(除了隔离 scope)。这是个很好的地方去定义 currentUser 对象:

.controller(&#39;ApplicationController&#39;, function ($scope,
                        USER_ROLES,
                        AuthService) {
 $scope.currentUser = null;
 $scope.userRoles = USER_ROLES;
 $scope.isAuthorized = AuthService.isAuthorized;

 $scope.setCurrentUser = function (user) {
  $scope.currentUser = user;
 };
})
Salin selepas log masuk

我们实际上不分配 currentUser 对象,我们仅仅初始化作用域上的属性以便 currentUser 能在后面被访问到。不幸的是,我们不能简单地在子作用域分配一个新的值给 currentUser 因为那样会造成 shadow property。这是用以值传递原始类型(strings, numbers, booleans,undefined and null)代替以引用传递原始类型的结果。为了防止 shadow property,我们要使用 setter 函数。如果想了解更多 Angular 作用域和原形继承,请阅读 Understanding Scopes。

访问控制

身份认证,也就是访问控制,其实在 AngularJS 并不存在。因为我们是客户端应用,所有源码都在用户手上。没有办法阻止用户篡改代码以获得认证后的界面。我们能做的只是显示控制。如果你需要真正的身份认证,你需要在服务器端做这个事情,但是这个超出了本文范畴。

限制元素的显示

AngularJS 拥有基于作用域或者表达式来控制显示或者隐藏元素的指令: ngShow, ngHide, ngIf 和 ngSwitch。前两个会使用一个