Разрешите запрос $http перед запуском приложения и переключением на маршрут или состояние

Я написал приложение, в котором мне нужно получить информацию о текущем вошедшем в систему пользователе, когда приложение запускается, прежде чем будет обработана маршрутизация. Я использую ui-router для поддержки нескольких/вложенных представлений и предоставления более функциональной маршрутизации с отслеживанием состояния.

Когда пользователь входит в систему, он может сохранить файл cookie, представляющий его токен авторизации. Я включаю этот токен в вызов службы для получения информации о пользователе, в том числе о том, к каким группам он принадлежит. Полученное удостоверение затем задается в службе, где его можно получить и использовать в остальной части приложения. Что еще более важно, маршрутизатор будет использовать этот идентификатор, чтобы убедиться, что они вошли в систему и принадлежат к соответствующей группе, прежде чем перевести их в запрошенное состояние.

У меня есть примерно такой код:

app
    .config(['$stateProvider', function($stateProvider) {

        // two states; one is the protected main content, the other is the sign-in screen

        $stateProvider
            .state('main', {
                url: '/',
                data: {
                    roles: ['Customer', 'Staff', 'Admin']
                },
                views: {} // omitted
            })
            .state('account.signin', {
                url: '/signin',
                views: {} // omitted
            });
    }])
    .run(['$rootScope', '$state', '$http', 'authority', 'principal', function($rootScope, $state, $http, authority, principal) {

        $rootScope.$on('$stateChangeStart', function (event, toState) { // listen for when trying to transition states...
            var isAuthenticated = principal.isAuthenticated(); // check if the user is logged in

            if (!toState.data.roles || toState.data.roles.length == 0) return; // short circuit if the state has no role restrictions

            if (!principal.isInAnyRole(toState.data.roles)) { // checks to see what roles the principal is a member of
                event.preventDefault(); // role check failed, so...
                if (isAuthenticated) $state.go('account.accessdenied'); // tell them they are accessing restricted feature
                else $state.go('account.signin'); // or they simply aren't logged in yet
            }
        });

        $http.get('/svc/account/identity') // now, looks up the current principal
            .success(function(data) {
                authority.authorize(data); // and then stores the principal in the service (which can be injected by requiring "principal" dependency, seen above)
            }); // this does its job, but I need it to finish before responding to any routes/states
    }]);

Все работает, как и ожидалось, если я вхожу в систему, перемещаюсь, выхожу из системы и т. д. Проблема заключается в том, что если я обновляю URL-адрес или перехожу по URL-адресу, когда я вошел в систему, меня отправляют на экран входа, потому что вызов службы идентификации не был завершено до изменения состояния. Однако после завершения этого вызова я мог бы продолжать работать, как и ожидалось, если есть ссылка или что-то еще, например, на состояние main, так что я почти у цели.

Я знаю, что вы можете заставить состояния ожидать разрешения параметров перед переходом, но я не знаю, как это сделать.


person moribvndvs    schedule 01.11.2013    source источник


Ответы (3)


Хорошо, после того, как я долго дергал за волосы, вот что я понял.

  1. Как и следовало ожидать, resolve — это подходящее место для инициирования любых асинхронных вызовов и обеспечения их завершения до перехода в состояние.
  2. Вы захотите создать абстрактное родительское состояние для всех состояний, которое гарантирует, что ваше resolve имеет место, таким образом, если кто-то обновит браузер, ваше асинхронное разрешение все еще будет работать, и ваша аутентификация будет работать правильно. Вы можете использовать свойство parent для state, чтобы создать другое состояние, которое в противном случае не было бы унаследовано путем именования/точечной нотации. Это помогает предотвратить неуправляемость имен ваших состояний.
  3. Хотя вы можете внедрить любые службы, которые вам нужны, в свой resolve, вы не можете получить доступ к toState или toStateParams состояния, в которое он пытается перейти. Однако событие $stateChangeStart произойдет до того, как ваше resolve будет разрешено. Итак, вы можете скопировать toState и toStateParams из аргументов события в $rootScope и внедрить $rootScope в свою функцию resolve. Теперь вы можете получить доступ к состоянию и параметрам, к которым он пытается перейти.
  4. После того, как вы разрешили свои ресурсы, вы можете использовать промисы для проверки авторизации, и если это не удается, используйте $state.go(), чтобы отправить их на страницу входа, или сделайте все, что вам нужно. Тут, конечно, есть оговорка.
  5. Как только resolve будет выполнено в родительском состоянии, ui-router больше не будет разрешать его. Это означает, что ваша проверка безопасности не произойдет! Арх! Решение этой проблемы состоит в том, чтобы иметь двухэтапную проверку. Однажды в resolve, как мы уже говорили. Второй раз в событии $stateChangeStart. Ключевым моментом здесь является проверка и проверка разрешения ресурсов. Если да, выполните ту же проверку безопасности, что и в resolve, но в событии. если ресурс(ы) не разрешен, то проверка в resolve заберет его. Чтобы осуществить это, вам нужно управлять своими ресурсами в службе, чтобы вы могли соответствующим образом управлять состоянием.

Некоторые другие разное. Примечания:

  • Не утруждайте себя попытками впихнуть всю логику авторизации в $stateChangeStart. Хотя вы можете предотвратить событие и выполнить асинхронное разрешение (которое эффективно останавливает изменение до тех пор, пока вы не будете готовы), а затем попытаться возобновить изменение состояния в обработчике успеха промиса, есть некоторые проблемы, препятствующие правильной работе.
  • Вы не можете изменять состояния в методе onEnter текущего состояния.

Этот планк является рабочим примером.

person moribvndvs    schedule 22.01.2014
comment
Не могли бы вы рассказать о своем первом разном. примечание, начинающееся с Не утруждайте себя попытками втиснуть.... В частности, вокруг ... есть некоторые проблемы, препятствующие правильной работе. Какие проблемы вы имеете в виду? - person Ruslans Uralovs; 06.11.2014
comment
@RuslansUralovs Я подробно рассказал о своем открытии, отвечая на другой вопрос здесь. Это должно быть более подробное объяснение того, что делать, включая проблему, о которой вы говорите. - person moribvndvs; 06.11.2014
comment
You can't change states in the current state's onEnter method. Почему? - person Hamlet Hakobyan; 06.04.2016

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

Мы устранили это разделение, поместив вызов $http.get в службу, которая кэширует ответ и немедленно вызывает обратный вызов успеха, если кэш уже заполнен. Например.

app.service('authorizationService', ['authority', function (authority) {
    var requestData = undefined;

    return {
        get: function (successCallback) {
            if (typeof requestData !== 'undefined') {
                successCallback(requestData);
            }
            else {
                $http.get('/svc/account/identity').success(function (data) {
                    requestData = data;
                    successCallback(data);
                });
            }
        }
    };
}]);

Это действует как защита вокруг успешного запроса. Затем вы можете безопасно вызвать authorizationService.get() в обработчике $stateChangeStart.

Этот подход уязвим для состояния гонки, если запрос уже выполняется, когда вызывается authorizationService.get(). Чтобы предотвратить это, можно было бы ввести некоторую бухгалтерию XHR.

В качестве альтернативы вы можете опубликовать пользовательское событие после завершения HTTP-запроса и зарегистрировать подписчика для этого события в обработчике $stateChangeStart. Однако позже вам нужно будет отменить регистрацию этого обработчика, возможно, в обработчике $stateChangeEnd.

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

Кроме того, есть интересное обсуждение аутентификации с помощью ui-router в Как фильтровать маршруты? в группе Google AngularJS.

person afternoon    schedule 17.12.2013
comment
Да, я думаю, что resolve в каждом штате, который использует $q из моей службы аутентификации, звучит наиболее многообещающе. Я попробую. - person moribvndvs; 19.12.2013

Я думаю, что более чистый способ справиться с этой ситуацией - это t$urlRouterProvider. deferIntercept() вместе с $urlRouter.listen(), чтобы остановить uiRouter, пока некоторые данные не будут получены с сервера.

См. этот ответ и docs для получения дополнительной информации.

person Alireza Mirian    schedule 30.09.2015