AngularJS UI-Router-Anmeldeauthentifizierung


376

Ich bin neu in AngularJS und ein wenig verwirrt darüber, wie ich Angular- "UI-Router" im folgenden Szenario verwenden kann:

Ich erstelle eine Webanwendung, die aus zwei Abschnitten besteht. Der erste Abschnitt ist die Homepage mit ihren Anmelde- und Anmeldeansichten, und der zweite Abschnitt ist das Dashboard (nach erfolgreicher Anmeldung).

Ich habe eine index.htmlfür den Home-Bereich mit seiner eckigen App und ui-routerKonfiguration zum Behandeln /loginund /signupAnzeigen erstellt, und es gibt eine weitere Datei dashboard.htmlfür den Dashboard-Abschnitt mit seiner App und ui-routerKonfiguration zum Behandeln vieler Unteransichten.

Jetzt habe ich den Dashboard-Abschnitt fertiggestellt und weiß nicht, wie ich die beiden Abschnitte mit ihren unterschiedlichen eckigen Apps kombinieren soll. Wie kann ich die Home-App anweisen, zur Dashboard-App umzuleiten?


1
Können Sie uns einen Code mitteilen?
Chancho

6
@Chancho Ich denke, es geht nicht um Code, ich weiß wirklich nicht, welchen Code ich teilen soll.
Ahmed Hashem

Ja, bitte teilen Sie den Code, sehr allgemeine Frage ...
Alireza

Antworten:


607

Ich bin gerade dabei, eine schönere Demo zu erstellen und einige dieser Dienste in ein verwendbares Modul zu bereinigen, aber hier ist, was ich mir ausgedacht habe. Dies ist ein komplexer Prozess, um einige Einschränkungen zu umgehen. Sie müssen dies in mehrere Teile zerlegen.

Schauen Sie sich dieses Plunk an .

Zunächst benötigen Sie einen Dienst zum Speichern der Benutzeridentität. Ich nenne das principal. Es kann überprüft werden, ob der Benutzer angemeldet ist, und auf Anfrage kann ein Objekt aufgelöst werden, das die wesentlichen Informationen zur Identität des Benutzers darstellt. Dies kann alles sein, was Sie benötigen, aber das Wesentliche sind ein Anzeigename, ein Benutzername, möglicherweise eine E-Mail und die Rollen, zu denen ein Benutzer gehört (sofern dies für Ihre App gilt). Der Principal verfügt auch über Methoden zum Durchführen von Rollenprüfungen.

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

Zweitens benötigen Sie einen Dienst, der den Status überprüft, zu dem der Benutzer wechseln möchte, sicherstellt, dass er angemeldet ist (falls erforderlich; nicht erforderlich für Anmeldung, Zurücksetzen des Kennworts usw.), und dann eine Rollenprüfung durchführt (falls Ihre App braucht das). Wenn sie nicht authentifiziert sind, senden Sie sie an die Anmeldeseite. Wenn sie authentifiziert sind, aber eine Rollenprüfung nicht bestehen, senden Sie sie an eine Seite, auf die der Zugriff verweigert wurde. Ich rufe diesen Service an 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');
              }
            }
          });
      }
    };
  }
])

Nun sind alle brauchen, ist in abgehört zu tun ui-routerist $stateChangeStart. Auf diese Weise können Sie den aktuellen Status und den Status, in den sie wechseln möchten, überprüfen und Ihre Berechtigungsprüfung einfügen. Wenn dies fehlschlägt, können Sie den Routenübergang abbrechen oder zu einer anderen Route wechseln.

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

Der schwierige Teil beim Verfolgen der Identität eines Benutzers besteht darin, ihn nachzuschlagen, wenn Sie sich bereits authentifiziert haben (z. B. wenn Sie die Seite nach einer vorherigen Sitzung besuchen und ein Authentifizierungstoken in einem Cookie gespeichert haben oder wenn Sie eine Seite hart aktualisiert haben oder von einem Link auf eine URL abgelegt). Aufgrund der Funktionsweise ui-routermüssen Sie Ihre Identitätsauflösung einmal durchführen, bevor Ihre Authentifizierung überprüft wird. Sie können dies mit der resolveOption in Ihrer Statuskonfiguration tun . Ich habe einen übergeordneten Status für die Site, von dem alle Status erben, wodurch der Principal aufgelöst werden muss, bevor etwas anderes passiert.

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

Hier gibt es ein anderes Problem ... wird resolvenur einmal angerufen. Sobald Ihr Versprechen für die Identitätssuche erfüllt ist, wird der Auflösungsdelegierte nicht mehr ausgeführt. Daher müssen wir Ihre Authentifizierungsprüfungen an zwei Stellen durchführen: einmal gemäß Ihrem Identitätsversprechen resolve, das das erste Mal, $stateChangeStartwenn Ihre App geladen wird, auflöst , und einmal, wenn die Auflösung durchgeführt wurde, das jede Zeit abdeckt, in der Sie durch Staaten navigieren.

OK, was haben wir bisher gemacht?

  1. Wir überprüfen, wann die App geladen wird, wenn der Benutzer angemeldet ist.
  2. Wir verfolgen Informationen über den angemeldeten Benutzer.
  3. Wir leiten sie weiter, um sich für Zustände anzumelden, bei denen der Benutzer angemeldet sein muss.
  4. Wir leiten sie in den Status "Zugriff verweigert" um, wenn sie nicht berechtigt sind, darauf zuzugreifen.
  5. Wir haben einen Mechanismus, um Benutzer in den ursprünglichen Status zurückzuleiten, den sie angefordert haben, wenn wir sie zum Anmelden benötigen.
  6. Wir können einen Benutzer abmelden (muss zusammen mit jedem Client- oder Servercode verkabelt werden, der Ihr Authentifizierungsticket verwaltet).
  7. Wir müssen Benutzer nicht jedes Mal zur Anmeldeseite zurückschicken, wenn sie ihren Browser neu laden oder auf einen Link klicken.

Was machen wir jetzt? Nun können Sie Ihre Staaten in Regionen organisieren , die in Zeichen benötigen. Sie können authentifiziert / autorisierte Benutzer erfordern durch Zugabe von datamit rolesdiesen Staaten (oder ein Elternteil von ihnen, wenn Sie die Vererbung verwenden möchten). Hier beschränken wir eine Ressource auf Administratoren:

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

Jetzt können Sie Status für Status steuern, welche Benutzer auf eine Route zugreifen können. Irgendwelche anderen Bedenken? Vielleicht nur einen Teil einer Ansicht variieren, je nachdem, ob sie angemeldet sind oder nicht? Kein Problem. Verwenden Sie die principal.isAuthenticated()oder sogar principal.isInRole()eine der zahlreichen Möglichkeiten, eine Vorlage oder ein Element bedingt anzuzeigen.

Spritzen Sie zuerst principalin einen Controller oder was auch immer und kleben Sie ihn an das Zielfernrohr, damit Sie ihn aus Ihrer Sicht problemlos verwenden können:

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

Ein- oder Ausblenden eines Elements:

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

Usw. so weiter, so weiter. In Ihrer Beispiel-App hätten Sie jedenfalls einen Status für die Startseite, über den nicht authentifizierte Benutzer vorbeischauen könnten. Sie können Links zu den Anmelde- oder Anmeldezuständen haben oder diese Formulare in diese Seite integriert haben. Was immer dir passt.

Die Dashboard-Seiten können alle von einem Status erben, bei dem die Benutzer angemeldet sein und beispielsweise ein UserRollenmitglied sein müssen. Alle Autorisierungsmaterialien, die wir besprochen haben, würden von dort fließen.


28
Danke, das hat mir wirklich geholfen, meinen eigenen Code zusammenzustellen. Nebenbei bemerkt, wenn Sie eine Endlos-Routing-Schleife (UI-Router-Fehler) erhalten, versuchen Sie es $location.pathstattdessen mit $state.go.
Jvannistelrooy

2
Dies ist eine großartige Antwort und hat mir sehr geholfen. Wenn ich in meinem Controller user = Principal setze und versuche, say user.identity (). Name in meiner Ansicht aufzurufen, um den aktuell angemeldeten Benutzernamen zu erhalten, erhalte ich anscheinend nur das Versprechungsobjekt {then: fn, catch: fn, finally :} zurückgegeben und nicht das eigentliche _identity-Objekt. Wenn ich user.identity.then (fn (user)) verwende, kann ich das Benutzerobjekt abrufen, aber dies scheint viel Code für die Ansicht zu sein. Fehlt mir etwas?
Mark

4
@ Ir1sh Ich würde die Identität zuerst in der Steuerung auflösen und sie $scope.userin Ihrer thenFunktion zuweisen . Sie können weiterhin userauf Ihre Ansichten verweisen . Wenn es aufgelöst ist, wird die Ansicht aktualisiert.
moribvndvs

2
@ HackedByChinese Ich denke, Ihre Demo funktioniert nicht mehr.
Blowsie

7
@jvannistelrooy Ich hatte Probleme mit go () to, aber nachdem ich es in die dann nach dem Aufrufen einer Noop-Funktion wie dieser eingefügt habe, funktioniert $q.when(angular.noop).then(function(){$state.go('myState')alles wie erwartet. Wenn ich anrufe, $state.gowährend ein anderer Zustandsübergang nicht abgeschlossen ist, funktioniert er nicht (ich denke, das ist der Grund, warum er nicht funktioniert).
Sebastian

120

Die bisher veröffentlichten Lösungen sind meiner Meinung nach unnötig kompliziert. Es gibt einen einfacheren Weg. Das Dokumentation vonui-router sagt, $locationChangeSuccessdass $urlRouter.sync()Sie einen Statusübergang abhören und verwenden , um ihn zu überprüfen, anzuhalten oder fortzusetzen. Aber auch das funktioniert eigentlich nicht.

Hier sind jedoch zwei einfache Alternativen. Wähle eins:

Lösung 1: Zuhören $locationChangeSuccess

$locationChangeSuccessSie können dort eine Logik anhören und ausführen, sogar eine asynchrone Logik. Basierend auf dieser Logik können Sie die Funktion undefiniert zurückgeben lassen, wodurch der Statusübergang wie gewohnt fortgesetzt wird, oder Sie können dies tun $state.go('logInPage'), wenn der Benutzer authentifiziert werden muss. Hier ist ein Beispiel:

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

Beachten Sie, dass dies das Laden des Zielstatus nicht verhindert, jedoch zur Anmeldeseite umleitet, wenn der Benutzer nicht autorisiert ist. Das ist in Ordnung, da der Server sowieso wirklich geschützt ist.

Lösung 2: Verwenden des Status resolve

In dieser Lösung verwenden Sie die ui-routerAuflösungsfunktion .

Sie lehnen das Versprechen grundsätzlich ab resolve wenn der Benutzer nicht authentifiziert ist, und leiten es dann auf die Anmeldeseite weiter.

So geht's:

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

Im Gegensatz zur ersten Lösung verhindert diese Lösung tatsächlich das Laden des Zielstatus.


6
@FredLackey sagt, dass nicht authentifizierter Benutzer in ist state A. Sie klicken auf einen Link, zu dem Sie wechseln protected state Bmöchten, aber Sie möchten sie weiterleiten logInPage. Wenn dies nicht der Fall ist $timeout, ui-routerwerden einfach alle Statusübergänge angehalten, sodass der Benutzer feststeckt state A. Das $timeouterlaubt ui-router, zuerst den anfänglichen Übergang zu zu verhindern, protected state Bda die Auflösung abgelehnt wurde und danach umgeleitet wird logInPage.
MK Safi

Wo heißt die authenticateFunktion eigentlich?
CodyBugstein

Die @ Imray- authenticateFunktion wird als Parameter an übergeben ui-router. Sie müssen es nicht selbst nennen. ui-routernennt es.
MK Safi

Warum verwenden Sie '$ locationChangeSuccess' anstelle von '$ stateChangeStart'?
Draex_

@ PeterDraexDräxler Ich habe hauptsächlich die Dokumentation verfolgt. Haben Sie bei der Verwendung einen Unterschied festgestellt $stateChangeStart?
MK Safi

42

Die einfachste Lösung besteht darin , die Statusänderung zu verwenden $stateChangeStartund event.preventDefault()abzubrechen, wenn der Benutzer nicht authentifiziert ist, und ihn in den Authentifizierungsstatus umzuleiten, der die Anmeldeseite ist.

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

6
Ich denke nicht, dass dies funktionieren wird, wenn User.authenticaded () ein asynchroner Aufruf ist. Das ist der heilige Gral, nach dem jeder sucht. Wenn beispielsweise jeder Status außer "Login" gesichert ist, möchte ich bestätigen, dass der Benutzer noch authentifiziert ist, bevor ein Status geladen wird . Die Verwendung von Auflösungen ist zum Kotzen, da sie nur einmal aufgelöst werden. Um zu verhindern, dass untergeordnete Zustände geladen werden, müssen Sie die Auflösung in JEDES KIND injizieren .
Jason

authentifiziert ist in meinem Fall kein asynchroner Aufruf: `this.authenticaded = function () {if (this.currentAccountID! == null) {return true; } falsch zurückgeben; }; `
Sebest

Gemäß : stackoverflow.com/a/38374313/849829 steht "run" weit über "service" und damit den Problemen. Das Überprüfen von localstorage auf den authentifizierten Status scheint ein guter Ansatz zu sein.
Deepak Thomas

22

Ich denke du brauchst eine service , die den Authentifizierungsprozess (und seine Speicherung) handhabt.

In diesem Dienst benötigen Sie einige grundlegende Methoden:

  • isAuthenticated()
  • login()
  • logout()
  • usw ...

Dieser Service sollte in Ihre Controller jedes Moduls eingefügt werden:

  • Verwenden Sie diesen Dienst in Ihrem Dashboard-Bereich, um zu überprüfen, ob der Benutzer authentifiziert ist (service.isAuthenticated() Methode). Wenn nicht, leiten Sie zu / login um
  • Verwenden Sie in Ihrem Anmeldeabschnitt einfach die Formulardaten, um den Benutzer über Ihre service.login()Methode zu authentifizieren

Ein gutes und robustes Beispiel für dieses Verhalten ist die Projekt- Winkel-App und speziell das Sicherheitsmodul, das auf dem Fantastischen basiert HTTP Auth Interceptor-Modul

Hoffe das hilft


21

Ich habe dieses Modul erstellt, um diesen Prozess zum Kinderspiel zu machen

Sie können Dinge tun wie:

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

Oder auch

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

Es ist brandneu, aber einen Besuch wert!

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


2
Was hindert mich daran, die Quelle zur Laufzeit zu bearbeiten und Ihren 'admin' || zu entfernen? "Gott" und weiter?
Pogrindis

12
Ich würde hoffen, dass alle Datenanforderungen, die eine Autorisierung erfordern, auch auf dem Server überprüft werden.
Ben Ripley

24
Dies ist nicht aus Sicherheitsgründen gedacht, eine clientseitige Autorisierung ist niemals möglich, da Sie die Werte jederzeit ändern können. Sie können sogar Antworten von der Serverseite abfangen und als "autorisiert" bewerten. Der Punkt der Berechtigungen / Autorisierung auf der Clientseite besteht darin, zu vermeiden, dass der Benutzer verbotene Dinge für UX-Zwecke ausführt. Wenn Sie beispielsweise eine Aktion nur für Administratoren ausführen, gibt der Server auch dann eine 401-Antwort zurück, wenn der Benutzer den Client böswillig dazu verleitet, das Senden einer eingeschränkten Anforderung an den Server zuzulassen. Dies ist natürlich immer die Verantwortung für die Implementierung der API @BenRipley in der Tat
Rafael Vidaurre

3
Tolle Antwort auf die Frage Rafael. Schützen Sie immer die APIs, da das Front-End das rückentwickelbarste und fälschbarste ist, was es fast gibt.
Frankie Loscavio

1
Dieses Problem mit der Geschichte ist seit einiger Zeit @Bohdan gelöst. Sie können es auch mit UI-Router-Extras sicher verwenden.
Masterspambot

16

Ich wollte eine andere Lösung mit dem UI-Router 1.0.0.X teilen

Wie Sie vielleicht wissen, sind stateChangeStart und stateChangeSuccess jetzt veraltet. https://github.com/angular-ui/ui-router/issues/2655

Stattdessen sollten Sie $ -Übergänge verwenden http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html

So habe ich es erreicht:

Zuerst habe ich und AuthService mit einigen nützlichen Funktionen

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

Dann habe ich diese Konfiguration:

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

Sie können sehen, dass ich benutze

data: {
   authRequired: true
}

um den Status zu markieren, auf den nur zugegriffen werden kann, wenn er authentifiziert ist.

dann benutze ich auf dem .run die Übergänge, um den authentifizierten Zustand zu überprüfen

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

Ich erstelle dieses Beispiel mit Code, der in der Dokumentation zu $ ​​transitions enthalten ist. Ich bin ziemlich neu mit dem UI-Router, aber es funktioniert.

Hoffe es kann jedem helfen.


Dies ist ideal für Benutzer des neueren Routers. Vielen Dank!
Mtro

5

Hier ist, wie wir aus der Endlos-Routing-Schleife herausgekommen sind und immer noch $state.gostatt verwendet haben$location.path

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

1
Würde jemand wissen, warum bei Verwendung der oben beschriebenen akzeptierten Antwort / Einrichtung in der Adressleiste nicht mehr die URL und alle Fragmente und Abfragezeichenfolgenparameter angezeigt werden? Seit dieser Implementierung können in der Adressleiste keine Lesezeichen mehr für unsere App gesetzt werden.
Frankie Loscavio

1
Soll dies nicht ein Kommentar zu einer der vorhandenen Antworten sein? Weil es in OP keinen solchen Code gibt und nicht einmal klar ist, auf welche Antwort / auf welchen Code sich dies bezieht
TJ

3

Ich habe eine andere Lösung: Diese Lösung funktioniert perfekt, wenn Sie nur Inhalte haben, die Sie anzeigen möchten, wenn Sie angemeldet sind. Definieren Sie eine Regel, bei der Sie überprüfen, ob Sie angemeldet sind und nicht den Pfad der Whitelist-Routen.

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

In meinem Beispiel frage ich, ob ich nicht angemeldet bin und die aktuelle Route, die ich routen möchte, nicht Teil von "/ login" ist, da meine Whitelist-Routen die folgenden sind

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

Ich habe also sofort Zugriff auf diese beiden Routen und jede andere Route wird überprüft, wenn Sie online sind.

Hier ist meine gesamte Routing-Datei für das Anmeldemodul

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 */ } ist ES6-Syntax, verwenden Sie stattdessen function() { /* code */ }


3

Verwenden Sie $ http Interceptor

Mit einem $ http-Interceptor können Sie Header an das Back-End oder umgekehrt senden und Ihre Überprüfungen auf diese Weise durchführen.

Großartiger Artikel über $ http Interceptors

Beispiel:

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

Fügen Sie dies in Ihre .config- oder .run-Funktion ein.


2

Zunächst benötigen Sie einen Dienst, den Sie in Ihre Controller einfügen können und der eine Vorstellung vom Status der App-Authentifizierung hat. Das Fortbestehen von Authentifizierungsdetails mit lokalem Speicher ist eine gute Möglichkeit, sich dem anzunähern.

Als Nächstes müssen Sie den Status der Authentifizierung überprüfen, bevor sich der Status ändert. Da Ihre App einige Seiten enthält, die authentifiziert werden müssen, und andere, die dies nicht tun, erstellen Sie eine übergeordnete Route, die die Authentifizierung überprüft, und machen Sie alle anderen Seiten, für die dieselbe erforderlich ist, zu einem untergeordneten Element dieses übergeordneten Elements.

Schließlich müssen Sie feststellen, ob Ihr aktuell angemeldeter Benutzer bestimmte Vorgänge ausführen kann. Dies kann erreicht werden, indem Sie Ihrem Authentifizierungsdienst eine 'can'-Funktion hinzufügen. Can akzeptiert zwei Parameter: - Aktion - Erforderlich - (dh 'manage_dashboards' oder 'create_new_dashboard') - Objekt - optional - Objekt, an dem gearbeitet wird. Wenn Sie beispielsweise ein Dashboard-Objekt hatten, möchten Sie möglicherweise überprüfen, ob dashboard.ownerId === loggedInUser.id ist. (Natürlich sollten vom Client übergebene Informationen niemals vertrauenswürdig sein, und Sie sollten dies immer auf dem Server überprüfen, bevor Sie sie in Ihre Datenbank schreiben.)

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

** HAFTUNGSAUSSCHLUSS: Der obige Code ist Pseudocode und wird ohne Garantie geliefert **

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.