1. ホーム
  2. ジャバスクリプト

[解決済み】AngularJS ui-router ログイン認証

2022-03-24 13:40:10

質問

私はAngularJSの初心者ですが、以下のシナリオでangular-"ui-router"をどのように使用すればよいのか、少し混乱しています。

私は、2つのセクションからなるウェブアプリケーションを構築しています。最初のセクションは、ログインとサインアップのビューを持つホームページで、2番目のセクションはダッシュボード(ログイン成功後)です。

を作成しました。 index.html をアンギュラーアプリのホームセクションに、そして ui-router を処理するためのコンフィグ /login/signup のビューを表示します。 というファイルがあり、もう1つのファイル dashboard.html は、ダッシュボードセクションとそのアプリ、そして ui-router コンフィグで多くのサブビューを扱えるようにします。

今、私はダッシュボードセクションを終え、2つのセクションとそれぞれの異なるangularアプリをどのように組み合わせたらよいのかわかりません。ホームアプリからダッシュボードアプリにリダイレクトさせるにはどうしたらよいでしょうか?

解決方法は?

現在、より良いデモを作るとともに、これらのサービスのいくつかを使えるモジュールに整理している最中ですが、こんな感じです。これは、いくつかの注意点を回避するための複雑なプロセスなので、がんばってください。これをいくつかのパーツに分解する必要があります。

このplunkを見てみましょう .

まず、ユーザーのIDを保存するためのサービスが必要です。私はこれを principal . これは、ユーザーがログインしているかどうかをチェックし、リクエストに応じて、ユーザーのIDに関する重要な情報を表すオブジェクトを解決することができます。これは必要なものであれば何でも構いませんが、基本的なものは表示名、ユーザー名、場合によってはメールアドレス、そしてユーザーが所属するロール(あなたのアプリに適用される場合)でしょう。Principalには、ロールチェックを行うためのメソッドも用意されています。

.factory('principal', ['$q', '$http', '$timeout',
  function($q, $http, $timeout) {
    var _identity = undefined,
      _authenticated = false;

    return {
      isIdentityResolved: function() {
        return angular.isDefined(_identity);
      },
      isAuthenticated: function() {
        return _authenticated;
      },
      isInRole: function(role) {
        if (!_authenticated || !_identity.roles) return false;

        return _identity.roles.indexOf(role) != -1;
      },
      isInAnyRole: function(roles) {
        if (!_authenticated || !_identity.roles) return false;

        for (var i = 0; i < roles.length; i++) {
          if (this.isInRole(roles[i])) return true;
        }

        return false;
      },
      authenticate: function(identity) {
        _identity = identity;
        _authenticated = identity != null;
      },
      identity: function(force) {
        var deferred = $q.defer();

        if (force === true) _identity = undefined;

        // check and see if we have retrieved the 
        // identity data from the server. if we have, 
        // reuse it by immediately resolving
        if (angular.isDefined(_identity)) {
          deferred.resolve(_identity);

          return deferred.promise;
        }

        // otherwise, retrieve the identity data from the
        // server, update the identity object, and then 
        // resolve.
        //           $http.get('/svc/account/identity', 
        //                     { ignoreErrors: true })
        //                .success(function(data) {
        //                    _identity = data;
        //                    _authenticated = true;
        //                    deferred.resolve(_identity);
        //                })
        //                .error(function () {
        //                    _identity = null;
        //                    _authenticated = false;
        //                    deferred.resolve(_identity);
        //                });

        // for the sake of the demo, fake the lookup
        // by using a timeout to create a valid
        // fake identity. in reality,  you'll want 
        // something more like the $http request
        // commented out above. in this example, we fake 
        // looking up to find the user is
        // not logged in
        var self = this;
        $timeout(function() {
          self.authenticate(null);
          deferred.resolve(_identity);
        }, 1000);

        return deferred.promise;
      }
    };
  }
])

次に、ユーザーが行きたい状態をチェックし、ログインしていることを確認し(必要な場合。サインイン、パスワードリセットなどでは不要)、ロールチェックを行うサービスが必要です(アプリケーションがこれを必要とする場合)。認証されていない場合は、サインインページに移動させます。認証されているが、ロールチェックが失敗した場合は、アクセス拒否のページに送ります。私はこのサービスを authorization .

.factory('authorization', ['$rootScope', '$state', 'principal',
  function($rootScope, $state, principal) {
    return {
      authorize: function() {
        return principal.identity()
          .then(function() {
            var isAuthenticated = principal.isAuthenticated();

            if ($rootScope.toState.data.roles
                && $rootScope.toState
                             .data.roles.length > 0 
                && !principal.isInAnyRole(
                   $rootScope.toState.data.roles))
            {
              if (isAuthenticated) {
                  // user is signed in but not
                  // authorized for desired state
                  $state.go('accessdenied');
              } else {
                // user is not authenticated. Stow
                // the state they wanted before you
                // send them to the sign-in state, so
                // you can return them when you're done
                $rootScope.returnToState
                    = $rootScope.toState;
                $rootScope.returnToStateParams
                    = $rootScope.toStateParams;

                // now, send them to the signin state
                // so they can log in
                $state.go('signin');
              }
            }
          });
      }
    };
  }
])

これで、あとは ui-router 's $stateChangeStart . これは、現在の状態、彼らが行きたい状態を調べ、認証チェックを挿入する機会を与えてくれます。もし失敗したら、ルート遷移をキャンセルするか、別のルートに変更することができます。

.run(['$rootScope', '$state', '$stateParams', 
      'authorization', 'principal',
    function($rootScope, $state, $stateParams, 
             authorization, principal)
{
      $rootScope.$on('$stateChangeStart', 
          function(event, toState, toStateParams)
      {
        // track the state the user wants to go to; 
        // authorization service needs this
        $rootScope.toState = toState;
        $rootScope.toStateParams = toStateParams;
        // if the principal is resolved, do an 
        // authorization check immediately. otherwise,
        // it'll be done when the state it resolved.
        if (principal.isIdentityResolved()) 
            authorization.authorize();
      });
    }
  ]);

ユーザーの ID を追跡する際に厄介なのは、ユーザーがすでに認証済みである場合 (たとえば、以前のセッションの後でページを訪れ、認証トークンをクッキーに保存した場合、あるいはページをハードリフレッシュした場合、あるいはリンクから URL に移動した場合) にそれを確認することです。というのも ui-router が動作するため、認証チェックの前に一度 ID の解決を行う必要があります。これを行うには resolve オプションを使用します。私は、すべてのステートが継承するサイトの親ステートを1つ持っており、これは、他の何かが起こる前にプリンシパルを解決することを強制します。

$stateProvider.state('site', {
  'abstract': true,
  resolve: {
    authorize: ['authorization',
      function(authorization) {
        return authorization.authorize();
      }
    ]
  },
  template: '<div ui-view />'
})

ここでもうひとつ問題が...。 resolve は一度だけ呼ばれます。ID検索用のプロミスが完了すると、resolveデリゲートは二度と実行されません。つまり、認証のチェックは二箇所で行わなければなりません。 resolve これはアプリの最初のロードをカバーし、もう1つは $stateChangeStart が解決された場合、つまりステートをナビゲートするすべての時間をカバーします。

さて、ここまでで何ができたでしょうか?

  1. アプリのロード時に、ユーザーがログインしているかどうかを確認します。
  2. ログインしているユーザーに関する情報を追跡します。
  3. ログインが必要な状態については、サインイン状態にリダイレクトしています。
  4. アクセスするための認証がない場合は、アクセス拒否の状態にリダイレクトしています。
  5. ログインが必要な場合、ユーザーが要求した元の状態にリダイレクトする仕組みがあります。
  6. ユーザーをサインアウトさせることができます(認証チケットを管理するクライアントまたはサーバーのコードと協調して配線する必要があります)。
  7. 私たちは しない は、ユーザーがブラウザを再読み込みしたり、リンクをドロップしたりするたびに、サインインページに戻る必要があります。

ここから先はどうすればいいのか?さて、サインインを必要とする地域に州を組織化することができます。認証済み/認可済みのユーザーを要求するには、以下のように data と共に roles をこれらの状態 (継承を使用する場合は、その親) に追加します。ここでは、リソースをAdminsに制限しています。

.state('restricted', {
    parent: 'site',
    url: '/restricted',
    data: {
      roles: ['Admin']
    },
    views: {
      'content@': {
        templateUrl: 'restricted.html'
      }
    }
  })

これで、ルートにアクセスできるユーザーを状態ごとに制御できるようになりました。他に気になることはありますか?ログインしているかどうかでビューの一部分だけを変化させるとか?問題ありません。その場合は principal.isAuthenticated() あるいは principal.isInRole() を、テンプレートや要素を条件付きで表示する数多くの方法のいずれかを使用します。

まず principal をコントローラなどで作成し、それをスコープに貼り付けておくと、ビューで簡単に使用できるようになります。

.scope('HomeCtrl', ['$scope', 'principal', 
    function($scope, principal)
{
  $scope.principal = principal;
});

要素を表示または非表示にします。

<div ng-show="principal.isAuthenticated()">
   I'm logged in
</div>
<div ng-hide="principal.isAuthenticated()">
  I'm not logged in
</div>

などなど。とにかく、例のアプリでは、未認証のユーザーが立ち寄ることができるホームページのステートを用意します。サインインやサインアップの状態へのリンクがあってもよいし、それらのフォームをそのページに組み込んでもよいでしょう。お好きなようにどうぞ。

ダッシュボードページはすべて、ユーザーがログインしていることを要求する状態を継承して、たとえば User ロールのメンバーです。これまで説明してきたすべての権限設定は、そこから派生していくでしょう。