javascript - templateprovider - ui-sref-active




Angularui-routerログイン認証 (7)

$ httpインターセプタを使用する

$ httpインターセプタを使用すると、ヘッダーをバックエンドに送信することができます。

$ httpインターセプタに関する素晴らしい記事

例:

$httpProvider.interceptors.push(function ($q) {
        return {
            'response': function (response) {

                // TODO Create check for user authentication. With every request send "headers" or do some other check
                return response;
            },
            'responseError': function (reject) {

                // Forbidden
                if(reject.status == 403) {
                    console.log('This page is forbidden.');
                    window.location = '/';
                // Unauthorized
                } else if(reject.status == 401) {
                    console.log("You're not authorized to view this page.");
                    window.location = '/';
                }

                return $q.reject(reject);
            }
        };
    });

.configまたは.run関数にこれを入れます。

私はAngularJSを初めて使っています。次のシナリオでは、どのように角度ui-routerを使うことができるのか少し混乱しています。

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

私は/login/signupビューを扱うために角型アプリケーションとui-router configを持つホームセクション用のindex.htmlを作成しました。また、そのアプリケーションとui-router configを持つダッシュボードセクション用の別のファイルdashboard.htmlがありui-router多くのサブビューを処理します。

今、私はダッシュボードのセクションを終え、2つのセクションをそれぞれの角度のあるアプリケーションと組み合わせる方法を知らない。 ホームアプリにダッシュボードアプリにリダイレクトするように指示するにはどうすればよいですか?


ここでは、無限ループのループから$state.go$location.path代わりに$location.path $state.go使用した方法があり$location.path

if('401' !== toState.name) {
  if (principal.isIdentityResolved()) authorization.authorize();
}

まず、あなたは、あなたがコントローラに注入できるサービスが必要です。これは、アプリの認証状態のアイディアを持っています。 ローカルストレージを使用して認証の詳細を保持することは、それにアプローチするための適切な方法です。

次に、状態が変更される直前にauthの状態を確認する必要があります。 アプリケーションには認証が必要なページと認証されていないページがあるため、認証を確認する親ルートを作成し、それを必要とする他のすべてのページをその親の子にします。

最後に、現在ログインしているユーザーが特定の操作を実行できるかどうかを確認する方法が必要です。 これは認証サービスに「can」機能を追加することで実現できます。 Canは2つのパラメータをとります: - action - required - (つまり、 'manage_dashboards'または 'create_new_dashboard') - オブジェクト - オプション - オブジェクトが操作されています。 たとえば、ダッシュボードオブジェクトがある場合は、dashboard.ownerId === loggedInUser.idを確認することができます。 (もちろん、クライアントから渡された情報は決して信頼されるべきではなく、データベースに書き込む前に必ずサーバ上でこれを検証する必要があります)。

angular.module('myApp', ['ngStorage']).config([
   '$stateProvider',
function(
   $stateProvider
) {
   $stateProvider
     .state('home', {...}) //not authed
     .state('sign-up', {...}) //not authed
     .state('login', {...}) //not authed
     .state('authed', {...}) //authed, make all authed states children
     .state('authed.dashboard', {...})
}])
.service('context', [
   '$localStorage',
function(
   $localStorage
) {
   var _user = $localStorage.get('user');
   return {
      getUser: function() {
         return _user;
      },
      authed: function() {
         return (_user !== null);
      },
      // server should return some kind of token so the app 
      // can continue to load authenticated content without having to
      // re-authenticate each time
      login: function() {
         return $http.post('/login.json').then(function(reply) {
            if (reply.authenticated === true) {
               $localStorage.set(_userKey, reply.user);
            }
         });
      },
      // this request should expire that token, rendering it useless
      // for requests outside of this session
      logout: function() {
         return $http.post('logout.json').then(function(reply) {
            if (reply.authenticated === true) {
               $localStorage.set(_userKey, reply.user);
            }
         });
      },
      can: function(action, object) {
         if (!this.authed()) {
            return false;
         }

         var user = this.getUser();

         if (user && user.type === 'admin') {
             return true;
         }

         switch(action) {
            case 'manage_dashboards':
               return (user.type === 'manager');
         }

         return false;


      }
   }
}])
.controller('AuthCtrl', [
   'context', 
   '$scope', 
function(
   context, 
   $scope
) {
   $scope.$root.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
      //only require auth if we're moving to another authed page
      if (toState && toState.name.indexOf('authed') > -1) {
         requireAuth();
      }
   });

   function requireAuth() {
      if (!context.authed()) {
         $state.go('login');
      }
   }
}]

**免責事項:上記のコードは擬似コードであり、保証はありません**


最も簡単な解決策は、 $stateChangeStartevent.preventDefault()を使用して、ユーザーが認証されていないときに状態の変更を取り消し、ログインページであるauth状態にリダイレクトする方法です。

angular
  .module('myApp', [
    'ui.router',
  ])
    .run(['$rootScope', 'User', '$state',
    function ($rootScope, User, $state) {
      $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
        if (toState.name !== 'auth' && !User.authenticaded()) {
          event.preventDefault();
          $state.go('auth');
        }
      });
    }]
  );

私はこのプロセスをケーキにするためにこのモジュールを作成しました

次のようなことができます:

$routeProvider
  .state('secret',
    {
      ...
      permissions: {
        only: ['admin', 'god']
      }
    });

またはまた

$routeProvider
  .state('userpanel',
    {
      ...
      permissions: {
        except: ['not-logged-in']
      }
    });

これは新品ですがチェックアウトする価値があります!

https://github.com/Narzerus/angular-permission


私はより良いデモだけでなく、これらのサービスの一部を使用可能なモジュールにクリーンアップする過程にありますが、ここで私が思いついたのはこれです。 これはいくつかの警告を回避する複雑なプロセスなので、そこにハングアップします。 これをいくつかに分割する必要があります。

この塊を見てみましょう

まず、ユーザーの身元を保管するサービスが必要です。 私はこの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$stateChangeStart聞くだけ$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();
      });
    }
  ]);

ユーザーの身元を追跡することの難しい部分は、既に認証されている場合(たとえば、以前のセッションの後にページにアクセスしていて、認証トークンをCookieに保存している場合、またはページをハードリフレッシュした場合、リンクから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照会の約束が完了すると、再度解決デリゲートは実行されません。 だから、私たちは2つの場所であなたの認証チェックをしなければなりません。あなたのアイデンティティの約束に基づいて、あなたのアプリケーションが初めて読み込まれたときにresolve約束を守ることと、決断が終わったら$stateChangeStart状態。

それでは、これまで何をしていますか?

  1. ユーザーがログインしているときにアプリがいつロードされるかを確認します。
  2. ログインしたユーザーに関する情報を追跡します。
  3. ユーザーにログインする必要がある州のサインイン状態にリダイレクトします。
  4. アクセス権がないアクセス拒否状態にリダイレクトします。
  5. ログインする必要がある場合は、ユーザーがリクエストした元の状態にユーザーをリダイレクトする仕組みがあります。
  6. ユーザーに署名することができます(認証チケットを管理するクライアントコードまたはサーバーコードと一緒に接続する必要があります)。
  7. ユーザーがブラウザをリロードしたりリンクをドロップするたびに、ログインページに戻す必要ありません

ここからどこにいきますか? これらの状態(または継承を使用する場合は、それらの親)にrolesを持つdataを追加dataことによって、認証された/許可されたユーザーを要求することができます。 ここでは、リソースをAdminsに制限します。

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

これで、どのユーザーがルートにアクセスできるかを州ごとに制御できます。 その他の懸念事項はありますか? 彼らがログインしているかどうかに基づいて、ビューの一部だけが変わるかもしれませんか? 問題ない。 principal.isAuthenticated()またはprincipal.isAuthenticated()使用しても、条件付きでテンプレートまたは要素を表示することができます。

まず、 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役割のメンバーであることをすべて継承することができます。 我々が議論したすべての認可のものはそこから流れてくるでしょう。


認証プロセス(とそのストレージ)を処理するserviceが必要だと思います。

このサービスでは、いくつかの基本的な方法が必要です。

  • isAuthenticated()
  • login()
  • logout()
  • 等...

このサービスは、各モジュールのコントローラに注入する必要があります。

  • ダッシュボードセクションで、このサービスを使用して、ユーザーが認証されているかどうかを確認します( service.isAuthenticated()メソッド)。 そうでない場合、/ loginにリダイレクトする
  • ログインセクションでは、フォームデータを使用してservice.login()メソッドを通じてユーザーを認証するだけです

この動作の良い、そして堅牢な例は、プロジェクトのangular-app 、特に素晴らしいHTTP Auth Interceptor Moduleをベースにしたセキュリティモジュールです

お役に立てれば





angular-ui-router