Uwierzytelnianie logowania AngularJS ui-router

376

Jestem nowy w AngularJS i jestem trochę zdezorientowany, jak mogę używać angular-„ui-router” w następującym scenariuszu:

Buduję aplikację internetową, która składa się z dwóch części. Pierwsza sekcja to strona główna z widokami logowania i rejestracji, a druga sekcja to pulpit nawigacyjny (po udanym zalogowaniu).

Utworzyłem index.htmlsekcję główną z aplikacją kątową i ui-routerkonfiguracją do obsługi /logini /signupwidoków, a dashboard.htmldla sekcji deski rozdzielczej z aplikacją i innym plikiem jest inny plikui-router konfiguracją do obsługi wielu widoków podrzędnych.

Teraz skończyłem sekcję deski rozdzielczej i nie wiem, jak połączyć te dwie sekcje z ich różnymi aplikacjami kątowymi. Jak mogę powiedzieć aplikacji domowej, aby przekierowała do aplikacji deski rozdzielczej?

Ahmed Hashem
źródło
1
Czy możesz podzielić się z nami kodem?
Chancho
6
@Chancho Myślę, że nie chodzi o kod, naprawdę nie wiem, jaki kod powinienem udostępnić.
Ahmed Hashem
tak, proszę podać kod, bardzo ogólne pytanie ...
Alireza,

Odpowiedzi:

607

Jestem w trakcie tworzenia ładniejszej wersji demo, a także porządkowania niektórych z tych usług w użytecznym module, ale oto co wymyśliłem. Jest to złożony proces obejścia niektórych zastrzeżeń, więc trzymaj się. Musisz to rozbić na kilka części.

Spójrz na to słowo .

Po pierwsze, potrzebujesz usługi do przechowywania tożsamości użytkownika. Nazywam to principal. Można to sprawdzić, czy użytkownik jest zalogowany, a na żądanie może rozwiązać obiekt reprezentujący niezbędne informacje o tożsamości użytkownika. Może to być wszystko, czego potrzebujesz, ale najważniejsze to nazwa wyświetlana, nazwa użytkownika, ewentualnie e-mail i role, do których użytkownik należy (jeśli dotyczy to Twojej aplikacji). Principal ma również metody sprawdzania ról.

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

Po drugie, potrzebujesz usługi, która sprawdza stan, w którym użytkownik chce przejść, upewnia się, że jest zalogowany (jeśli to konieczne; nie jest konieczne do logowania, resetowania hasła itp.), A następnie sprawdza rolę (jeśli Twoja aplikacja jest zalogowana) potrzebuje tego). Jeśli nie są uwierzytelnione, wyślij je na stronę logowania. Jeśli są uwierzytelnione, ale nie sprawdzają roli, wyślij je na stronę, na której odmówiono dostępu. Dzwonię do tej usługi 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');
              }
            }
          });
      }
    };
  }
])

Teraz wszystko, co musisz zrobić, to słuchać na ui-router„s $stateChangeStart. Daje to szansę na sprawdzenie bieżącego stanu, stanu, do którego chcą przejść, i wstawienie kontroli autoryzacji. Jeśli się nie powiedzie, możesz anulować zmianę trasy lub zmienić inną trasę.

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

Trudną częścią śledzenia tożsamości użytkownika jest sprawdzenie go, jeśli już go uwierzytelniłeś (powiedzmy, że odwiedzasz stronę po poprzedniej sesji i zapisałeś token uwierzytelnienia w pliku cookie, a może mocno odświeżyłeś stronę lub upuszczony na adres URL z linku). Ze względu na sposób ui-routerdziałania konieczne jest jednokrotne rozpoznanie tożsamości przed sprawdzeniem autoryzacji. Możesz to zrobić za pomocą resolveopcji w konfiguracji stanu. Mam jeden stan nadrzędny dla witryny, z której dziedziczą wszystkie stany, co zmusza zasadę do rozwiązania, zanim cokolwiek innego się wydarzy.

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

Jest tu inny problem ... resolvezostaje wywołany tylko raz. Po wypełnieniu obietnicy wyszukiwania tożsamości nie uruchomi ona ponownie delegata rozwiązania. Musimy więc przeprowadzić weryfikację uwierzytelnienia w dwóch miejscach: raz, zgodnie z obietnicą tożsamości, w resolvektórej dotyczy to pierwszego uruchomienia aplikacji, i raz, $stateChangeStartjeśli rozdzielczość została wykonana, co obejmuje każdą nawigację po stanach.

OK, więc co do tej pory zrobiliśmy?

  1. Sprawdzamy, kiedy aplikacja się ładuje, jeśli użytkownik jest zalogowany.
  2. Śledzimy informacje o zalogowanym użytkowniku.
  3. Przekierowujemy ich do stanu logowania dla stanów wymagających zalogowania użytkownika.
  4. Przekierowujemy ich do stanu odmowy dostępu, jeśli nie mają autoryzacji dostępu.
  5. Mamy mechanizm przekierowujący użytkowników z powrotem do pierwotnego stanu, o który prosili, jeśli potrzebowaliśmy ich do zalogowania się.
  6. Możemy wylogować użytkownika (musi być połączony z dowolnym kodem klienta lub serwera, który zarządza twoim biletem autoryzacyjnym).
  7. My nie potrzebujemy do wysyłania użytkownikom powrót do strony logowania za każdym razem przeładować swoją przeglądarkę lub spadek na link.

Dokąd zmierzamy? Cóż, można organizować swoje stany w regionach, które wymagają podpisania w. Można wymagać uwierzytelnionych / autoryzowanych użytkowników, dodając dataz rolestych państw (lub rodzic z nich, jeśli chcesz korzystać z dziedziczenia). Tutaj ograniczamy zasób do administratorów:

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

Teraz możesz kontrolować stan po stanie, którzy użytkownicy mogą uzyskać dostęp do trasy. Jakieś inne obawy? Może zmieniając tylko część widoku w zależności od tego, czy są zalogowani? Nie ma problemu. Użyj principal.isAuthenticated()lubprincipal.isInRole() z jednego z wielu sposobów warunkowego wyświetlania szablonu lub elementu, z nich.

Najpierw wstrzyknij principaldo kontrolera lub cokolwiek innego i przyklej go do lunety, abyś mógł go łatwo używać w swoim widoku:

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

Pokaż lub ukryj element:

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

Itd., Itd. Itd. Tak czy inaczej, w twojej przykładowej aplikacji miałbyś stan strony głównej, który pozwalałby odwiedzać nieuwierzytelnionym użytkownikom. Mogą mieć linki do stanów logowania lub rejestracji albo mieć wbudowane formularze na tej stronie. Cokolwiek Ci odpowiada.

Wszystkie strony panelu kontrolnego mogą dziedziczyć po stanie, który wymaga zalogowania użytkowników i, powiedzmy, bycia Userczłonkiem roli. Stąd płyną wszystkie omówione przez nas kwestie związane z autoryzacją.

moribvndvs
źródło
28
Dzięki, naprawdę pomogło mi to zebrać własny kod. Na marginesie, jeśli otrzymasz nieskończoną pętlę routingu (błąd routera interfejsu użytkownika), spróbuj $location.pathzamiast $state.go.
jvannistelrooy
2
To świetna odpowiedź i bardzo mi pomogła. Kiedy ustawiam user = principal w moim kontrolerze i próbuję wywołać powiedz user.identity (). Name w moim widoku, aby uzyskać nazwę aktualnie zalogowanych użytkowników, wydaje mi się, że dostaję tylko obiekt obietnicy {then: fn, catch: fn, wreszcie :} zwrócił, a nie rzeczywisty obiekt _identity. Jeśli użyję user.identity.then (fn (użytkownik)), mogę uzyskać obiekt użytkownika, ale wydaje się, że to dużo kodu dla widoku. Czy coś mi brakuje?
Mark
4
@ Ir1sh Najpierw rozpoznałbym tożsamość w kontrolerze i przypisałbym ją do $scope.usertwojej thenfunkcji. Nadal możesz odwoływać się userw swoich poglądach; po rozwiązaniu widok zostanie zaktualizowany.
moribvndvs
2
@HackedByChinese Myślę, że twoje demo już nie działa.
Blowsie
7
@ jvannistelrooy Miałem problemy z funkcją go (), ale po umieszczeniu jej w środku po wywołaniu funkcji noop w ten sposób $q.when(angular.noop).then(function(){$state.go('myState')wszystko działa zgodnie z oczekiwaniami. Jeśli zadzwonię, $state.gogdy przejście innego stanu nie zostanie zakończone, to nie zadziała (myślę, że to jest powód, dla którego nie zadziała).
Sebastian
120

Dotychczasowe rozwiązania są, moim zdaniem, niepotrzebnie skomplikowane. Jest prostszy sposób. Dokumentacjaui-router mówi słuchać $locationChangeSuccessi wykorzystanie$urlRouter.sync() sprawdzić przejście stanu, zatrzymanie go lub ją wznowić. Ale nawet to tak naprawdę nie działa.

Oto jednak dwie proste alternatywy. Wybierz jedno:

Rozwiązanie 1: słuchanie dalej $locationChangeSuccess

Możesz słuchać $locationChangeSuccessi wykonywać logikę, nawet logikę asynchroniczną. W oparciu o tę logikę możesz pozwolić, aby funkcja zwróciła niezdefiniowaną, co spowoduje, że przejście stanu będzie kontynuowane jak zwykle, lub możesz to zrobić $state.go('logInPage'), jeśli użytkownik musi zostać uwierzytelniony. Oto przykład:

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

Należy pamiętać, że tak naprawdę nie zapobiega to ładowaniu stanu docelowego, ale przekierowuje na stronę logowania, jeśli użytkownik jest nieautoryzowany. To jest w porządku, ponieważ prawdziwa ochrona jest zresztą na serwerze.

Rozwiązanie 2: za pomocą stanu resolve

W tym rozwiązaniu korzystasz z ui-routerfunkcji rozpoznawania .

Zasadniczo odrzucasz obietnicę, resolvejeśli użytkownik nie jest uwierzytelniony, a następnie przekierowujesz go na stronę logowania.

Oto jak to wygląda:

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()
      }
    }
  }
)

W przeciwieństwie do pierwszego rozwiązania, to rozwiązanie faktycznie zapobiega ładowaniu stanu docelowego.

MK Safi
źródło
6
@FredLackey mówi, że nieuwierzytelniony użytkownik jest w systemie state A. Klikają link, aby przejść do, protected state Bale chcesz je przekierować logInPage. Jeśli nie $timeout, ui-routerpo prostu zatrzyma wszystkie przejścia między stanami, aby użytkownik utknął state A. $timeoutPozwala ui-routernajpierw zapobiec początkową przejścia protected state Bponieważ postanowienie zostało odrzucone, a potem zrobił, to przekierowuje logInPage.
MK Safi
Gdzie jest authenticatefaktycznie wywoływana funkcja?
CodyBugstein,
authenticateFunkcja @Imray jest przekazywana jako parametr do ui-router. Nie musisz nazywać tego sam. ui-routernazywa to.
MK Safi,
Dlaczego używasz „$ locationChangeSuccess” zamiast „$ stateChangeStart”?
Draex_
@ PeterDraexDräxler Przeważnie śledziłem dokumentację. Czy zauważyłeś jakąś różnicę przy użyciu $stateChangeStart?
MK Safi,
42

Najprostszym rozwiązaniem jest użycie $stateChangeStarti event.preventDefault()aby anulować zmianę stanu, gdy użytkownik nie jest uwierzytelniony i przekierować go do auth państwa, które jest strona logowania.

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');
        }
      });
    }]
  );
sebest
źródło
6
Nie sądzę, że to zadziała, jeśli User.authenticaded () jest wywołaniem asynchronicznym. To święty Graal, którego wszyscy szukają. Na przykład, jeśli każdy stan oprócz „login” jest zabezpieczony, chcę potwierdzić, że użytkownik jest nadal uwierzytelniony PRZED załadowaniem dowolnego stanu. Używanie resolves jest do bani, ponieważ rozwiązują one tylko raz, a aby zapobiec ładowaniu stanów potomnych, musisz wstrzyknąć postanowienie do KAŻDEGO DZIECKA .
Jason
Uwierzytelnione nie jest w moim przypadku wywołaniem asynchronicznym: `this.authenticaded = function () {if (this.currentAccountID! == null) {return true; } return false; }; `
sebest
Zgodnie z: stackoverflow.com/a/38374313/849829 , „uruchom” znacznie przewyższa usługi i stąd problemy. Sprawdzanie statusu lokalnego uwierzytelnienia wydaje się być dobrym rozwiązaniem.
Deepak Thomas
22

Myślę, że potrzebujesz proces serviceobsługi uwierzytelnienia (i jego przechowywania).

W tej usłudze potrzebujesz kilku podstawowych metod:

  • isAuthenticated()
  • login()
  • logout()
  • itp ...

Tę usługę należy wprowadzić do kontrolerów każdego modułu:

  • W sekcji dotyczącej pulpitu nawigacyjnego skorzystaj z tej usługi, aby sprawdzić, czy użytkownik jest uwierzytelniony ( service.isAuthenticated()metoda). jeśli nie, przekieruj na / login
  • W sekcji logowania wystarczy użyć danych formularza, aby uwierzytelnić użytkownika za pomocą service.login()metody

Dobrym i solidnym przykładem tego zachowania jest aplikacja kątowa projektu, a zwłaszcza jej moduł bezpieczeństwa oparty na niesamowitym module przechwytującym HTTP Auth

Mam nadzieję że to pomoże

Cétia
źródło
21

Stworzyłem ten moduł, aby ten proces był jak bułka z masłem

Możesz robić takie rzeczy jak:

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

Lub też

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

Jest nowy, ale warto go sprawdzić!

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

Rafael Vidaurre
źródło
2
co mnie powstrzyma przed edycją źródła w czasie wykonywania i usunięciem twojego „administratora” || „bóg” i kontynuuje?
Pogrindis,
12
Mam nadzieję, że wszelkie żądania danych wymagające autoryzacji są również weryfikowane na serwerze.
Ben Ripley,
24
Nie ma to na celu zapewnienia bezpieczeństwa, nigdy nie ma autoryzacji po stronie klienta, ponieważ zawsze można zmienić wartości. Możesz nawet przechwytywać odpowiedzi ze strony serwera i oceniać je jako „autoryzowane”. Punktem uprawnień / autoryzacji po stronie klienta jest unikanie pozwalania użytkownikowi na wykonywanie zabronionych czynności w celach związanych z interfejsem użytkownika. Na przykład, jeśli wykonujesz akcję tylko dla administratora, nawet jeśli użytkownik złośliwie oszukuje klienta, aby umożliwić wysyłanie ograniczonego żądania do serwera, serwer nadal zwróci odpowiedź 401. Oczywiście jest to zawsze odpowiedzialność za wdrożenie interfejsu API @BenRipley
Rafael Vidaurre
3
Świetna odpowiedź na pytanie Rafael. Zawsze chroń interfejsy API, ponieważ przód jest najbardziej możliwą do inżynierii wstecznej, podobieństwem.
Frankie Loscavio
1
Ten problem z historią rozwiązany jest już od dłuższego czasu @Bohdan. Możesz bezpiecznie używać go nawet z dodatkami do routera.
masterspambot
16

Chciałem udostępnić inne rozwiązanie współpracujące z routerem interfejsu użytkownika 1.0.0.X

Jak zapewne wiesz, stateChangeStart i stateChangeSuccess są teraz przestarzałe. https://github.com/angular-ui/ui-router/issues/2655

Zamiast tego powinieneś użyć $ transitions http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html

Oto jak to osiągnąłem:

Najpierw mam i AuthService z kilkoma przydatnymi funkcjami

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

Następnie mam tę konfigurację:

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

Widać, że używam

data: {
   authRequired: true
}

aby oznaczyć stan jako dostępny tylko wtedy, gdy jest uwierzytelniony.

następnie na .run używam przejść, aby sprawdzić stan uwierzytelnienia

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

Buduję ten przykład przy użyciu kodu znalezionego w dokumentacji $ Transitions. Jestem całkiem nowy z routerem interfejsu użytkownika, ale działa.

Mam nadzieję, że może pomóc każdemu.

Sergio Fernandez
źródło
Jest to idealne rozwiązanie dla osób korzystających z nowszego routera. Dzięki!
Mt
5

Oto jak wydostaliśmy się z nieskończonej pętli routingu i nadal używaliśmy $state.gozamiast$location.path

if('401' !== toState.name) {
  if (principal.isIdentityResolved()) authorization.authorize();
}
Jason Girdner
źródło
1
Czy ktokolwiek wiedziałby, dlaczego podczas korzystania z zaakceptowanej odpowiedzi / konfiguracji opisanej powyżej pasek adresu nie wyświetla już adresu URL oraz wszystkich fragmentów i parametrów ciągu zapytania? Od czasu wdrożenia tego paska adresu nie można już dodawać do zakładek naszej aplikacji.
Frankie Loscavio
1
Czy to nie powinien być komentarz do jednej z istniejących odpowiedzi? Ponieważ w OP nie ma takiego kodu i nie jest nawet jasne, do której odpowiedzi / jakiego kodu się odnosi
TJ
3

Mam inne rozwiązanie: to rozwiązanie działa idealnie, gdy masz tylko treść, którą chcesz wyświetlić, gdy jesteś zalogowany. Zdefiniuj regułę, w której sprawdzasz, czy jesteś zalogowany, a nie ścieżkę do białej listy tras.

$urlRouterProvider.rule(function ($injector, $location) {
   var UserService = $injector.get('UserService');
   var path = $location.path(), normalized = path.toLowerCase();

   if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
     $location.path('/login/signin');
   }
});

W moim przykładzie pytam, czy nie jestem zalogowany, a bieżąca trasa, którą chcę trasować, nie jest częścią `/ login ', ponieważ moje trasy na białej liście są następujące

/login/signup // registering new user
/login/signin // login to app

więc mam natychmiastowy dostęp do tych dwóch tras, a każda inna trasa zostanie sprawdzona, jeśli jesteś online.

Oto mój cały plik routingu dla modułu logowania

export default (
  $stateProvider,
  $locationProvider,
  $urlRouterProvider
) => {

  $stateProvider.state('login', {
    parent: 'app',
    url: '/login',
    abstract: true,
    template: '<ui-view></ui-view>'
  })

  $stateProvider.state('signin', {
    parent: 'login',
    url: '/signin',
    template: '<login-signin-directive></login-signin-directive>'
  });

  $stateProvider.state('lock', {
    parent: 'login',
    url: '/lock',
    template: '<login-lock-directive></login-lock-directive>'
  });

  $stateProvider.state('signup', {
    parent: 'login',
    url: '/signup',
    template: '<login-signup-directive></login-signup-directive>'
  });

  $urlRouterProvider.rule(function ($injector, $location) {
    var UserService = $injector.get('UserService');
    var path = $location.path();

    if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
         $location.path('/login/signin');
    }
  });

  $urlRouterProvider.otherwise('/error/not-found');
}

() => { /* code */ } jest składnią ES6, użyj zamiast tego function() { /* code */ }

Chris Incoqnito
źródło
3

Użyj $ http Interceptor

Za pomocą przechwytywacza $ http możesz wysyłać nagłówki do zaplecza lub na odwrót i przeprowadzać kontrole w ten sposób.

Świetny artykuł na temat $ http przechwytujących

Przykład:

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

Umieść to w swojej funkcji .config lub .run.

TSlegaitis
źródło
2

Najpierw potrzebujesz usługi, którą możesz wstrzyknąć do kontrolerów, która ma pewne pojęcie o stanie uwierzytelnienia aplikacji. Utrzymywanie danych uwierzytelniających w lokalnej pamięci jest dobrym sposobem na zbliżenie się do nich.

Następnie musisz sprawdzić stan uwierzytelnienia tuż przed zmianą stanu. Ponieważ aplikacja zawiera niektóre strony, które wymagają uwierzytelnienia, a inne nie, utwórz trasę nadrzędną, która sprawdza uwierzytelnianie, i spraw, aby wszystkie inne strony, które tego wymagają, były potomkami tego rodzica.

Na koniec będziesz potrzebować sposobu, aby stwierdzić, czy aktualnie zalogowany użytkownik może wykonywać określone operacje. Można to osiągnąć, dodając funkcję „można” do usługi uwierzytelniania. Może przyjmować dwa parametry: - działanie - wymagane - (tj. „Manage_dashboards” lub „create_new_dashboard”) - obiekt - opcjonalnie - obiekt jest obsługiwany. Na przykład, jeśli masz obiekt deski rozdzielczej, możesz sprawdzić, czy dashboard.ownerId === loggedInUser.id. (Oczywiście informacje przekazywane od klienta nigdy nie powinny być zaufane i zawsze należy je zweryfikować na serwerze przed zapisaniem ich w bazie danych).

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

** ZASTRZEŻENIE: Powyższy kod jest pseudokodem i nie ma żadnych gwarancji **

Colefner
źródło