javascript - AngularJS - Innlogging og autentisering i hver rute og kontroller


Jeg har en AngularJS-applikasjon laget ved å bruke Yeoman, Grunt og Bower.

Jeg har en påloggingsside som har en kontroller som sjekker autentisering. Hvis legitimasjonen er korrekt, videresender jeg til hjemmesiden.

app.js

'use strict';
//Define Routing for app
angular.module('myApp', []).config(['$routeProvider', '$locationProvider',
  function($routeProvider,$locationProvider) {
    $routeProvider
    .when('/login', {
        templateUrl: 'login.html',
        controller: 'LoginController'
    })
    .when('/register', {
        templateUrl: 'register.html',
        controller: 'RegisterController'
      })
    .when('/forgotPassword', {
        templateUrl: 'forgotpassword.html',
        controller: 'forgotController'
      })
   .when('/home', {
       templateUrl: 'views/home.html',
       controller: 'homeController'
    })
    .otherwise({
       redirectTo: '/login'
    });
//    $locationProvider.html5Mode(true); //Remove the '#' from URL.
}]);

angular.module('myApp').factory("page", function($rootScope){
    var page={};
    var user={};
    page.setPage=function(title,bodyClass){
        $rootScope.pageTitle = title;
        $rootScope.bodylayout=bodyClass;
    };
    page.setUser=function(user){
        $rootScope.user=user;
    }
    return page;
});

LoginKontroller.js

'use strict';

angular.module('myApp').controller('LoginController', function($scope, $location, $window,page) {
    page.setPage("Login","login-layout");
    $scope.user = {};
    $scope.loginUser=function()
    {
        var username=$scope.user.name;
        var password=$scope.user.password;
        if(username=="admin" && password=="admin123")
        {
            page.setUser($scope.user);
            $location.path( "/home" );
        }
        else
        {
            $scope.message="Error";
            $scope.messagecolor="alert alert-danger";
        }
    }
});

På hjemmesiden har jeg

<span class="user-info">
    <small>Welcome,
    {{user.name}}
</span>
<span class="logout"><a href="" ng-click="logoutUser()">Logout</a></span>

I loginController sjekker jeg innloggingsinformasjonen, og hvis den er vellykket, setter jeg brukerobjektet i service-fabrikken. Jeg vet ikke om dette er korrekt eller ikke.

Det jeg trenger er at når brukeren er logget inn, setter den en verdi i brukerobjektet slik at alle andre sider kan hente den verdien.

Når som helst ruteendringer skjer, bør kontrolløren sjekke om brukeren er logget inn eller ikke. Hvis ikke, bør den omdirigere til påloggingssiden. Også, hvis brukeren allerede er logget inn og kommer tilbake til siden, bør den gå til hjemmesiden. Kontrolløren bør også sjekke legitimasjonen på alle rutene.

Jeg har hørt om ng-cookies, men jeg vet ikke hvordan jeg skal bruke dem.

Mange av eksemplene jeg så var ikke veldig klare, og de bruker en slags tilgangsroller eller noe. Det vil jeg ikke ha. Jeg vil bare ha et påloggingsfilter. Kan noen gi meg noen ideer?

Svar #1

Min løsning deles inn i 3 deler: tilstanden til brukeren lagres i en tjeneste, i run-metoden overvåker du når ruten endres og sjekker om brukeren har lov til å få tilgang til den forespurte siden, i hovedkontrolleren din overvåker du om tilstanden til brukeren endres.

app.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
    $rootScope.$on('$routeChangeStart', function (event) {

        if (!Auth.isLoggedIn()) {
            console.log('DENY');
            event.preventDefault();
            $location.path('/login');
        }
        else {
            console.log('ALLOW');
            $location.path('/home');
        }
    });
}]);

Du bør lage en tjeneste (jeg vil kalle den Auth) som vil håndtere brukerobjektet og ha en metode for å vite om brukeren er logget inn eller ikke.

tjeneste:

 .factory('Auth', function(){
var user;

return{
    setUser : function(aUser){
        user = aUser;
    },
    isLoggedIn : function(){
        return(user)? user : false;
    }
  }
})

Fra din app.run bør du lytte til $routeChangeStart-hendelsen. Når ruten endres, vil den sjekke om brukeren er logget inn (metoden isLoggedIn bør håndtere dette). Den vil ikke laste den forespurte ruten hvis brukeren ikke er logget inn, og den vil omdirigere brukeren til riktig side (i ditt tilfelle login).

loginController bør brukes på innloggingssiden din for å håndtere innlogging. Den bør bare samhandle med Auth-tjenesten og sette brukeren som innlogget eller ikke.

loginController:

.controller('loginCtrl', [ '$scope', 'Auth', function ($scope, Auth) {
  //submit
  $scope.login = function () {
    // Ask to the server, do your job and THEN set the user

    Auth.setUser(user); //Update the state of the user in the app
  };
}])

Fra hovedkontrolleren din kan du lytte hvis brukertilstanden endres og reagere med en omdirigering.

.controller('mainCtrl', ['$scope', 'Auth', '$location', function ($scope, Auth, $location) {

  $scope.$watch(Auth.isLoggedIn, function (value, oldValue) {

    if(!value && oldValue) {
      console.log("Disconnect");
      $location.path('/login');
    }

    if(value) {
      console.log("Connect");
      //Do something when the user is connected
    }

  }, true);

Svar #2

Her er en annen mulig løsning, ved å bruke resolve-attributtet til $stateProvider eller $routeProvider. Eksempel med $stateProvider:

.config(["$stateProvider", function ($stateProvider) {

  $stateProvider

  .state("forbidden", {
    /* ... */
  })

  .state("signIn", {
    /* ... */
    resolve: {
      access: ["Access", function (Access) { return Access.isAnonymous(); }],
    }
  })

  .state("home", {
    /* ... */
    resolve: {
      access: ["Access", function (Access) { return Access.isAuthenticated(); }],
    }
  })

  .state("admin", {
    /* ... */
    resolve: {
      access: ["Access", function (Access) { return Access.hasRole("ROLE_ADMIN"); }],
    }
  });

}])

Access løser eller avviser et løfte avhengig av de nåværende brukerrettighetene:

.factory("Access", ["$q", "UserProfile", function ($q, UserProfile) {

  var Access = {

    OK: 200,

    // "we don't know who you are, so we can't say if you're authorized to access
    // this resource or not yet, please sign in first"
    UNAUTHORIZED: 401,

    // "we know who you are, and your profile does not allow you to access this resource"
    FORBIDDEN: 403,

    hasRole: function (role) {
      return UserProfile.then(function (userProfile) {
        if (userProfile.$hasRole(role)) {
          return Access.OK;
        } else if (userProfile.$isAnonymous()) {
          return $q.reject(Access.UNAUTHORIZED);
        } else {
          return $q.reject(Access.FORBIDDEN);
        }
      });
    },

    hasAnyRole: function (roles) {
      return UserProfile.then(function (userProfile) {
        if (userProfile.$hasAnyRole(roles)) {
          return Access.OK;
        } else if (userProfile.$isAnonymous()) {
          return $q.reject(Access.UNAUTHORIZED);
        } else {
          return $q.reject(Access.FORBIDDEN);
        }
      });
    },

    isAnonymous: function () {
      return UserProfile.then(function (userProfile) {
        if (userProfile.$isAnonymous()) {
          return Access.OK;
        } else {
          return $q.reject(Access.FORBIDDEN);
        }
      });
    },

    isAuthenticated: function () {
      return UserProfile.then(function (userProfile) {
        if (userProfile.$isAuthenticated()) {
          return Access.OK;
        } else {
          return $q.reject(Access.UNAUTHORIZED);
        }
      });
    }

  };

  return Access;

}])

UserProfile kopierer de nåværende brukerens egenskaper, og implementerer logikken for metodene $hasRole, $hasAnyRole, $isAnonymous og $isAuthenticated (i tillegg til en $refresh-metode, forklart senere):

.factory("UserProfile", ["Auth", function (Auth) {

  var userProfile = {};

  var clearUserProfile = function () {
    for (var prop in userProfile) {
      if (userProfile.hasOwnProperty(prop)) {
        delete userProfile[prop];
      }
    }
  };

  var fetchUserProfile = function () {
    return Auth.getProfile().then(function (response) {
      clearUserProfile();
      return angular.extend(userProfile, response.data, {

        $refresh: fetchUserProfile,

        $hasRole: function (role) {
          return userProfile.roles.indexOf(role) >= 0;
        },

        $hasAnyRole: function (roles) {
          return !!userProfile.roles.filter(function (role) {
            return roles.indexOf(role) >= 0;
          }).length;
        },

        $isAnonymous: function () {
          return userProfile.anonymous;
        },

        $isAuthenticated: function () {
          return !userProfile.anonymous;
        }

      });
    });
  };

  return fetchUserProfile();

}])

Auth har ansvar for å forespørre serveren, for å kjenne til brukerprofilen (tilknyttet et tilgangstoken festet til forespørselen for eksempel):

.service("Auth", ["$http", function ($http) {

  this.getProfile = function () {
    return $http.get("api/auth");
  };

}])

Serveren forventes å returnere et slikt JSON-objekt når GET api/auth etterspørres:

{
  "name": "John Doe", // plus any other user information
  "roles": ["ROLE_ADMIN", "ROLE_USER"], // or any other role (or no role at all, i.e. an empty array)
  "anonymous": false // or true
}

Til slutt, når Access avviser et løfte, hvis du bruker ui.router, vil $stateChangeError-hendelsen bli utløst:

.run(["$rootScope", "Access", "$state", "$log", function ($rootScope, Access, $state, $log) {

  $rootScope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) {
    switch (error) {

    case Access.UNAUTHORIZED:
      $state.go("signIn");
      break;

    case Access.FORBIDDEN:
      $state.go("forbidden");
      break;

    default:
      $log.warn("$stateChangeError event catched");
      break;

    }
  });

}])

Hvis du bruker ngRoute, vil $routeChangeError-hendelsen bli utløst:

.run(["$rootScope", "Access", "$location", "$log", function ($rootScope, Access, $location, $log) {

  $rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
    switch (rejection) {

    case Access.UNAUTHORIZED:
      $location.path("/signin");
      break;

    case Access.FORBIDDEN:
      $location.path("/forbidden");
      break;

    default:
      $log.warn("$stateChangeError event catched");
      break;

    }
  });

}])

Brukerprofilen kan også nås i kontrollerne:

.state("home", {
  /* ... */
  controller: "HomeController",
  resolve: {
    userProfile: "UserProfile"
  }
})

UserProfile inneholder da egenskapene som returneres av serveren når man sender forespørsel GET api/auth:

.controller("HomeController", ["$scope", "userProfile", function ($scope, userProfile) {

  $scope.title = "Hello " + userProfile.name; // "Hello John Doe" in the example

}])

UserProfile må oppdateres når en bruker logger inn eller ut, slik at Access kan håndtere rutene med den nye brukerprofilen. Du kan enten laste hele siden på nytt, eller kalle UserProfile.$refresh(). Eksempel ved innlogging:

.service("Auth", ["$http", function ($http) {

  /* ... */

  this.signIn = function (credentials) {
    return $http.post("api/auth", credentials).then(function (response) {
      // authentication succeeded, store the response access token somewhere (if any)
    });
  };

}])
.state("signIn", {
  /* ... */
  controller: "SignInController",
  resolve: {
    /* ... */
    userProfile: "UserProfile"
  }
})
.controller("SignInController", ["$scope", "$state", "Auth", "userProfile", function ($scope, $state, Auth, userProfile) {

  $scope.signIn = function () {
    Auth.signIn($scope.credentials).then(function () {
      // user successfully authenticated, refresh UserProfile
      return userProfile.$refresh();
    }).then(function () {
      // UserProfile is refreshed, redirect user somewhere
      $state.go("home");
    });
  };

}])

Svar #3

Den mest direkte måten å definere egendefinert atferd for individuelle ruter på, ville være ganske enkel:

1) routes.js: opprett en ny egenskap (som requireAuth) for hvilken som helst ønsket rute

angular.module('yourApp').config(function($routeProvider) {
    $routeProvider
        .when('/home', {
            templateUrl: 'templates/home.html',
            requireAuth: true // our custom property
        })
        .when('/login', {
            templateUrl: 'templates/login.html',
        })
        .otherwise({
            redirectTo: '/home'
        });
})

2) I en toppnivå-kontroller som ikke er bundet til et element inne i ng-view (for å unngå konflikt med Angular $routeProvider), sjekk om newUrl har requireAuth-egenskapen og handle deretter

 angular.module('YourApp').controller('YourController', function ($scope, $location, session) {
     
     // intercept the route change event
     $scope.$on('$routeChangeStart', function (angularEvent, newUrl) {
         
         // check if the custom property exist
         if (newUrl.requireAuth && !session.user) {
             
             // user isn’t authenticated
             $location.path("/login");
         }
     });
 });

Relaterte artikler