Аутентификация и авторизация являются важными компонентами практически для каждого серьезного приложения. Одностраничные приложения (SPA) не являются исключением. Приложение может не предоставлять все свои данные и функциональные возможности любому пользователю. Пользователям может потребоваться пройти аутентификацию, чтобы увидеть определенные части приложения или выполнить определенные действия с приложением. Чтобы идентифицировать пользователя в приложении, нам нужно войти в систему.
Существует различие в способах управления пользователями, реализованных в традиционных серверных приложениях и одностраничных приложениях. Единственный способ, которым SPA может взаимодействовать со своими серверными компонентами, — это AJAX. Это верно даже для входа и выхода.
Сервер, ответственный за идентификацию пользователя, должен предоставить конечную точку аутентификации. SPA отправит учетные данные, введенные пользователем, в эту конечную точку для проверки. В типичной системе аутентификации на основе токенов служба может ответить токеном доступа или объектом, содержащим имя и роль вошедшего в систему пользователя после проверки учетных данных. Клиент должен использовать этот токен доступа во всех защищенных запросах API, направленных на сервер.
Так как токен доступа будет использоваться несколько раз, лучше хранить его на стороне клиента. В Angular мы можем хранить значение в службе или значение, поскольку они являются одноэлементными объектами на стороне клиента. Но если пользователь обновит страницу, значение в службе или значение будет потеряно. В таком случае лучше хранить токен, используя один из механизмов сохранения, предлагаемых браузером; предпочтительно sessionStorage , так как он очищается после закрытия браузера.
Реализация Логин
Давайте посмотрим на код сейчас. Предположим, что у нас реализована вся логика на стороне сервера, и служба предоставляет конечную точку REST в /api/login
для проверки учетных данных и возврата маркера доступа. Давайте напишем простой сервис, который выполняет действие входа в систему, нажимая на конечную точку аутентификации. Мы добавим больше функциональности к этому сервису позже:
app.factory("authenticationSvc", function($http, $q, $window) { var userInfo; function login(userName, password) { var deferred = $q.defer(); $http.post("/api/login", { userName: userName, password: password }).then(function(result) { userInfo = { accessToken: result.data.access_token, userName: result.data.userName }; $window.sessionStorage["userInfo"] = JSON.stringify(userInfo); deferred.resolve(userInfo); }, function(error) { deferred.reject(error); }); return deferred.promise; } return { login: login }; });
В реальном коде вы можете захотеть перефакторизовать оператор, сохраняющий данные для sessionStorage, в отдельный сервис, так как этот сервис получает несколько обязанностей, если мы делаем это. Я оставляю его в том же сервисе, чтобы сделать демонстрацию простой. Эта услуга может использоваться контроллером, который обрабатывает функциональность входа в систему для приложения.
Обеспечение маршрутов
У нас может быть набор защищенных маршрутов в приложении. Если пользователь не вошел в систему и пытается войти в один из этих маршрутов, он должен быть направлен на страницу входа. Это может быть достигнуто с помощью блока resolve
в параметрах маршрутизации. Следующий фрагмент дает представление о реализации:
$routeProvider.when("/", { templateUrl: "templates/home.html", controller: "HomeController", resolve: { auth: ["$q", "authenticationSvc", function($q, authenticationSvc) { var userInfo = authenticationSvc.getUserInfo(); if (userInfo) { return $q.when(userInfo); } else { return $q.reject({ authenticated: false }); } }] } });
Блок resolve
может содержать несколько блоков операторов, которые должны возвращать объекты обещания по завершении. Просто чтобы уточнить, имя auth
определенное выше, не определяется платформой; Я определил это. Вы можете изменить имя на что угодно в зависимости от варианта использования.
Там может быть несколько причин, чтобы пройти или отклонить маршрут. В зависимости от сценария вы можете передать объект при разрешении / отклонении обещания. Мы еще не реализовали метод getLoggedInUser()
в сервисе. Это простой метод, который возвращает объект loggedInUser
из сервиса.
app.factory("authenticationSvc", function() { var userInfo; function getUserInfo() { return userInfo; } });
Объекты, отправленные с помощью обещания в приведенном выше фрагменте, передаются через $rootScope
. Если маршрут разрешен, событие $routeChangeSuccess
транслируется. Однако, если маршрут потерпел неудачу, событие $routeChangeError
транслируется. Мы можем прослушать событие $routeChangeError
и перенаправить пользователя на страницу входа. Поскольку событие находится на уровне $rootScope
, лучше прикрепить обработчики к событию в блоке выполнения.
app.run(["$rootScope", "$location", function($rootScope, $location) { $rootScope.$on("$routeChangeSuccess", function(userInfo) { console.log(userInfo); }); $rootScope.$on("$routeChangeError", function(event, current, previous, eventObj) { if (eventObj.authenticated === false) { $location.path("/login"); } }); }]);
Обновление страницы
Когда пользователь нажимает кнопку обновления на странице, сервис теряет свое состояние. Мы должны получить данные из хранилища сеансов браузера и присвоить их переменной loggedInUser
. Поскольку фабрика вызывается только один раз, мы можем установить эту переменную в функции инициализации, как показано ниже.
function init() { if ($window.sessionStorage["userInfo"]) { userInfo = JSON.parse($window.sessionStorage["userInfo"]); } } init();
Выйти
Когда пользователь выходит из приложения, должен быть вызван соответствующий API с токеном доступа, включенным в заголовки запроса. Как только пользователь вышел из системы, мы должны также очистить данные в sessionStorage. В следующем примере содержится функция выхода из системы, которую необходимо добавить в службу аутентификации.
function logout() { var deferred = $q.defer(); $http({ method: "POST", url: logoutUrl, headers: { "access_token": userInfo.accessToken } }).then(function(result) { $window.sessionStorage["userInfo"] = null; userInfo = null; deferred.resolve(result); }, function(error) { deferred.reject(error); }); return deferred.promise; }
Вывод
Подход для реализации аутентификации в одностраничных приложениях сильно отличается от традиционных веб-приложений. Поскольку большая часть работы выполняется на стороне клиента, состояние пользователя также должно храниться где-то на клиенте. Важно помнить, что состояние должно поддерживаться и проверяться также на сервере, поскольку хакер может потенциально украсть данные, хранящиеся в клиентской системе.
Исходный код из этой статьи доступен для скачивания на GitHub .