Nice programing

AngularJS 인증 + RESTful API

nicepro 2020. 10. 30. 20:59
반응형

AngularJS 인증 + RESTful API


인증 / (재) 라우팅을위한 API가 포함 된 Angular + RESTful 클라이언트 측 통신

이것은 몇 가지 다른 질문과 몇 가지 다른 자습서에서 다루었지만 이전에 접한 모든 리소스가 머리에 제대로 맞지 않았습니다.

간단히 말해서, 나는

  • 에서 POST를 통해 로그인 http://client.foohttp://api.foo/login
  • logout경로 를 제공하는 사용자에 대해 "로그인 된"GUI / 구성 요소 상태가 있어야 합니다.
  • 사용자가 로그 아웃 / 로그 아웃 할 때 UI를 "업데이트"할 수 있습니다. 이것은 가장 실망 스러웠습니다.
  • 내 경로를 보호하여 인증 상태 (필요한 경우)를 확인하고 그에 따라 사용자를 로그인 페이지로 리디렉션합니다.

내 문제는

  • 다른 페이지로 이동할 때마다 api.foo/status사용자가 로그인했는지 여부를 확인 하기 위해 전화를 걸어야합니다. (ATM은 경로에 Express를 사용하고 있습니다.) Angular가 다음과 같은 사항을 결정함에 따라 딸꾹질이 발생합니다.ng-show="user.is_authenticated"
  • 로그인 / 로그 아웃에 성공하면 페이지를 새로 고침 (이렇게하고 싶지 않음)하여와 같은 항목을 채우 {{user.first_name}}거나 로그 아웃의 경우 해당 값을 비워야합니다.
// Sample response from `/status` if successful 

{
   customer: {...},
   is_authenticated: true,
   authentication_timeout: 1376959033,
   ...
}

내가 시도한 것

정신이 나간 것 같은 이유

  • 모든 튜토리얼이 일부 데이터베이스 (많은 Mongo, Couch, PHP + MySQL, ad infinitum) 솔루션에 의존하고 로그인 상태를 유지하기 위해 RESTful API와의 통신에만 의존하는 사람은없는 것 같습니다. 로그인하면 추가 POST / GET이와 함께 전송 withCredentials:true되므로 문제가되지 않습니다.
  • 백엔드 언어가 아닌 Angular + REST + Auth를 수행하는 예제 / 튜토리얼 / 리포지토리를 찾을 수 없습니다.

너무 자랑스럽지 않아

물론, 저는 Angular를 처음 접했고, 우스꽝스러운 방식으로 접근한다면 놀라지 않을 것입니다. 누군가가 수프 대 견과류라도 대안을 제안하면 기뻐할 것입니다.

나는 Express내가 정말로 좋아하기 때문에 주로 사용 Jade하고있다 Stylus. 나는 Express라우팅과 결혼하지 않았고 내가 원하는 것이 Angular의 라우팅으로 만 가능하다면 포기할 것이다.

누구든지 제공 할 수있는 도움에 미리 감사드립니다. 그리고 제발 Google에 요청하지 마세요. 약 26 페이지의 보라색 링크가 있습니다. ;-)


1 이 솔루션은 Angular의 $ httpBackend 모의에 의존하며 실제 서버와 통신하는 방법이 명확하지 않습니다.

2 가장 가깝지만 인증이 필요한 기존 API가있어 여권의 'localStrategy'를 사용할 수 없었고 , OAUTH 서비스를 작성하는 것이 제정신아닌 것 같았습니다 .


이 URL 루트 권한 부여 및 요소 보안에 내 블로그 게시물에서 가져 여기 지만 간략하게 요약에게 요점을 것입니다 :-)

프론트 엔드 웹 애플리케이션의 보안은 Joe Public을 중지하기위한 시작 조치 일뿐입니다. 그러나 웹 지식이있는 모든 사용자는이를 우회 할 수 있으므로 항상 서버 측 보안을 유지해야합니다.

각도에서 보안 항목에 대한 주요 관심사는 경로 보안입니다. 다행히 각도로 경로를 정의 할 때 다른 속성을 가질 수있는 객체 인 객체를 생성합니다. 내 접근 방식의 초석은 기본적으로 사용자가 특정 경로에 액세스 할 수있는 역할을 정의하는이 경로 개체에 보안 개체를 추가하는 것입니다.

 // route which requires the user to be logged in and have the 'Admin' or 'UserManager' permission
    $routeProvider.when('/admin/users', {
        controller: 'userListCtrl',
        templateUrl: 'js/modules/admin/html/users.tmpl.html',
        access: {
            requiresLogin: true,
            requiredPermissions: ['Admin', 'UserManager'],
            permissionType: 'AtLeastOne'
        });

전체 접근 방식은 기본적으로 사용자에게 필요한 권한이 있는지 확인하는 인증 서비스에 중점을 둡니다. 이 서비스는 로그인 중에 서버에서 검색된 사용자 및 실제 권한과 관련된이 솔루션의 다른 부분에서 문제를 추상화합니다. 코드는 매우 장황하지만 내 블로그 게시물에 자세히 설명되어 있습니다. 그러나 기본적으로 권한 검사와 두 가지 인증 모드를 처리합니다. 첫 번째는 사용자에게 최소한 정의 된 권한이 있어야하고 두 번째는 사용자가 정의 된 모든 권한을 가져야한다는 것입니다.

angular.module(jcs.modules.auth.name).factory(jcs.modules.auth.services.authorization, [  
'authentication',  
function (authentication) {  
 var authorize = function (loginRequired, requiredPermissions, permissionCheckType) {
    var result = jcs.modules.auth.enums.authorised.authorised,
        user = authentication.getCurrentLoginUser(),
        loweredPermissions = [],
        hasPermission = true,
        permission, i;

    permissionCheckType = permissionCheckType || jcs.modules.auth.enums.permissionCheckType.atLeastOne;
    if (loginRequired === true && user === undefined) {
        result = jcs.modules.auth.enums.authorised.loginRequired;
    } else if ((loginRequired === true && user !== undefined) &&
        (requiredPermissions === undefined || requiredPermissions.length === 0)) {
        // Login is required but no specific permissions are specified.
        result = jcs.modules.auth.enums.authorised.authorised;
    } else if (requiredPermissions) {
        loweredPermissions = [];
        angular.forEach(user.permissions, function (permission) {
            loweredPermissions.push(permission.toLowerCase());
        });

        for (i = 0; i < requiredPermissions.length; i += 1) {
            permission = requiredPermissions[i].toLowerCase();

            if (permissionCheckType === jcs.modules.auth.enums.permissionCheckType.combinationRequired) {
                hasPermission = hasPermission && loweredPermissions.indexOf(permission) > -1;
                // if all the permissions are required and hasPermission is false there is no point carrying on
                if (hasPermission === false) {
                    break;
                }
            } else if (permissionCheckType === jcs.modules.auth.enums.permissionCheckType.atLeastOne) {
                hasPermission = loweredPermissions.indexOf(permission) > -1;
                // if we only need one of the permissions and we have it there is no point carrying on
                if (hasPermission) {
                    break;
                }
            }
        }

        result = hasPermission ?
                 jcs.modules.auth.enums.authorised.authorised :
                 jcs.modules.auth.enums.authorised.notAuthorised;
    }

    return result;
};

이제 경로에 보안이 설정되었으므로 경로 변경이 시작되었을 때 사용자가 경로에 액세스 할 수 있는지 확인하는 방법이 필요합니다. 이를 위해 라우트 변경 요청을 가로 채서 라우트 객체 (새 액세스 객체가있는)를 검사하고 사용자가 뷰에 액세스 할 수없는 경우 라우트를 다른 것으로 대체합니다.

angular.module(jcs.modules.auth.name).run([  
    '$rootScope',
    '$location',
    jcs.modules.auth.services.authorization,
    function ($rootScope, $location, authorization) {
        $rootScope.$on('$routeChangeStart', function (event, next) {
            var authorised;
            if (next.access !== undefined) {
                authorised = authorization.authorize(next.access.loginRequired,
                                                     next.access.permissions,
                                                     next.access.permissionCheckType);
                if (authorised === jcs.modules.auth.enums.authorised.loginRequired) {
                    $location.path(jcs.modules.auth.routes.login);
                } else if (authorised === jcs.modules.auth.enums.authorised.notAuthorised) {
                    $location.path(jcs.modules.auth.routes.notAuthorised).replace();
                }
            }
        });
    }]);

여기서 핵심은 실제로 '.replace ()'입니다. 이것은 현재 경로 (볼 권한이없는 경로)를 우리가 리디렉션하는 경로로 대체하기 때문입니다. 이렇게하면 승인되지 않은 경로로 다시 이동하는 것이 중지됩니다.

이제 라우트를 가로 챌 수 있습니다 . 사용자 가 로그인 해야하는 경로에 도달 한 경우 로그인 후 리디렉션을 포함하여 몇 가지 멋진 작업을 수행 할 수 있습니다 .

솔루션의 두 번째 부분은 권한에 따라 사용자에게 UI 요소를 숨기거나 표시 할 수 있다는 것입니다. 이것은 간단한 지침을 통해 달성됩니다.

angular.module(jcs.modules.auth.name).directive('access', [  
        jcs.modules.auth.services.authorization,
        function (authorization) {
            return {
              restrict: 'A',
              link: function (scope, element, attrs) {
                  var makeVisible = function () {
                          element.removeClass('hidden');
                      },
                      makeHidden = function () {
                          element.addClass('hidden');
                      },
                      determineVisibility = function (resetFirst) {
                          var result;
                          if (resetFirst) {
                              makeVisible();
                          }

                          result = authorization.authorize(true, roles, attrs.accessPermissionType);
                          if (result === jcs.modules.auth.enums.authorised.authorised) {
                              makeVisible();
                          } else {
                              makeHidden();
                          }
                      },
                      roles = attrs.access.split(',');


                  if (roles.length > 0) {
                      determineVisibility(true);
                  }
              }
            };
        }]);

그런 다음 다음과 같은 요소를 확인합니다.

 <button type="button" access="CanEditUser, Admin" access-permission-type="AtLeastOne">Save User</button>

접근 방식에 대한 훨씬 더 자세한 개요를 보려면 전체 블로그 게시물읽어보십시오 .


나는 당신이 요구하는 거의 모든 것을 수행 하는 UserAppAngularJS 모듈작성했습니다 . 다음 중 하나를 수행 할 수 있습니다.

  1. 모듈을 수정하고 함수를 자신의 API에 연결하거나
  2. 사용자 관리 API 인 UserApp 과 함께 모듈 사용

https://github.com/userapp-io/userapp-angular

보호 / 공개 경로, 로그인 / 로그 아웃시 경로 변경, 상태 확인을위한 하트 비트를 지원하고, 쿠키, 이벤트 등에 세션 토큰을 저장합니다.

UserApp을 사용해보고 싶다면 Codecademy에서 과정을 수강하세요 .

작동 방식에 대한 몇 가지 예는 다음과 같습니다.

  • 오류 처리가 포함 된 로그인 양식 :

    <form ua-login ua-error="error-msg">
        <input name="login" placeholder="Username"><br>
        <input name="password" placeholder="Password" type="password"><br>
        <button type="submit">Log in</button>
        <p id="error-msg"></p>
    </form>
    
  • 오류 처리가 포함 된 가입 양식 :

    <form ua-signup ua-error="error-msg">
      <input name="first_name" placeholder="Your name"><br>
      <input name="login" ua-is-email placeholder="Email"><br>
      <input name="password" placeholder="Password" type="password"><br>
      <button type="submit">Create account</button>
      <p id="error-msg"></p>
    </form>
    
  • 공개 할 경로와 로그인 양식 인 경로를 지정하는 방법 :

    $routeProvider.when('/login', {templateUrl: 'partials/login.html', public: true, login: true});
    $routeProvider.when('/signup', {templateUrl: 'partials/signup.html', public: true});
    

    .otherwise()경로는 당신이 당신의 사용자가 로그인 후 리디렉션 할 위치로 설정해야합니다. 예:

    $routeProvider.otherwise({redirectTo: '/home'});

  • 로그 아웃 링크 :

    <a href="#" ua-logout>Log Out</a>

    (세션을 종료하고 로그인 경로로 리디렉션)

  • 사용자 속성에 액세스 :

    사용자 정보는 user서비스를 사용하여 액세스됩니다. 예 :user.current.email

    또는 템플릿에서 : <span>{{ user.email }}</span>

  • 로그인했을 때만 표시되어야하는 요소 숨기기 :

    <div ng-show="user.authorized">Welcome {{ user.first_name }}!</div>

  • 권한에 따라 요소를 표시합니다.

    <div ua-has-permission="admin">You are an admin</div>

백엔드 서비스를 인증하려면을 사용 user.token()하여 세션 토큰을 가져 와서 AJAX 요청과 함께 보냅니다. 백엔드에서 UserApp API ( UserApp을 사용하는 경우)를 사용하여 토큰이 유효한지 확인합니다.

도움이 필요하면 알려주세요 :)


내 응용 프로그램에 대한 서비스 요청을 직접 작성하고 있기 때문에 $ resource를 사용하지 않았습니다. 즉, 일종의 초기화 데이터를 얻는 다른 모든 서비스에 의존하는 서비스를 사용하여 로그인을 처리했습니다. 로그인이 성공하면 모든 서비스의 초기화를 트리거합니다.

내 컨트롤러 범위 내에서 loginServiceInformation을보고 그에 따라 모델의 일부 속성을 채 웁니다 (적절한 ng-show / hide를 트리거하기 위해). 라우팅과 관련하여 Angular의 기본 제공 라우팅을 사용하고 있으며 여기에 표시된 login 부울을 기반으로하는 ng-hide가 있으며 로그인을 요청하는 텍스트 또는 ng-view 속성이있는 div가 표시됩니다 (로그인하지 않은 경우 로그인 후 즉시 올바른 페이지에 표시됩니다. 현재 모든보기에 대한 데이터를로드하지만 필요한 경우 더 선택적으로 표시 될 수 있습니다.)

//Services
angular.module("loginModule.services", ["gardenModule.services",
                                        "surveyModule.services",
                                        "userModule.services",
                                        "cropModule.services"
                                        ]).service(
                                            'loginService',
                                            [   "$http",
                                                "$q",
                                                "gardenService",
                                                "surveyService",
                                                "userService",
                                                "cropService",
                                                function (  $http,
                                                            $q,
                                                            gardenService,
                                                            surveyService,
                                                            userService,
                                                            cropService) {

    var service = {
        loginInformation: {loggedIn:false, username: undefined, loginAttemptFailed:false, loggedInUser: {}, loadingData:false},

        getLoggedInUser:function(username, password)
        {
            service.loginInformation.loadingData = true;
            var deferred = $q.defer();

            $http.get("php/login/getLoggedInUser.php").success(function(data){
                service.loginInformation.loggedIn = true;
                service.loginInformation.loginAttemptFailed = false;
                service.loginInformation.loggedInUser = data;

                gardenService.initialize();
                surveyService.initialize();
                userService.initialize();
                cropService.initialize();

                service.loginInformation.loadingData = false;

                deferred.resolve(data);
            }).error(function(error) {
                service.loginInformation.loggedIn = false;
                deferred.reject(error);
            });

            return deferred.promise;
        },
        login:function(username, password)
        {
            var deferred = $q.defer();

            $http.post("php/login/login.php", {username:username, password:password}).success(function(data){
                service.loginInformation.loggedInUser = data;
                service.loginInformation.loggedIn = true;
                service.loginInformation.loginAttemptFailed = false;

                gardenService.initialize();
                surveyService.initialize();
                userService.initialize();
                cropService.initialize();

                deferred.resolve(data);
            }).error(function(error) {
                service.loginInformation.loggedInUser = {};
                service.loginInformation.loggedIn = false;
                service.loginInformation.loginAttemptFailed = true;
                deferred.reject(error);
            });

            return deferred.promise;
        },
        logout:function()
        {
            var deferred = $q.defer();

            $http.post("php/login/logout.php").then(function(data){
                service.loginInformation.loggedInUser = {};
                service.loginInformation.loggedIn = false;
                deferred.resolve(data);
            }, function(error) {
                service.loginInformation.loggedInUser = {};
                service.loginInformation.loggedIn = false;
                deferred.reject(error);
            });

            return deferred.promise;
        }
    };
    service.getLoggedInUser();
    return service;
}]);

//Controllers
angular.module("loginModule.controllers", ['loginModule.services']).controller("LoginCtrl", ["$scope", "$location", "loginService", function($scope, $location, loginService){

    $scope.loginModel = {
                        loadingData:true,
                        inputUsername: undefined,
                        inputPassword: undefined,
                        curLoginUrl:"partials/login/default.html",
                        loginFailed:false,
                        loginServiceInformation:{}
                        };

    $scope.login = function(username, password) {
        loginService.login(username,password).then(function(data){
            $scope.loginModel.curLoginUrl = "partials/login/logoutButton.html";
        });
    }
    $scope.logout = function(username, password) {
        loginService.logout().then(function(data){
            $scope.loginModel.curLoginUrl = "partials/login/default.html";
            $scope.loginModel.inputPassword = undefined;
            $scope.loginModel.inputUsername = undefined;
            $location.path("home");
        });
    }
    $scope.switchUser = function(username, password) {
        loginService.logout().then(function(data){
            $scope.loginModel.curLoginUrl = "partials/login/loginForm.html";
            $scope.loginModel.inputPassword = undefined;
            $scope.loginModel.inputUsername = undefined;
        });
    }
    $scope.showLoginForm = function() {
        $scope.loginModel.curLoginUrl = "partials/login/loginForm.html";
    }
    $scope.hideLoginForm = function() {
        $scope.loginModel.curLoginUrl = "partials/login/default.html";
    }

    $scope.$watch(function(){return loginService.loginInformation}, function(newVal) {
        $scope.loginModel.loginServiceInformation = newVal;
        if(newVal.loggedIn)
        {
            $scope.loginModel.curLoginUrl = "partials/login/logoutButton.html";
        }
    }, true);
}]);

angular.module("loginModule", ["loginModule.services", "loginModule.controllers"]);

HTML

<div style="height:40px;z-index:200;position:relative">
    <div class="well">
        <form
            ng-submit="login(loginModel.inputUsername, loginModel.inputPassword)">
            <input
                type="text"
                ng-model="loginModel.inputUsername"
                placeholder="Username"/><br/>
            <input
                type="password"
                ng-model="loginModel.inputPassword"
                placeholder="Password"/><br/>
            <button
                class="btn btn-primary">Submit</button>
            <button
                class="btn"
                ng-click="hideLoginForm()">Cancel</button>
        </form>
        <div
            ng-show="loginModel.loginServiceInformation.loginAttemptFailed">
            Login attempt failed
        </div>
    </div>
</div>

위의 부분을 사용하여 그림을 완성하는 기본 HTML :

<body ng-controller="NavigationCtrl" ng-init="initialize()">
        <div id="outerContainer" ng-controller="LoginCtrl">
            <div style="height:20px"></div>
            <ng-include src="'partials/header.html'"></ng-include>
            <div  id="contentRegion">
                <div ng-hide="loginModel.loginServiceInformation.loggedIn">Please login to continue.
                <br/><br/>
                This new version of this site is currently under construction.
                <br/><br/>
                If you need the legacy site and database <a href="legacy/">click here.</a></div>
                <div ng-view ng-show="loginModel.loginServiceInformation.loggedIn"></div>
            </div>
            <div class="clear"></div>
            <ng-include src="'partials/footer.html'"></ng-include>
        </div>
    </body>

로그 인 변수를 기반으로 페이지의 본문 영역을 변경할 수 있도록 DOM에서 더 높은 ng 컨트롤러로 로그인 컨트롤러를 정의했습니다.

참고 아직 여기에서 양식 유효성 검사를 구현하지 않았습니다. 또한 Angular에 대해 여전히 매우 신선하므로이 게시물의 모든 포인터를 환영합니다. 이것이 RESTful 기반 구현이 아니기 때문에 질문에 직접 답하지는 않지만 $ http 호출을 기반으로 구축되었으므로 $ resources에도 동일하게 적용될 수 있다고 생각합니다.


기본적으로이 기사를 요약 한 github 저장소를 만들었습니다. https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec

ng-login Github 저장소

플 런커

가능한 한 잘 설명하려고 노력하겠습니다. 여러분 중 일부를 돕길 바랍니다.

(1) app.js : 앱 정의에 대한 인증 상수 생성

var loginApp = angular.module('loginApp', ['ui.router', 'ui.bootstrap'])
/*Constants regarding user login defined here*/
.constant('USER_ROLES', {
    all : '*',
    admin : 'admin',
    editor : 'editor',
    guest : 'guest'
}).constant('AUTH_EVENTS', {
    loginSuccess : 'auth-login-success',
    loginFailed : 'auth-login-failed',
    logoutSuccess : 'auth-logout-success',
    sessionTimeout : 'auth-session-timeout',
    notAuthenticated : 'auth-not-authenticated',
    notAuthorized : 'auth-not-authorized'
})

(2) Auth Service: All following functions are implemented in auth.js service. The $http service is used to communicate with the server for the authentication procedures. Also contains functions on authorization, that is if the user is allowed to perform a certain action.

angular.module('loginApp')
.factory('Auth', [ '$http', '$rootScope', '$window', 'Session', 'AUTH_EVENTS', 
function($http, $rootScope, $window, Session, AUTH_EVENTS) {

authService.login() = [...]
authService.isAuthenticated() = [...]
authService.isAuthorized() = [...]
authService.logout() = [...]

return authService;
} ]);

(3) Session: A singleton to keep user data. The implementation here depends on you.

angular.module('loginApp').service('Session', function($rootScope, USER_ROLES) {

    this.create = function(user) {
        this.user = user;
        this.userRole = user.userRole;
    };
    this.destroy = function() {
        this.user = null;
        this.userRole = null;
    };
    return this;
});

(4) Parent controller: Consider this as the "main" function of your application, all controllers inherit from this controller, and it's the backbone of the authentication of this app.

<body ng-controller="ParentController">
[...]
</body>

(5) Access control: To deny access on certain routes 2 steps have to be implemented:

a) Add data of the roles allowed to access each route, on ui router's $stateProvider service as can be seen below (same can work for ngRoute).

.config(function ($stateProvider, USER_ROLES) {
  $stateProvider.state('dashboard', {
    url: '/dashboard',
    templateUrl: 'dashboard/index.html',
    data: {
      authorizedRoles: [USER_ROLES.admin, USER_ROLES.editor]
    }
  });
})

b) On $rootScope.$on('$stateChangeStart') add the function to prevent state change if the user is not authorized.

$rootScope.$on('$stateChangeStart', function (event, next) {
    var authorizedRoles = next.data.authorizedRoles;
    if (!Auth.isAuthorized(authorizedRoles)) {
      event.preventDefault();
      if (Auth.isAuthenticated()) {
        // user is not allowed
        $rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
      } else {d
        // user is not logged in
        $rootScope.$broadcast(AUTH_EVENTS.notAuthenticated);
      }
    }
});

(6) Auth interceptor: This is implemented, but can't be checked on the scope of this code. After each $http request, this interceptor checks the status code, if one of the below is returned, then it broadcasts an event to force the user to log-in again.

angular.module('loginApp')
.factory('AuthInterceptor', [ '$rootScope', '$q', 'Session', 'AUTH_EVENTS',
function($rootScope, $q, Session, AUTH_EVENTS) {
    return {
        responseError : function(response) {
            $rootScope.$broadcast({
                401 : AUTH_EVENTS.notAuthenticated,
                403 : AUTH_EVENTS.notAuthorized,
                419 : AUTH_EVENTS.sessionTimeout,
                440 : AUTH_EVENTS.sessionTimeout
            }[response.status], response);
            return $q.reject(response);
        }
    };
} ]);

P.S. A bug with the form data autofill as stated on the 1st article can be easily avoided by adding the directive that is included in directives.js.

P.S.2 This code can be easily tweaked by the user, to allow different routes to be seen, or display content that was not meant to be displayed. The logic MUST be implemented server-side, this is just a way to show things properly on your ng-app.

참고URL : https://stackoverflow.com/questions/18325324/angularjs-authentication-restful-api

반응형