"/> ">

ホームページ >ウェブフロントエンド >jsチュートリアル >認証に AngularJS を使用する方法の詳細なコード例

認証に AngularJS を使用する方法の詳細なコード例

伊谢尔伦
伊谢尔伦オリジナル
2017-07-20 10:46:101603ブラウズ

ID 認証

最も一般的な ID 認証方法は、ユーザー名 (または電子メール) とパスワードを使用してログインすることです。これは、ユーザーが個人情報を使用してログインできるようにログイン フォームを実装することを意味します。フォームは次のようになります:

<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 を利用したフォームであるため、フォームをアップロードするときに ngSubmit ディレクティブを使用して関数をトリガーします。注意すべき点の 1 つは、$scope.credentials オブジェクトを直接使用するのではなく、個人情報をアップロード フォーム関数に渡すことです。これにより、関数の単体テストが容易になり、現在のコントローラー スコープへの関数の結合が軽減されます。コントローラーは次のようになります:

.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);
})

ここには実際のロジックが欠如していることに気付きました。このコントローラーは、認証ロジックをフォームから切り離すためにこのように作成されています。コントローラーからできるだけ多くのロジックを抽出し、それをすべてサービスに配置することをお勧めします。 AngularJS のコントローラーは、過度に重いタスクを引き受けるのではなく、(監視または手動操作を使用して) $scope 内のオブジェクトのみを管理する必要があります。

セッション変更の通知

ID認証はアプリケーション全体の状態に影響します。このため、私はイベント ($broadcast を使用) を使用してユーザー セッションの変更を通知することを好みます。考えられるすべてのイベント コードを中間点で定義することをお勧めします。私はこれを行うために定数を使用するのが好きです:

.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;
})

定数の良い特徴は、サービスと同じように、自由に他の場所に注入できることです。これにより、単体テストで定数を簡単に呼び出すことができるようになります。定数を使用すると、大量のファイルを変更しなくても、後で簡単に名前を変更できます。同じトリックがユーザーの役割でも機能します:

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

編集者と管理者に同じ権限を与えたい場合は、「editor」を「admin」に変更するだけです。

AuthService

認証と承認 (アクセス制御) に関連するロジックは、同じサービスに配置するのが最適です:

.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(&#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 ツリーより上位のどこかにコントローラーを定義することです。 タグは良い選択です:

<body ng-controller="ApplicationController">
 ...
</body>

ApplicationController はアプリケーションのグローバル ロジックのコンテナーであり、Angular の run メソッドを実行するためのオプションです。したがって、これは $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 に単純に新しい値を割り当てることはできません。これは、プリミティブ型 (文字列、数値、ブール値、未定義、null) を参照ではなく値で渡した結果です。シャドウプロパティを防ぐには、セッター関数を使用する必要があります。 Angular スコープとプロトタイプの継承について詳しく知りたい場合は、「スコープについて」を参照してください。

アクセス制御

ID認証、つまりアクセス制御は、実はAngularJSには存在しません。私たちはクライアント アプリケーションであるため、すべてのソース コードはユーザーの手にあります。ユーザーがコードを改ざんして認証されたインターフェイスを取得することを防ぐ方法はありません。私たちにできることはコントロールを表示することだけです。真の認証が必要な場合は、サーバー側で認証を行う必要がありますが、それはこの記事の範囲外です。

要素の表示を制限する

AngularJS には、スコープまたは式に基づいて要素の表示または非表示を制御するディレクティブ (ngShow、ngHide、ngIf、ngSwitch) があります。最初の 2 つは c9ccee2e6ea535a969eb3f532ad9fe89 属性を使用して要素を非表示にしますが、最後の 2 つは DOM から要素を削除します。

第一种方式,也就是隐藏元素,最好用于表达式频繁改变并且没有包含过多的模板逻辑和作用域引用的元素上。原因是在隐藏的元素里,这些元素的模板逻辑仍然会在每个 digest 循环里重新计算,使得应用性能下降。第二种方式,移除元素,也会移除所有在这个元素上的 handler 和作用域绑定。改变 DOM 对于浏览器来说是很大工作量的(在某些场景,和 ngShow/ngHide 对比),但是在很多时候这种代价是值得的。因为用户访问信息不会经常改变,使用 ngIf 或 ngShow 是最好的选择:

<p ng-if="currentUser">Welcome, {{ currentUser.name }}</p>
<p ng-if="isAuthorized(userRoles.admin)">You&#39;re admin.</p>
<p ng-switch on="currentUser.role">
 <p ng-switch-when="userRoles.admin">You&#39;re admin.</p>
 <p ng-switch-when="userRoles.editor">You&#39;re editor.</p>
 <p ng-switch-default>You&#39;re something else.</p>
</p>

限制路由访问

很多时候你会想让整个网页都不能被访问,而不是仅仅隐藏一个元素。如果可以再路由(在UI Router 里,路由也叫状态)使用一种自定义的数据结构,我们就可以明确哪些用户角色可以被允许访问哪些内容。下面这个例子使用 UI Router 的风格,但是这些同样适用于 ngRoute。

.config(function ($stateProvider, USER_ROLES) {
 $stateProvider.state(&#39;dashboard&#39;, {
  url: &#39;/dashboard&#39;,
  templateUrl: &#39;dashboard/index.html&#39;,
  data: {
   authorizedRoles: [USER_ROLES.admin, USER_ROLES.editor]
  }
 });
})

下一步,我们需要检查每次路由变化(就是用户跳转到其他页面的时候)。这需要监听 $routeChangStart(ngRoute 里的)或者 $stateChangeStart(UI Router 里的)事件:

.run(function ($rootScope, AUTH_EVENTS, AuthService) {
 $rootScope.$on(&#39;$stateChangeStart&#39;, function (event, next) {
  var authorizedRoles = next.data.authorizedRoles;
  if (!AuthService.isAuthorized(authorizedRoles)) {
   event.preventDefault();
   if (AuthService.isAuthenticated()) {
    // user is not allowed
    $rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
   } else {
    // user is not logged in
    $rootScope.$broadcast(AUTH_EVENTS.notAuthenticated);
   }
  }
 });
})

Session 时效

身份认证多半是服务器端的事情。无论你用什么实现方式,你的后端会对用户信息做真正的验证和处理诸如 Session 时效和访问控制的处理。这意味着你的 API 会有时返回一些认证错误。标准的错误码就是 HTTP 状态吗。普遍使用这些错误码:

  • 401 Unauthorized — The user is not logged in

  • 403 Forbidden — The user is logged in but isn't allowed access

  • 419 Authentication Timeout (non standard) — Session has expired

  • 440 Login Timeout (Microsoft only) — Session has expired

后两种不是标准内容,但是可能广泛应用。最好的官方的判断 session 过期的错误码是 401。无论怎样,你的登陆对话框都应该在 API 返回 401, 419, 440 或者 403 的时候马上显示出来。总的来说,我们想广播和基于这些 HTTP 返回码的时间,为此我们在 $httpProvider 增加一个拦截器:

.config(function ($httpProvider) {
 $httpProvider.interceptors.push([
  &#39;$injector&#39;,
  function ($injector) {
   return $injector.get(&#39;AuthInterceptor&#39;);
  }
 ]);
})
.factory(&#39;AuthInterceptor&#39;, function ($rootScope, $q,
                   AUTH_EVENTS) {
 return {
  responseError: function (response) { 
   $rootScope.$broadcast({
    401: AUTH_EVENTS.notAuthenticated,
    403: AUTH_EVENTS.notAuthorized,
    419: AUTH_EVENTS.sessionTimeout,
    440: AUTH_EVENTS.sessionTimeout
   }[response.status], response);
   return $q.reject(response);
  }
 };
})

这只是一个认证拦截器的简单实现。有个很棒的项目在 Github ,它做了相同的事情,并且使用了 httpBuffer 服务。当返回 HTTP 错误码时,它会阻止用户进一步的请求,直到用户再次登录,然后继续这个请求。

登录对话框指令

当一个 session 过期了,我们需要用户重新进入他的账号。为了防止他丢失他当前的工作,最好的方法就是弹出登录登录对话框,而不是跳转到登录页面。这个对话框需要监听 notAuthenticated 和 sessionTimeout 事件,所以当其中一个事件被触发了,对话框就要打开:

.directive(&#39;loginDialog&#39;, function (AUTH_EVENTS) {
 return {
  restrict: &#39;A&#39;,
  template: &#39;<p ng-if="visible"
          ng-include="\&#39;login-form.html\&#39;">&#39;,
  link: function (scope) {
   var showDialog = function () {
    scope.visible = true;
   };

   scope.visible = false;
   scope.$on(AUTH_EVENTS.notAuthenticated, showDialog);
   scope.$on(AUTH_EVENTS.sessionTimeout, showDialog)
  }
 };
})

只要你喜欢,这个对话框可以随便扩展。主要的思想是重用已存在的登陆表单模板和 LoginController。你需要在每个页面写上如下的代码:

<p login-dialog ng-if="!isLoginPage"></p>

注意 isLoginPage 检查。一个失败了的登陆会触发 notAuthenticated 时间,但我们不想在登陆页面显示这个对话框,因为这很多余和奇怪。这就是为什么我们不把登陆对话框也放在登陆页面的原因。所以在 ApplicationController 里定义一个 $scope.isLoginPage 是合理的。

保存用户状态

在用户刷新他们的页面,依旧保存已登陆的用户信息是单页应用认证里面狡猾的一个环节。因为所有状态都存在客户端,刷新会清空用户信息。为了修复这个问题,我通常实现一个会返回已登陆的当前用户的数据的 API (比如 /profile),这个 API 会在 AngularJS 应用启动(比如在 “run” 函数)。然后用户数据会被保存在 Session 服务或者 $rootScope,就像用户已经登陆后的状态。或者,你可以把用户数据直接嵌入到 index.html,这样就不用额外的请求了。第三种方式就是把用户数据存在 cookie 或者 LocalStorage,但这会使得登出或者清空用户数据变得困难一点。

以上が認証に AngularJS を使用する方法の詳細なコード例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。