AngularJS + UI Router: проверка авторизации и прав доступа

  • Tutorial
Если ваше приложение предполагает авторизацию пользователей и/или проверку прав доступа, то вам придется либо изобретать велосипед, либо гуглить в поисках подходящего решения. В принципе, я тоже это делал. В итоге я принял приемлемым для себя описанный ниже вариант.

Предпосылки


Информацию об авторизованном пользователе я решил хранить в sessionStorage, копируя её при запуске приложения в $rootScope. Также по рекомендации авторов UI Router я храню в $rootScope значения объекты $state и $stateParam, для удобного доступа. Информацию же о доступе к тому или иному состоянию можно передавать через блок data при описании самого состояния. Поскольку в моем приложении везде закрыт доступ, я решил идти от обратного и добавлять значение noLogin = true для состояний, которые не требуют авторизации, например страницы ввода логина, восстановления пароля или регистрации.

angular.module('myApp.auth', [
    'ui.router'
  ])
  .config(['$stateProvider', '$urlRouterProvider',
    function ($stateProvider, $urlRouterProvider) {

      $stateProvider
        .state('auth', {
          url: '/auth',
          abstract: true,
          template: '<ui-view>'
        })

      .state('auth.login', {
        url: '/login',
        templateUrl: 'src/auth/partials/login.html', 
        data: {
          'noLogin': true
        }
      });
  ]);


Создание сервиса для пре-роутинга


Проверять авторизацию и права доступа нужно в самом начале, до работы роутера, перед тем, как он отправит посетителя на запрошенное состояние (предполагается, что вы знаете, что UI Router управляет не положениями, а состояниями. Больше читайте в официальной документации). Хороший способ это сделать — повесить слушателя на событие $stateChangeStart в методе run() вашего главного модуля. Чтобы не захламлять функционалом тело метода, который может быть объемным и сложным, я вынес его в отдельный сервис, в методе run() я просто вызываю метод сервиса. Думаю, дальше объяснения не понадобятся.

angular.module('myApp.auth')
  .service('SessionService', [
    '$injector',
    function($injector) {
      "use strict";

      this.checkAccess = function(event, toState, toParams, fromState, fromParams) {
        var $scope = $injector.get('$rootScope'),
            $sessionStorage = $injector.get('$sessionStorage');

        if (toState.data !== undefined) {
          if (toState.data.noLogin !== undefined && toState.data.noLogin) {
            // если нужно, выполняйте здесь какие-то действия 
            // перед входом без авторизации
          }
        } else {
          // вход с авторизацией
          if ($sessionStorage.user) {
            $scope.$root.user = $sessionStorage.user;
          } else {
            // если пользователь не авторизован - отправляем на страницу авторизации
            event.preventDefault();
            $scope.$state.go('auth.login');
          }
        }
      };
    }
  ]);


Собираем все вместе


Ну и остается последний штрих, чтобы все это заработало — повесить слушателя на событие сервиса $state.

angular.module('myApp', [
  'myApp.auth',
  'ui.router',
  'ngStorage'
])

.run([
  '$rootScope', '$state', '$stateParams', 'SessionService',
  function ($rootScope, $state, $stateParams, SessionService) {

    $rootScope.$state = $state;
    $rootScope.$stateParams = $stateParams;

    $rootScope.user = null;

    // Здесь мы будем проверять авторизацию
    $rootScope.$on('$stateChangeStart',
      function (event, toState, toParams, fromState, fromParams) {
        SessionService.checkAccess(event, toState, toParams, fromState, fromParams);
      }
    );
  }
])


Заключение


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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Что вы используете для управления маршрутами в Angular JS?

Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 23

    +1
    Очень просто.
    Было бы здорово добавить возможность продолжить переход после логина по тому пути, куда собирался идти.
    Было бы здорово читать пользователя перед проверкой. Может человек уже вошел в систему, и просто обновил страницу.
    Новичкам пригодится, но в интернете Ваш подход уже давно описан и очень много раз.
      0
      Все уже где-то описано, это факт. Используя приведенный пример как скелет каждый может написать что угодно, включая перечисленное вами.
      +1
      .
        +1
        Если использовать галочку «запомнить меня» в форме авторизации и требуется сверить с сервером актуальность сохраненного токена, то лучше использовать resolve. Такой подход описан на stackoverflow.com. Обратите внимание, что в методах resolve без promise нельзя сделать $state.go.
          +3
          Вы описали банальные вещи, как известно дьявол кроется в деталях.
          Например, вы не описали, как проверять авторизованность пользователя в том смысле, что при первом заходе надо сделать блокирующий рендеринг страницы запрос на бэкенд для проверки авторизации.
          Также, не описан к примеру механизм разделения по правам, когда пользователи могут иметь или не иметь доступ к целым страницам.
          Не описали установку перехватчика ответов бэкенда для отслеживания потери сессии.
          Не описали, как после логина повторно разрешить зависимости, прописанные в resolve корневого state.
          И много чего еще.
            +2
            Еще не описали решения проблемы запоминания паролей в некоторых браузерах, например Chrome
            0
            Я попытался использовать UI Router, но видимо не до конца понял их глубокую идею и остановился вот на этом решении:
            angular-route-segment.com/

            В итоге задача решилась гораздо быстрее
              0
                +1
                И видео к коду www.youtube.com/watch?v=3H_YqMITSi0
                  0
                  Плохой пример, обоснование:
                  Юзер авторизировался на сайте, находится на странице с таблицей, его сессия протухает, он делает асинхронную загрузку данных (например, нажимает на кнопку «Обновить данные в таблице»), т.е. он не меняет текущий роут и событие $stateChangeStart не вызывается. В итоге запрос возвращает «401 Unauthorized», таблица ломается/не обновляется/всё ломается.

                  Я делаю это через Interceptors (https://docs.angularjs.org/api/ng/service/$http), немного моего кода, вытащенного из проекта, он в свою очередь вытащен из какого-то мана:

                  angular.module(...).config(function ($locationProvider) {
                      $httpProvider.interceptors.push('authInterceptor');
                    }).factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
                      return {
                        // Add authorization token to headers
                        request: function (config) {
                          config.headers = config.headers || {};
                          if ($cookieStore.get('token')) {
                            config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
                          }
                          return config;
                        },
                  
                        // Intercept 401s and redirect you to login
                        responseError: function(response) {
                          if(response.status === 401) {
                            $location.path('/login');
                            // remove any stale tokens
                            $cookieStore.remove('token');
                            return $q.reject(response);
                          }
                          else {
                            return $q.reject(response);
                          }
                        }
                      };
                    })
                  
                    0
                    Ваш пример и мой выполняют разные задачи Я тоже использую интерцепторы к слову, хорошая техника.
                    0
                    Никто не знает случайно, как пробросить resolve на все state?
                      0
                      В корневой прописать
                      0
                      if (toState.data.noLogin !== undefined && toState.data.noLogin)
                      


                      Можно пример когда noLogin==true и в то же время undefined?
                        0
                        noLogin может быть не определен вообще.
                          +1
                          Правильнее все-таки как-то так:

                          if(toState.data && toState.data.noLogin === true)
                          


                          Т.к. data потенциально вообще может не быть.
                            0
                            И что что не определен? Вернет false.
                            Короче левую часть выражения можно опустить.
                          0
                            0
                            Эксперементирую с ui.router.
                            Какой бы сервис не прописал (в том числе и из этого примера), при вызове внутри $rootScope.$on('$stateChangeStart') получаю TypeError: SessionService.checkAccess is not a function

                            В чем может быть проблема? Покажите, ПОЖАЛУЙСТА, на примере примитивный .service (hello world) и запуск его в $rootScope.$on('$stateChangeStart')
                              0

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

                                0

                                Вот эта строка


                                if ($sessionStorage.user) {

                                по идее асинхронная. Ибо для проверки есть ли аутентификация или нет нужно делать запрос на сервер.

                                  0

                                  Нашел вариант асинхронной и постоянной проверки аутентификации http://erraticdev.blogspot.ru/2015/10/angular-ngroute-routing-authorization.html

                                  0
                                  Спасибо, отличная статья.

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое