Аутентификация AngularJS + RESTful API

Связь на стороне клиента Angular+RESTful с API для аутентификации/(ре)маршрутизации

Это было рассмотрено в нескольких разных вопросах и в нескольких разных учебниках, но все предыдущие ресурсы, с которыми я столкнулся, не совсем попали в точку.

Короче говоря, мне нужно

  • Вход через POST с http://client.foo по http://api.foo/login
  • Иметь состояние графического интерфейса/компонента «вошел в систему» ​​для пользователя, который предоставляет маршрут logout
  • Иметь возможность «обновлять» UI, когда пользователь выходит/выходит из системы. Это было самым неприятным
  • Защитите мои маршруты для проверки состояния аутентификации (если им это нужно) и соответствующим образом перенаправьте пользователя на страницу входа

Мои проблемы

  • Каждый раз, когда я перехожу на другую страницу, мне нужно сделать вызов api.foo/status, чтобы определить, вошел ли пользователь в систему. (Я использую банкомат 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, до бесконечности), и ни одно из них не полагается исключительно на связь с RESTful API для сохранения состояний входа в систему. После входа в систему дополнительные POST/GET отправляются с withCredentials:true, так что это не проблема.
  • Я не могу найти ЛЮБЫЕ примеры/учебники/репозитории, которые используют Angular+REST+Auth, без внутреннего языка.

Я не слишком горжусь

По общему признанию, я новичок в Angular, и не удивлюсь, если подхожу к этому нелепо; Я был бы в восторге, если бы кто-нибудь предложил альтернативу, даже если это суп с орехами.

Я использую Express в основном потому, что мне очень нравятся Jade и Stylus — я не женат на маршрутизации Express и откажусь от нее, если то, что я хочу сделать, возможно только с маршрутизацией Angular.

Заранее благодарим за любую помощь, которую может оказать любой. И, пожалуйста, не просите меня гуглить, потому что у меня около 26 страниц фиолетовых ссылок. ;-)


1Это решение основано на макете Angular $httpBackend, и неясно, как заставить его общаться с реальным сервером.

2Это было ближе всего, но, поскольку у меня есть существующий API, с помощью которого мне нужно пройти аутентификацию, я не мог использовать 'localStrategy' паспорта, и мне казалось безумием писать OAUTH сервис... который только я собирался использовать.


comment
Разве $cookie не решает вашу проблему каждый раз вызывать сервер? И, возможно, вам не нужно обновлять страницу, если вы используете какой-либо сервис для управления своими переменными в области видимости. Извините, если я неправильно понял ваш вопрос...   -  person Deividi Cavarzan    schedule 20.08.2013
comment
Я не использую $cookie или $cookieStore. Файл cookie (созданный сервером) возвращается и сохраняется в браузере, и когда я делаю другие вызовы REST, требующие аутентификации, я устанавливаю withCredentials:true в своих вызовах. Это отлично работает, поддерживает состояние входа в систему и защищает/скрывает маршруты до тех пор, пока этот пользователь не войдет в систему, с которой у меня возникли проблемы.   -  person couzzi    schedule 20.08.2013
comment
Вы говорите, что у вас нет контроля над вашим auth API? Делаете ли вы еще один вызов другой службе REST для аутентификации после того, как пользователь попадает на ваш экспресс-маршрут?   -  person BoxerBucks    schedule 20.08.2013
comment
Что вы имеете в виду под контролем? Это не CORS, если вы это имеете в виду. Когда пользователь переходит к /login, <form>, который он заполняет, использует ng-sumbit="login()". Консоль показывает, что POST прошел успешно, но никакие данные не заполняются до обновления, и когда я возвращаюсь к /login, я не хочу, чтобы они видели форму, я хочу, чтобы они были перенаправлены на /profile.   -  person couzzi    schedule 20.08.2013
comment
Что ты в итоге сделал? У меня точно такой же вопрос, как и у вас.   -  person Derek Perkins    schedule 23.01.2014
comment
@DerekPerkins - в итоге я обработал аутентификацию на уровне NodeJS. AngularJS берет на себя все остальные вызовы RESTful. Хотя изначально я хотел, чтобы AngularJS поглотил все это, на самом деле я очень доволен своим текущим стеком: Node+Express+Jade+Angular. Прошло 5 месяцев (с момента написания этого вопроса), и у меня не было проблем.   -  person couzzi    schedule 23.01.2014
comment
@couzzi Спасибо за ответ. Итак, вы предлагаете им посетить страницу, обслуживаемую узлом, предоставить им файл cookie, а затем использовать этот файл cookie для аутентификации каждого запроса REST?   -  person Derek Perkins    schedule 24.01.2014
comment
Точно. На самом деле я использую Restangular, который является сервисом AngularJS для обработки ресурсов отдыха Rest API, и это потрясающе. Вы можете глобально настроить Restangular для установки withCrednetials, а также для перехвата ответов об ошибках. (например, отправить пользователя обратно на страницу входа, если GET/POST/и т. д. возвращает 401)   -  person couzzi    schedule 24.01.2014
comment
Взгляните на этот jonsamwell.com/url-route-authorization- и-безопасность-в-ангуляре   -  person Jon    schedule 01.08.2014
comment
@JonSamwell - это фантастика, и ответ в этой ветке ждал почти год. Пожалуйста, укажите это как ответ и предоставьте краткий обзор деталей/кода, и я отмечу его как принятый. Большое спасибо.   -  person couzzi    schedule 01.08.2014
comment
@couzzi - Смотрите мой ответ - я очень рад, что это помогло   -  person Jon    schedule 02.08.2014


Ответы (4)


Это взято из моего сообщения в блоге об авторизации URL-маршрута и безопасности элементов здесь, но я кратко изложу основные моменты :-)

Безопасность во внешнем веб-приложении — это всего лишь начальная мера, чтобы остановить Joe Public, однако любой пользователь, обладающий некоторыми знаниями в Интернете, может обойти ее, поэтому вы всегда должны иметь защиту на стороне сервера.

Основная проблема, связанная с безопасностью в angular, - это безопасность маршрута, к счастью, при определении маршрута в angular вы создаете объект, объект, который может иметь другие свойства. Краеугольным камнем моего подхода является добавление объекта безопасности к этому объекту маршрута, который в основном определяет роли, в которых пользователь должен быть, чтобы иметь доступ к определенному маршруту.

 // 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()', поскольку он заменяет текущий маршрут (тот, на который у них нет прав на просмотр) маршрутом, на который мы их перенаправляем. Это остановит любой возврат к неавторизованному маршруту.

Теперь мы можем перехватывать маршруты и делать много интересных вещей, включая перенаправление после входа в систему, если пользователь приземлился на маршруте, для которого ему нужно было войти в систему.

Вторая часть решения — возможность скрывать/показывать элемент пользовательского интерфейса пользователю в зависимости от его прав. Это достигается с помощью простой директивы.

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>

Прочтите мой полный пост в блоге, чтобы получить гораздо более подробный обзор этого подхода.

person Jon    schedule 02.08.2014

Я не использовал $resource, потому что я просто вручную создаю вызовы службы для своего приложения. Тем не менее, я обрабатывал вход в систему, имея службу, которая зависит от всех других служб, которые получают какие-то данные инициализации. При успешном входе в систему запускается инициализация всех служб.

В рамках моего контроллера я просматриваю loginServiceInformation и соответствующим образом заполняю некоторые свойства модели (чтобы вызвать соответствующий ng-show/hide). Что касается маршрутизации, я использую встроенную маршрутизацию Angular, и у меня просто есть ng-hide на основе логического значения loggedIn, показанного здесь, он показывает текст для запроса входа в систему или же div с атрибутом ng-view (поэтому, если вы не вошли в систему сразу после входа в систему вы находитесь на правильной странице, в настоящее время я загружаю данные для всех представлений, но я считаю, что при необходимости это может быть более избирательным)

//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>

У меня есть контроллер входа в систему, определенный с помощью контроллера ng выше в DOM, чтобы я мог изменить область тела моей страницы на основе переменной loggedIn.

Примечание. Я еще не реализовал здесь проверку формы. Также, по общему признанию, все еще довольно свежий для Angular, поэтому приветствуются любые указатели на вещи в этом посте. Хотя это не отвечает на вопрос напрямую, поскольку это не реализация на основе RESTful, я считаю, что то же самое можно адаптировать к $resources, поскольку оно построено поверх вызовов $http.

person shaunhusain    schedule 20.08.2013
comment
Это выглядит действительно многообещающе. Я попробую утром. Спасибо! Однако один вопрос — можете ли вы уточнить этот момент?: У меня есть контроллер входа в систему, определенный с помощью контроллера ng выше в DOM, чтобы я мог изменять область тела моей страницы на основе переменной loggedIn.< /я> - person couzzi; 20.08.2013
comment
Да, я на самом деле закончил тем, что отредактировал и вставил эту часть в нижней части сообщения, базовый HTML — это та часть, о которой я говорил, где в теге body у меня есть NavigationCtrl, который обрабатывает навигацию и является моим контроллером верхнего уровня. Затем вы можете увидеть, что LoginCtrl определен в моем внешнем контейнере, который является div, который оборачивает все остальное. Таким образом, я мог бы использовать его переменные области видимости в любом из дочерних элементов DOM (в данном случае практически где угодно). - person shaunhusain; 20.08.2013

Я написал модуль AngularJS для UserApp, который делает почти все, что вы просите. Вы можете:

  1. Измените модуль и прикрепите функции к вашему собственному API или
  2. Используйте модуль вместе с API управления пользователями, UserApp.

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

Он поддерживает защищенные/общедоступные маршруты, перенаправление при входе/выходе из системы, пульсацию для проверки состояния, сохраняет токен сеанса в файле cookie, событиях и т. д.

Если вы хотите попробовать 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), чтобы проверить, токен действителен или нет.

Если вам нужна помощь, просто дайте мне знать :)

person Timothy E. Johansson    schedule 17.12.2013
comment
Это платное решение, нет? - person couzzi; 13.01.2014
comment
Я бы посоветовал, если вы собираетесь передать аутентификацию пользователя на аутсорсинг, вы должны надеяться, что это платное решение (с юридическими привязками, которые это подразумевает). Другое дело, хотите ли вы доверить столь важный компонент вашего приложения третьей стороне... - person TK-421; 30.09.2014
comment
UserApp теперь мертв - person oleksii; 22.03.2018

Я создал репозиторий 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.js. Служба $http используется для связи с сервером для процедур аутентификации. Также содержит функции по авторизации, то есть разрешено ли пользователю выполнять определенное действие.

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) Сеанс: одноэлементный элемент для хранения пользовательских данных. Реализация здесь зависит от вас.

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) Родительский контроллер: рассматривайте это как «основную» функцию вашего приложения, все контроллеры наследуются от этого контроллера, и это основа аутентификации этого приложения.

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

(5) Контроль доступа: чтобы запретить доступ к определенным маршрутам, необходимо выполнить 2 шага:

a) Добавьте данные о ролях, которым разрешен доступ к каждому маршруту, в службу $stateProvider маршрутизатора пользовательского интерфейса, как показано ниже (то же самое может работать для ngRoute).

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

б) В $rootScope.$on('$stateChangeStart') добавьте функцию для предотвращения изменения состояния, если пользователь не авторизован.

$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) Перехватчик аутентификации: это реализовано, но его нельзя проверить в рамках этого кода. После каждого запроса $http этот перехватчик проверяет код состояния, если возвращается один из приведенных ниже, то он передает событие, чтобы заставить пользователя снова войти в систему.

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. Ошибки с автозаполнением данных формы, описанной в 1-й статье, можно легко избежать, добавив директиву, включенную в directives.js.

P.S.2 Этот код может быть легко изменен пользователем, чтобы разрешить отображение различных маршрутов или отображать контент, который не предназначен для отображения. Логика ДОЛЖНА быть реализована на стороне сервера, это просто способ правильно показать вещи в вашем ng-приложении.

person Alex Arvanitidis    schedule 05.10.2014