[javascript] AngularJS UI 라우터 로그인 인증

나는 AngularJS를 처음 사용하고 다음 시나리오에서 angular- “ui-router”를 사용하는 방법에 대해 약간 혼란스러워합니다.

두 섹션으로 구성된 웹 응용 프로그램을 작성 중입니다. 첫 번째 섹션은 로그인 및 가입보기가있는 홈페이지이고 두 번째 섹션은 로그인에 성공한 대시 보드입니다.

index.html각도 앱과 ui-router구성을 사용하여 처리 /login하고 /signup볼 수 있는 홈 섹션을 만들었 dashboard.html으며 앱과 ui-router구성을 사용하여 많은 하위보기를 처리 하는 대시 보드 섹션에 대한 다른 파일 이 있습니다.

이제 대시 보드 섹션을 마쳤으며 두 섹션을 서로 다른 각도 앱과 결합하는 방법을 모르겠습니다. 홈 앱에 대시 보드 앱으로 리디렉션하도록 지시하려면 어떻게해야합니까?



답변

더 나은 데모를 만들고 이러한 서비스 중 일부를 사용 가능한 모듈로 정리하는 과정에 있습니다. 그러나 여기에 제가 제시 한 내용이 있습니다. 이는 일부주의 사항을 해결하기위한 복잡한 프로세스이므로 여기에 연결하십시오. 이것을 여러 조각으로 나누어야합니다.

이 펑크를 살펴보십시오 .

먼저, 사용자의 신원을 저장하는 서비스가 필요합니다. 나는 이것을 부른다 principal. 사용자가 로그인했는지 확인할 수 있으며 요청시 사용자 ID에 대한 필수 정보를 나타내는 개체를 확인할 수 있습니다. 필요한 것은 무엇이든 가능하지만 필수는 표시 이름, 사용자 이름, 전자 메일 및 사용자가 속한 역할 (앱에 적용되는 경우)입니다. 교장은 역할 점검을 수행하는 방법도 있습니다.

.factory('principal', ['$q', '$http', '$timeout',
  function($q, $http, $timeout) {
    var _identity = undefined,
      _authenticated = false;

    return {
      isIdentityResolved: function() {
        return angular.isDefined(_identity);
      },
      isAuthenticated: function() {
        return _authenticated;
      },
      isInRole: function(role) {
        if (!_authenticated || !_identity.roles) return false;

        return _identity.roles.indexOf(role) != -1;
      },
      isInAnyRole: function(roles) {
        if (!_authenticated || !_identity.roles) return false;

        for (var i = 0; i < roles.length; i++) {
          if (this.isInRole(roles[i])) return true;
        }

        return false;
      },
      authenticate: function(identity) {
        _identity = identity;
        _authenticated = identity != null;
      },
      identity: function(force) {
        var deferred = $q.defer();

        if (force === true) _identity = undefined;

        // check and see if we have retrieved the 
        // identity data from the server. if we have, 
        // reuse it by immediately resolving
        if (angular.isDefined(_identity)) {
          deferred.resolve(_identity);

          return deferred.promise;
        }

        // otherwise, retrieve the identity data from the
        // server, update the identity object, and then 
        // resolve.
        //           $http.get('/svc/account/identity', 
        //                     { ignoreErrors: true })
        //                .success(function(data) {
        //                    _identity = data;
        //                    _authenticated = true;
        //                    deferred.resolve(_identity);
        //                })
        //                .error(function () {
        //                    _identity = null;
        //                    _authenticated = false;
        //                    deferred.resolve(_identity);
        //                });

        // for the sake of the demo, fake the lookup
        // by using a timeout to create a valid
        // fake identity. in reality,  you'll want 
        // something more like the $http request
        // commented out above. in this example, we fake 
        // looking up to find the user is
        // not logged in
        var self = this;
        $timeout(function() {
          self.authenticate(null);
          deferred.resolve(_identity);
        }, 1000);

        return deferred.promise;
      }
    };
  }
])

둘째, 사용자가 가고 싶은 상태를 확인하고 로그인했는지 (필요한 경우, 로그인, 비밀번호 재설정 등에 필요하지 않음) 확인한 다음 역할 확인 (앱이있는 경우)을 수행하는 서비스가 필요합니다 이 필요합니다). 인증되지 않은 경우 로그인 페이지로 보내십시오. 인증되었지만 역할 확인에 실패하면 액세스 거부 페이지로 보내십시오. 이 서비스를 호출합니다 authorization.

.factory('authorization', ['$rootScope', '$state', 'principal',
  function($rootScope, $state, principal) {
    return {
      authorize: function() {
        return principal.identity()
          .then(function() {
            var isAuthenticated = principal.isAuthenticated();

            if ($rootScope.toState.data.roles
                && $rootScope.toState
                             .data.roles.length > 0
                && !principal.isInAnyRole(
                   $rootScope.toState.data.roles))
            {
              if (isAuthenticated) {
                  // user is signed in but not
                  // authorized for desired state
                  $state.go('accessdenied');
              } else {
                // user is not authenticated. Stow
                // the state they wanted before you
                // send them to the sign-in state, so
                // you can return them when you're done
                $rootScope.returnToState
                    = $rootScope.toState;
                $rootScope.returnToStateParams
                    = $rootScope.toStateParams;

                // now, send them to the signin state
                // so they can log in
                $state.go('signin');
              }
            }
          });
      }
    };
  }
])

이제 모든 사용자는가에 경청 할 필요가 ui-router$stateChangeStart. 이를 통해 현재 상태, 가고 싶은 상태를 검사하고 권한 확인을 삽입 할 수 있습니다. 실패하면 경로 전환을 취소하거나 다른 경로로 변경할 수 있습니다.

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

사용자의 신원을 추적하는 데있어 까다로운 부분은 이미 인증을받은 경우 (예 : 이전 세션 후 페이지를 방문하고 쿠키에 인증 토큰을 저장했거나 페이지를 강제로 새로 고침 한 경우) 확인하는 것입니다. 링크에서 URL로 삭제). ui-router작동 방식으로 인해 인증 확인 전에 신원 확인을 한 번 수행해야합니다. resolve상태 구성 의 옵션을 사용하여이 작업을 수행 할 수 있습니다 . 모든 상태가 상속되는 사이트에 대해 하나의 부모 상태가 있으므로 다른 일이 발생하기 전에 교장을 해결해야합니다.

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

여기 또 다른 문제가 있습니다 … resolve한 번만 호출됩니다. 신원 조회에 대한 약속이 완료되면 해결 대리인을 다시 실행하지 않습니다. 따라서 두 가지 위치에서 인증 확인을 수행해야합니다. 한 번은 아이덴티티 약속 해결에 resolve따라 (이는 앱이 처음로드 될 때를 포함하고 $stateChangeStart, 해결이 완료된 경우에 한 번) 상태를 탐색 할 때마다 적용됩니다.

좋아, 지금까지 우리는 무엇을 했는가?

  1. 사용자가 로그인했는지 앱이 언제로드되는지 확인합니다.
  2. 로그인 한 사용자에 대한 정보를 추적합니다.
  3. 사용자가 로그인해야하는 상태에 대해 로그인 상태로 리디렉션합니다.
  4. 액세스 권한이없는 경우 액세스 거부 상태로 리디렉션합니다.
  5. 로그인이 필요한 경우 사용자를 요청한 원래 상태로 다시 리디렉션하는 메커니즘이 있습니다.
  6. 사용자를 로그 아웃 할 수 있습니다 (인증 티켓을 관리하는 모든 클라이언트 또는 서버 코드와 연계하여 연결해야 함).
  7. 우리는 하지 않습니다 로그인 페이지로 다시 그들의 브라우저를 다시로드하거나 링크를 드롭 할 때마다 사용자를 보내야합니다.

여기서 어디로 가나 요? 글쎄, 당신은 로그인이 필요 지역으로 상태를 구성 할 수 있습니다. 당신은 추가 인증 / 권한이 부여 된 사용자를 요구할 수 dataroles(상속을 사용하려는 경우, 또는 이들의 부모)이 상태로. 여기서는 리소스를 관리자로 제한합니다.

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

이제 사용자가 라우트에 액세스 할 수있는 상태를 상태별로 제어 할 수 있습니다. 다른 문제가 있습니까? 로그인 여부에 따라보기의 일부만 다를 수 있습니까? 문제 없어요. 를 사용 principal.isAuthenticated()하거나 principal.isInRole()수많은 방법 중 하나를 사용하면 조건부 서식 또는 요소를 표시 할 수 있습니다.

먼저 principal컨트롤러 또는 다른 것에 주입 하고 스코프에 고정하여보기에서 쉽게 사용할 수 있도록하십시오.

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

요소 표시 또는 숨기기

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

기타 등등. 어쨌든 예제 앱에서는 인증되지 않은 사용자가 방문 할 수있는 홈페이지 상태가됩니다. 로그인 또는 가입 상태에 대한 링크가 있거나 해당 양식을 해당 페이지에 내장 할 수 있습니다. 당신에게 맞는 것이 무엇이든.

대시 보드 페이지는 모두 사용자가 로그인해야하는 상태 (예 : User역할 구성원) 에서 상속 될 수 있습니다 . 우리가 논의한 모든 인증 항목은 거기서부터 흐릅니다.


답변

지금까지 게시 된 솔루션은 불필요하게 복잡합니다. 더 간단한 방법이 있습니다. 의 문서는ui-router 듣는 말한다 $locationChangeSuccess사용 $urlRouter.sync()상태 변화를 확인하기 위해, 그것을 중단하거나 재개. 그러나 그조차도 실제로 작동하지 않습니다.

그러나 여기에 두 가지 간단한 대안이 있습니다. 하나를 선택:

해결 방법 1 : 듣기 $locationChangeSuccess

당신은들을 수 $locationChangeSuccess있고 어떤 로직, 심지어 비동기 로직을 ​​수행 할 수 있습니다. 해당 논리를 기반으로 함수가 정의되지 않은 상태로 리턴되도록하여 상태 전이가 정상적으로 계속되도록하거나 $state.go('logInPage')사용자를 인증해야하는 경우 수행 할 수 있습니다 . 예를 들면 다음과 같습니다.

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

이렇게해도 실제로 대상 상태가로드되지는 않지만 사용자가 권한이없는 경우 로그인 페이지로 리디렉션됩니다. 어쨌든 진정한 보호 기능이 서버에 있기 때문에 괜찮습니다.

해결 방법 2 : 상태 사용 resolve

이 솔루션에서는 ui-routerresolve feature 를 사용 합니다 .

기본적으로 resolve사용자가 인증되지 않은 경우 약속을 거부 한 다음 로그인 페이지로 리디렉션합니다.

방법은 다음과 같습니다.

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

첫 번째 솔루션과 달리이 솔루션은 실제로 대상 상태가로드되지 않도록합니다.


답변

가장 쉬운 해결책은 사용자가 인증되지 않은 경우 상태 변경 을 사용 $stateChangeStart하고 event.preventDefault()취소 하여 로그인 페이지 인 인증 상태로 리디렉션 하는 것입니다.

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


답변

service인증 프로세스 (및 저장소)를 처리 하는 것이 필요하다고 생각합니다 .

이 서비스에는 몇 가지 기본 방법이 필요합니다.

  • isAuthenticated()
  • login()
  • logout()
  • 등 …

이 서비스는 각 모듈의 컨트롤러에 삽입해야합니다.

  • 대시 보드 섹션에서이 서비스를 사용하여 사용자가 인증되었는지 확인하십시오 ( service.isAuthenticated()메소드). 그렇지 않은 경우 / login으로 리디렉션
  • 로그인 섹션에서 양식 데이터를 사용하여 service.login()메소드를 통해 사용자를 인증하십시오.

이 동작에 대한 강력하고 강력한 예는 프로젝트 angular-app 및 특히 멋진 HTTP Auth 인터셉터 모듈을 기반으로하는 보안 모듈 입니다

도움이 되었기를 바랍니다


답변

이 프로세스를 케이크 조각으로 만들기 위해이 모듈을 만들었습니다.

다음과 같은 작업을 수행 할 수 있습니다.

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

또는

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

새롭지 만 체크 아웃 할 가치가 있습니다!

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


답변

ui router 1.0.0.X로 작업하는 다른 솔루션을 공유하고 싶었습니다.

아시다시피 stateChangeStart 및 stateChangeSuccess는 더 이상 사용되지 않습니다. https://github.com/angular-ui/ui-router/issues/2655

대신 $ transitions http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html 을 사용해야합니다.

이것이 내가 달성 한 방법입니다.

먼저 유용한 기능이있는 AuthService 가 있습니다.

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

그런 다음이 구성이 있습니다.

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

내가 사용하는 것을 볼 수 있습니다

data: {
   authRequired: true
}

인증 된 경우에만 액세스 가능한 상태로 표시합니다.

그런 다음 .run 에서 전환을 사용하여 인증 된 상태를 확인합니다.

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

$ transitions 문서에서 찾은 일부 코드를 사용하여이 예제를 빌드합니다. 나는 ui 라우터를 처음 접했지만 작동합니다.

그것이 누군가를 도울 수 있기를 바랍니다.


답변

다음은 우리가 무한 라우팅 루프에서 빠져 나와 $state.go대신에 어떻게 사용되는지 입니다.$location.path

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