首頁 > web前端 > js教程 > 主體

如何使用AngularJS進行身份驗證的程式碼實例詳解

伊谢尔伦
發布: 2017-07-20 10:46:10
原創
1558 人瀏覽過

身份認證

最普遍的身份認證方式就是用使用者名稱(或 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(&#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);
})
登入後複製

我們注意到這裡是缺少實際的邏輯的。這個 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;
})
登入後複製

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;
})
登入後複製

如果你想給 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;
})
登入後複製

為了進一步遠離身分認證的擔憂,我使用另一個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;
})
登入後複製

一旦使用者登入了,他的資訊應該會被展示在某些地方(例如右上角使用者頭像什麼的)。為了實現這個,使用者物件必須要被 $scope 物件引用,更好的是一個可以被全域呼叫的地方。雖然 $rootScope 是顯然易見的第一個選擇,但是我嘗試克制自己,不過度使用 $rootScope(實際上我只在全域事件廣播使用 $rootScope)。用我喜歡的方式去做這個事情,就是在應用的根節點,或者在別的至少高於 Dom 樹的地方,定義一個 controller 。 標籤是個很好的選擇:

<body ng-controller="ApplicationController">
 ...
</body>
登入後複製

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;
 };
})
登入後複製

我們實際上不分配 currentUser 對象,我們僅僅初始化作用域上的屬性以便 currentUser 能在後面被訪問。不幸的是,我們不能簡單地在子作用域分配一個新的值給 currentUser 因為那樣會造成 shadow property。這是用以值傳遞原始型別(strings, numbers, booleans,undefined and null)來取代以引用傳遞原始型別的結果。為了防止 shadow property,我們要使用 setter 函數。如果想了解更多 Angular 作用域和原形繼承,請閱讀 Understanding Scopes。

存取控制

身份認證,也就是存取控制,其實在 AngularJS 並不存在。因為我們是客戶端應用,所有原始碼都在使用者手上。沒有辦法阻止使用者篡改程式碼以獲得認證後的介面。我們能做的只是顯示控制。如果你需要真正的身份認證,你需要在伺服器端做這個事情,但是這個超出了本文範疇。

限制元素的顯示

AngularJS 擁有基於作用域或表達式來控制顯示或隱藏元素的指令: ngShow, ngHide,ngIf 和 ngSwitch。前兩者會使用一個