javascript - AngularJS ui-router giriş kimlik doğrulama


Ben AngularJS'e yeniyim ve aşağıdaki senaryoda angular-"ui-router"'ı nasıl kullanabileceğim konusunda biraz kafam karıştı:

İki bölümden oluşan bir web uygulaması yapıyorum. Birinci bölüm, giriş ve kayıt görünümlerine sahip ana sayfa, ikinci bölüm ise kontrol panelidir (başarılı girişten sonra).

Ana bölüm için Angular uygulaması ve /login ve /signup görünümlerini yönetmek için ui-router yapılandırması ile bir index.html oluşturdum ve gösterge paneli bölümü için de birçok alt görünümü yönetmek üzere uygulaması ve ui-router yapılandırması olan başka bir dashboard.html dosyası var.

Şimdi gösterge paneli bölümünü bitirdim ve farklı Angular uygulamalarına sahip iki bölümü nasıl birleştireceğimi bilmiyorum. Ana uygulamaya, gösterge paneli uygulamasına yönlendirmesini nasıl söyleyebilirim?

Cevap #1

Daha güzel bir demo yapma ve bazı bu servisleri kullanılabilir bir modüle dönüştürme sürecindeyim, ama işte ortaya çıkardıklarım. Bu, bazı püf noktalarının etrafından dolaşmayı gerektiren karmaşık bir süreç, bu yüzden sabredin. Bunu birkaç parçaya ayırmanız gerekecek.

Bu çarpıntıya bir bak.

Öncelikle, kullanıcının kimliğini saklamak için bir hizmete ihtiyacınız var. Buna principal diyorum. Kullanıcının giriş yapıp yapmadığını kontrol etmek için kullanılabilir ve talep edildiğinde, kullanıcının kimliğiyle ilgili temel bilgileri temsil eden bir nesneyi çözebilir. Bu, ihtiyacınız olan her şey olabilir, ancak temel bilgiler bir görüntüleme adı, bir kullanıcı adı, muhtemelen bir e-posta ve kullanıcının ait olduğu roller olacaktır (bu, uygulamanıza uygunsa). Principal ayrıca rol kontrolleri yapmak için yöntemlere de sahiptir.

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

İkincisi, kullanıcının gitmek istediği durumu kontrol eden, giriş yapmış olduklarından emin olan (gerekliyse; giriş, şifre sıfırlama vb. için gerekli değildir) ve ardından rol kontrolü yapan bir hizmete ihtiyacınız var (uygulamanız bunu gerektiriyorsa). Kimlik doğrulaması yapılmamışsa, onları giriş sayfasına yönlendirin. Kimlik doğrulaması yapılmış, ancak rol kontrolünü geçememişlerse, onları erişim reddedildi sayfasına yönlendirin. Bu hizmete authorization adını veriyorum.

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

Şimdi yapmanız gereken tek şey ui-router'ın $stateChangeStart'ini dinlemektir. Bu, mevcut durumu, gitmek istedikleri durumu inceleme ve yetkilendirme kontrolünüzü ekleme şansı verir. Eğer başarısız olursa, rota geçişini iptal edebilir veya farklı bir rotaya geçebilirsiniz.

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

Bir kullanıcının kimliğini takip etmenin zor kısmı, zaten doğrulanmışsanız onu aramaktır (örneğin, önceki bir oturumdan sonra sayfayı ziyaret ediyorsunuz ve bir çerezde bir kimlik doğrulama belirteci sakladınız veya sayfayı zorla yenilediniz ya da bir bağlantıdan bir URL'ye düştünüz). ui-router'ın çalışma şekli nedeniyle, kimlik çözümlemenizi bir kez yapmanız gerekir, doğrulama kontrollerinizden önce. Bunu durum konfigürasyonunuzdaki resolve seçeneğini kullanarak yapabilirsiniz. Sitenin tüm durumların miras aldığı bir ana durumum var, bu da her şeyden önce principal’in çözülmesini zorunlu kılıyor.

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

Burada başka bir sorun var... resolve yalnızca bir kez çağrılır. Kimlik sorgulamanız için oluşturduğunuz promise tamamlandığında, resolve delegesi bir daha çalıştırılmaz. Bu nedenle kimlik doğrulama kontrollerinizi iki yerde yapmamız gerekiyor: bir kez kimlik promise'iniz resolve içinde çözülürken, bu uygulamanız ilk yüklendiğinde geçerlidir; bir kez de çözüm tamamlandıysa $stateChangeStart içinde, bu ise herhangi bir zamanda durumlar arasında gezinirken geçerlidir.

Tamam, peki şimdiye kadar ne yaptık?

  1. Uygulama yüklendiğinde kullanıcının oturum açıp açmadığını kontrol ediyoruz.
  2. Giriş yapmış kullanıcı hakkında bilgi takip ediyoruz.
  3. Kullanıcının giriş yapmış olması gereken durumlar için onları giriş yapma durumuna yönlendiriyoruz.
  4. Erişim yetkileri yoksa onları erişim engellendi durumuna yönlendiriyoruz.
  5. Kullanıcıların giriş yapmaları gerekirse, onları istedikleri orijinal duruma geri yönlendirecek bir mekanizmamız var.
  6. Bir kullanıcıyı oturumu kapatabiliriz (bu, kimlik doğrulama biletinizi yöneten herhangi bir istemci veya sunucu koduyla birlikte bağlanması gerekir).
  7. Kullanıcıları her tarayıcıyı yeniden yüklediklerinde veya bir bağlantıya tıkladıklarında oturum açma sayfasına geri göndermemize gerek yok.

Buradan nereye gidiyoruz? Pekala, durumlarınızı oturum açmayı gerektiren bölgelere ayırabilirsiniz. Bu durumlara (veya isterseniz miras kullanmak için bir üstlerine) roles ile data ekleyerek doğrulanmış/ yetkilendirilmiş kullanıcıları gerektirebilirsiniz. Burada, bir kaynağı Yöneticilerle sınırlıyoruz:

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

Artık kullanıcıların bir rotaya neye erişebileceğini eyalet bazında kontrol edebilirsiniz. Başka kaygılarınız var mı? Belki sadece bir görünümün bir kısmını kullanıcı giriş yapmış mı yapmamış mı durumuna göre değiştirmek mi? Sorun değil. Bir şablonu veya öğeyi koşullu olarak gösterebileceğiniz birçok yöntemle principal.isAuthenticated() veya hatta principal.isInRole() kullanın.

Öncelikle, principal'i bir denetleyiciye veya herhangi bir yere enjekte edin ve görünümünüzde kolayca kullanabilmek için bunu scope'a ekleyin:

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

Bir öğeyi göster veya gizle:

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

Vs., ve benzeri. Her neyse, örnek uygulamanızda, doğrulanmamış kullanıcıların uğrayabileceği bir ana sayfa durumu olurdu. Onlar, giriş yapma veya kayıt olma durumlarına yönlendiren bağlantılara sahip olabilirler ya da bu formlar o sayfaya entegre edilmiş olabilir. Size ne uygunsa.

Gösterge panosu sayfalarının tümü, kullanıcıların giriş yapmasını ve örneğin bir User rolü üyesi olmasını gerektiren bir durumdan miras alabilir. Tartıştığımız tüm yetkilendirme konuları buradan ileri akacaktır.

Cevap #2

Bana göre şu ana kadar paylaşılan çözümler gereksiz şekilde karmaşık. Daha basit bir yol var. ui-router dokümantasyonu, bir durum geçişini kontrol etmek, durdurmak veya devam ettirmek için $locationChangeSuccess dinlemenizi ve $urlRouter.sync() kullanmanızı söylüyor. Ama aslında bu bile çalışmıyor.

Ancak, işte iki basit alternatif. Birini seç:

Çözüm 1: $locationChangeSuccess dinleme

$locationChangeSuccess öğesini dinleyebilirsiniz ve orada bazı mantıklar, hatta eşzamansız mantık bile yürütebilirsiniz. Bu mantığa dayanarak, fonksiyonun undefined döndürmesine izin verebilirsiniz; bu, durum geçişinin normal şekilde devam etmesini sağlar veya kullanıcı doğrulanması gerekiyorsa $state.go('logInPage') yapabilirsiniz. İşte bir örnek:

angular.module('App', ['ui.router'])

// In the run phase of your Angular application  
.run(function($rootScope, user, $state) {

  // Listen to '$locationChangeSuccess', not '$stateChangeStart'
  $rootScope.$on('$locationChangeSuccess', function() {
    user
      .logIn()
      .catch(function() {
        // log-in promise failed. Redirect to log-in page.
        $state.go('logInPage')
      })
  })
})

Unutmayın, bu aslında hedef durumun yüklenmesini engellemez, ancak kullanıcı yetkisizse giriş sayfasına yönlendirir. Bu sorun değil, çünkü gerçek koruma zaten sunucudadır.

Çözüm 2: resolve durumunu kullanmak

Bu çözümde, ui-router çözümleme (resolve) özelliğini kullanırsınız.

Kullanıcı kimlik doğrulaması yapılmamışsa, temel olarak resolve içindeki sözü reddediyorsunuz ve ardından onları giriş sayfasına yönlendiriyorsunuz.

İşin nasıl ilerlediği şöyle:

angular.module('App', ['ui.router'])

.config(
  function($stateProvider) {
    $stateProvider
      .state('logInPage', {
        url: '/logInPage',
        templateUrl: 'sections/logInPage.html',
        controller: 'logInPageCtrl',
      })
      .state('myProtectedContent', {
        url: '/myProtectedContent',
        templateUrl: 'sections/myProtectedContent.html',
        controller: 'myProtectedContentCtrl',
        resolve: { authenticate: authenticate }
      })
      .state('alsoProtectedContent', {
        url: '/alsoProtectedContent',
        templateUrl: 'sections/alsoProtectedContent.html',
        controller: 'alsoProtectedContentCtrl',
        resolve: { authenticate: authenticate }
      })

    function authenticate($q, user, $state, $timeout) {
      if (user.isAuthenticated()) {
        // Resolve the promise successfully
        return $q.when()
      } else {
        // The next bit of code is asynchronously tricky.

        $timeout(function() {
          // This code runs after the authentication promise has been rejected.
          // Go to the log-in page
          $state.go('logInPage')
        })

        // Reject the authentication promise to prevent the state from loading
        return $q.reject()
      }
    }
  }
)

İlk çözümün aksine, bu çözüm aslında hedef durumun yüklenmesini engelliyor.

Cevap #3

En kolay çözüm, kullanıcı kimliği doğrulanmamışsa durumu iptal etmek ve onu giriş sayfası olan auth durumuna yönlendirmek için $stateChangeStart ve event.preventDefault() kullanmaktır.

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');
        }
      });
    }]
  );

Cevap #4

Bence kimlik doğrulama sürecini (ve depolanmasını) yöneten bir servicee ihtiyacınız var.

Bu serviste bazı temel yöntemlere ihtiyacınız olacak:

  • isAuthenticated()
  • login()
  • logout()
  • vb ...

Bu servis her modülünüzün denetleyicilerine enjekte edilmelidir :

  • Kontrol paneli bölümünüzde, bu servisi kullanarak kullanıcının doğrulanıp doğrulanmadığını kontrol edin (service.isAuthenticated() yöntemi). Eğer doğrulanmamışsa, /login sayfasına yönlendirin
  • Giriş bölümünüzde, kullanıcıyı kimlik doğrulamak için form verilerini sadece service.login() yönteminizi kullanın

Bu davranış için iyi ve sağlam bir örnek, angular-app projesi ve özellikle şaşırtıcı HTTP Auth Interceptor Modülü üzerine kurulu olan güvenlik modülüdür.

Umarım bu yardımcı olur

Cevap #5

Bu süreci çocuk oyuncağı haline getirmeye yardımcı olması için bu modülü oluşturdum

Şunları yapabilirsiniz:

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

Veya ayrıca

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

Yepyeni ama göz atmaya değer!

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

Cevap #6

UI Router 1.0.0.X ile çalışan başka bir çözümü paylaşmak istedim

Bildiğiniz gibi, stateChangeStart ve stateChangeSuccess artık kullanım dışı. https://github.com/angular-ui/ui-router/issues/2655

Bunun yerine $transitions http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html kullanmalısınız

Bunu böyle başardım:

Öncelikle bazı kullanışlı fonksiyonlara sahip bir AuthService'im var

angular.module('myApp')

        .factory('AuthService',
                ['$http', '$cookies', '$rootScope',
                    function ($http, $cookies, $rootScope) {
                        var service = {};

                        // Authenticates throug a rest service
                        service.authenticate = function (username, password, callback) {

                            $http.post('api/login', {username: username, password: password})
                                    .success(function (response) {
                                        callback(response);
                                    });
                        };

                        // Creates a cookie and set the Authorization header
                        service.setCredentials = function (response) {
                            $rootScope.globals = response.token;

                            $http.defaults.headers.common['Authorization'] = 'Bearer ' + response.token;
                            $cookies.put('globals', $rootScope.globals);
                        };

                        // Checks if it's authenticated
                        service.isAuthenticated = function() {
                            return !($cookies.get('globals') === undefined);
                        };

                        // Clear credentials when logout
                        service.clearCredentials = function () {
                            $rootScope.globals = undefined;
                            $cookies.remove('globals');
                            $http.defaults.headers.common.Authorization = 'Bearer ';
                        };

                        return service;
                    }]);

O zaman benim bu konfigürasyonum var:

angular.module('myApp', [
    'ui.router',
    'ngCookies'
])
        .config(['$stateProvider', '$urlRouterProvider',
            function ($stateProvider, $urlRouterProvider) {
                $urlRouterProvider.otherwise('/resumen');
                $stateProvider
                        .state("dashboard", {
                            url: "/dashboard",
                            templateUrl: "partials/dashboard.html",
                            controller: "dashCtrl",
                            data: {
                                authRequired: true
                            }
                        })
                        .state("login", {
                            url: "/login",
                            templateUrl: "partials/login.html",
                            controller: "loginController"
                        })
            }])

        .run(['$rootScope', '$transitions', '$state', '$cookies', '$http', 'AuthService',
            function ($rootScope, $transitions, $state, $cookies, $http, AuthService) {

                // keep user logged in after page refresh
                $rootScope.globals = $cookies.get('globals') || {};
                $http.defaults.headers.common['Authorization'] = 'Bearer ' + $rootScope.globals;

                $transitions.onStart({
                    to: function (state) {
                        return state.data != null && state.data.authRequired === true;
                    }
                }, function () {
                    if (!AuthService.isAuthenticated()) {
                        return $state.target("login");
                    }
                });
            }]);

Kullandığımı görebilirsiniz

data: {
   authRequired: true
}

sadece doğrulanmışsa erişilebilir durumu işaretlemek için.

sonra, .run üzerinde geçişleri kullanarak kimlik doğrulanmış durumu kontrol ediyorum

$transitions.onStart({
    to: function (state) {
        return state.data != null && state.data.authRequired === true;
    }
}, function () {
    if (!AuthService.isAuthenticated()) {
        return $state.target("login");
    }
});

Bu örneği, $transitions dokümantasyonunda bulduğum bazı kodları kullanarak oluşturdum. UI router ile oldukça yeniyim ama çalışıyor.

Umarım bu birine yardımcı olur.

İlgili makaleler