首页 文章

angular ui-router登录验证

提问于
浏览
365

我是AngularJS的新手,我对如何在以下场景中使用angular-“ui-router”感到困惑:

我正在构建一个由两部分组成的Web应用程序 . 第一部分是具有登录和注册视图的主页,第二部分是仪表板(成功登录后) .

我为主页部分创建了一个 index.html ,其角度应用程序和 ui-router config用于处理 /login/signup 视图,还有另一个文件 dashboard.html 用于仪表板部分及其app和 ui-router config用于处理许多子视图 .

现在我完成了仪表板部分,不知道如何将这两个部分与不同的角度应用程序结合起来 . 我怎么能告诉家庭应用程序重定向到仪表板应用程序?

10 回答

  • 22

    Use $http Interceptor

    通过使用$ http拦截器,您可以将标头发送到后端,或者反过来以这种方式进行检查 .

    关于$http interceptors的精彩文章

    例:

    $httpProvider.interceptors.push(function ($q) {
            return {
                'response': function (response) {
    
                    // TODO Create check for user authentication. With every request send "headers" or do some other check
                    return response;
                },
                'responseError': function (reject) {
    
                    // Forbidden
                    if(reject.status == 403) {
                        console.log('This page is forbidden.');
                        window.location = '/';
                    // Unauthorized
                    } else if(reject.status == 401) {
                        console.log("You're not authorized to view this page.");
                        window.location = '/';
                    }
    
                    return $q.reject(reject);
                }
            };
        });
    

    把它放在你的.config或.run函数中 .

  • 2

    我有另一个解决方案:当您只有登录时要显示的内容时,该解决方案可以正常工作 . 定义一条规则,用于检查您是否已登录,而不是白名单路径的路径 .

    $urlRouterProvider.rule(function ($injector, $location) {
       var UserService = $injector.get('UserService');
       var path = $location.path(), normalized = path.toLowerCase();
    
       if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
         $location.path('/login/signin');
       }
    });
    

    在我的例子中,我问我是否没有登录,我想要路由的当前路由不是“/ login”的一部分,因为我的白名单路由如下

    /login/signup // registering new user
    /login/signin // login to app
    

    所以我可以即时访问这两条路线,如果您在线,将检查每条其他路线 .

    这是我的登录模块的整个路由文件

    export default (
      $stateProvider,
      $locationProvider,
      $urlRouterProvider
    ) => {
    
      $stateProvider.state('login', {
        parent: 'app',
        url: '/login',
        abstract: true,
        template: '<ui-view></ui-view>'
      })
    
      $stateProvider.state('signin', {
        parent: 'login',
        url: '/signin',
        template: '<login-signin-directive></login-signin-directive>'
      });
    
      $stateProvider.state('lock', {
        parent: 'login',
        url: '/lock',
        template: '<login-lock-directive></login-lock-directive>'
      });
    
      $stateProvider.state('signup', {
        parent: 'login',
        url: '/signup',
        template: '<login-signup-directive></login-signup-directive>'
      });
    
      $urlRouterProvider.rule(function ($injector, $location) {
        var UserService = $injector.get('UserService');
        var path = $location.path();
    
        if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
             $location.path('/login/signin');
        }
      });
    
      $urlRouterProvider.otherwise('/error/not-found');
    }
    

    () => { /* code */ } 是ES6语法,改为使用 function() { /* code */ }

  • 41

    以下是我们如何摆脱无限路由循环并仍然使用 $state.go 而不是 $location.path

    if('401' !== toState.name) {
      if (principal.isIdentityResolved()) authorization.authorize();
    }
    
  • 5

    我正在制作一个更好的演示以及将一些这些服务清理成一个可用的模块,但这就是我想出来的 . 这是一个解决一些警告的复杂过程,所以请坚持下去 . 你需要把它分成几块 .

    Take a look at this plunk .

    首先,您需要一项服务来存储用户的身份 . 我称之为 principal . 可以检查用户是否登录,并且根据请求,它可以解析表示用户身份的基本信息的对象 . 这可以是您需要的任何内容,但必需品将是显示名称,用户名,可能是电子邮件以及用户所属的角色(如果这适用于您的应用) . 校长还有进行角色检查的方法 .

    .factory('principal', ['$q', '$http', '$timeout',
      function($q, $http, $timeout) {
        var _identity = undefined,
          _authenticated = false;
    
        return {
          isIdentityResolved: function() {
            return angular.isDefined(_identity);
          },
          isAuthenticated: function() {
            return _authenticated;
          },
          isInRole: function(role) {
            if (!_authenticated || !_identity.roles) return false;
    
            return _identity.roles.indexOf(role) != -1;
          },
          isInAnyRole: function(roles) {
            if (!_authenticated || !_identity.roles) return false;
    
            for (var i = 0; i < roles.length; i++) {
              if (this.isInRole(roles[i])) return true;
            }
    
            return false;
          },
          authenticate: function(identity) {
            _identity = identity;
            _authenticated = identity != null;
          },
          identity: function(force) {
            var deferred = $q.defer();
    
            if (force === true) _identity = undefined;
    
            // check and see if we have retrieved the 
            // identity data from the server. if we have, 
            // reuse it by immediately resolving
            if (angular.isDefined(_identity)) {
              deferred.resolve(_identity);
    
              return deferred.promise;
            }
    
            // otherwise, retrieve the identity data from the
            // server, update the identity object, and then 
            // resolve.
            //           $http.get('/svc/account/identity', 
            //                     { ignoreErrors: true })
            //                .success(function(data) {
            //                    _identity = data;
            //                    _authenticated = true;
            //                    deferred.resolve(_identity);
            //                })
            //                .error(function () {
            //                    _identity = null;
            //                    _authenticated = false;
            //                    deferred.resolve(_identity);
            //                });
    
            // for the sake of the demo, fake the lookup
            // by using a timeout to create a valid
            // fake identity. in reality,  you'll want 
            // something more like the $http request
            // commented out above. in this example, we fake 
            // looking up to find the user is
            // not logged in
            var self = this;
            $timeout(function() {
              self.authenticate(null);
              deferred.resolve(_identity);
            }, 1000);
    
            return deferred.promise;
          }
        };
      }
    ])
    

    其次,您需要一项服务来检查用户想要访问的状态,确保他们已登录(如果需要;不需要登录,密码重置等),然后进行角色检查(如果您的应用程序)需要这个) . 如果未经过身份验证,请将其发送到登录页面 . 如果对它们进行了身份验证,但未通过角色检查,则将其发送到拒绝访问的页面 . 我叫这个服务 authorization .

    .factory('authorization', ['$rootScope', '$state', 'principal',
      function($rootScope, $state, principal) {
        return {
          authorize: function() {
            return principal.identity()
              .then(function() {
                var isAuthenticated = principal.isAuthenticated();
    
                if ($rootScope.toState.data.roles
                    && $rootScope.toState
                                 .data.roles.length > 0 
                    && !principal.isInAnyRole(
                       $rootScope.toState.data.roles))
                {
                  if (isAuthenticated) {
                      // user is signed in but not
                      // authorized for desired state
                      $state.go('accessdenied');
                  } else {
                    // user is not authenticated. Stow
                    // the state they wanted before you
                    // send them to the sign-in state, so
                    // you can return them when you're done
                    $rootScope.returnToState
                        = $rootScope.toState;
                    $rootScope.returnToStateParams
                        = $rootScope.toStateParams;
    
                    // now, send them to the signin state
                    // so they can log in
                    $state.go('signin');
                  }
                }
              });
          }
        };
      }
    ])
    

    现在你需要做的就是收听 ui-router$stateChangeStart . 这使您有机会检查当前状态,他们想要进入的状态,以及插入授权检查 . 如果失败,您可以取消路由转换,或更改为其他路由 .

    .run(['$rootScope', '$state', '$stateParams', 
          'authorization', 'principal',
        function($rootScope, $state, $stateParams, 
                 authorization, principal)
    {
          $rootScope.$on('$stateChangeStart', 
              function(event, toState, toStateParams)
          {
            // track the state the user wants to go to; 
            // authorization service needs this
            $rootScope.toState = toState;
            $rootScope.toStateParams = toStateParams;
            // if the principal is resolved, do an 
            // authorization check immediately. otherwise,
            // it'll be done when the state it resolved.
            if (principal.isIdentityResolved()) 
                authorization.authorize();
          });
        }
      ]);
    

    关于跟踪已经过身份验证的用户's identity is looking it up if you' ve的棘手部分(例如,您在上一个会话之后访问该页面,并在cookie中保存了一个身份验证令牌,或者您可能已经刷新了一个页面,或者从链接中删除了一个URL) ) . 由于 ui-router 的工作方式,您需要在身份验证之前执行一次身份解析 . 您可以使用状态配置中的 resolve 选项执行此操作 . 我有一个父状态用于所有状态继承的站点,这会强制在其他任何事情发生之前解析主体 .

    $stateProvider.state('site', {
      'abstract': true,
      resolve: {
        authorize: ['authorization',
          function(authorization) {
            return authorization.authorize();
          }
        ]
      },
      template: '<div ui-view />'
    })
    

    这里还有另一个问题... resolve 只被调用一次 . 一旦您的身份查找承诺完成,它就不会再次运行resolve delegate . 因此,我们必须在两个地方进行身份验证:一次是根据您的身份承诺解决 resolve ,这涵盖您的应用首次加载,一次在 $stateChangeStart ,如果分辨率已经完成,这涵盖了您在任何时候导航状态 .

    好的,到目前为止我们做了什么?

    • 如果用户已登录,我们会检查应用加载的时间 .

    • 我们跟踪有关登录用户的信息 .

    • 我们将它们重定向到需要用户登录的状态的登录状态 .

    • 如果他们没有访问权限,我们会将其重定向到访问被拒绝状态 .

    • 我们有一种机制可以将用户重定向到他们请求的原始状态,如果我们需要他们登录的话 .

    • 我们可以签署用户(需要与管理您的授权凭证的任何客户端或服务器代码一起连接) .

    • 每次重新加载浏览器或放入链接时,我们都不需要将用户发回登录页面 .

    我们从哪里开始?好吧,您可以将州组织到需要登录的区域 . 您可以通过将 dataroles 添加到这些状态(或者如果您要使用继承,则为父级)来要求经过身份验证/授权的用户 . 在这里,我们将资源限制为管理员:

    .state('restricted', {
        parent: 'site',
        url: '/restricted',
        data: {
          roles: ['Admin']
        },
        views: {
          'content@': {
            templateUrl: 'restricted.html'
          }
        }
      })
    

    现在,您可以控制用户可以访问路由的状态 . 还有其他问题吗?也许只根据他们是否登录而改变视图的一部分?没问题 . 使用 principal.isAuthenticated() 甚至 principal.isInRole() 可以通过多种方式有条件地显示模板或元素 .

    首先,将 principal 注入控制器或其他任何东西,并将其粘贴到示波器上,以便您可以在视图中轻松使用它:

    .scope('HomeCtrl', ['$scope', 'principal', 
        function($scope, principal)
    {
      $scope.principal = principal;
    });
    

    显示或隐藏元素:

    <div ng-show="principal.isAuthenticated()">
       I'm logged in
    </div>
    <div ng-hide="principal.isAuthenticated()">
      I'm not logged in
    </div>
    

    等等,等等 . 无论如何,在您的示例应用程序中,您将拥有一个允许未经身份验证的用户访问的主页状态 . 他们可以链接到登录或注册状态,或者将这些表单内置到该页面中 . 无论什么适合你 .

    仪表板页面都可以从需要用户登录的状态继承,例如,成为 User 角色成员 . 我们讨论过的所有授权资料都会从那里流出来 .

  • 21

    我创建了这个模块来帮助制作这个过程

    你可以这样做:

    $routeProvider
      .state('secret',
        {
          ...
          permissions: {
            only: ['admin', 'god']
          }
        });
    

    或者也

    $routeProvider
      .state('userpanel',
        {
          ...
          permissions: {
            except: ['not-logged-in']
          }
        });
    

    这是全新的但值得一试!

    https://github.com/Narzerus/angular-permission

  • 2

    在我看来,到目前为止发布的解决方案是不必要的复杂 . 有一种更简单的方法 . documentation of ui-router说听 $locationChangeSuccess 并使用 $urlRouter.sync() 检查状态转换,暂停或恢复它 . 但即使这样实际上也行不通 .

    但是,这里有两个简单的替代方案 . 选一个:

    解决方案1:监听$ locationChangeSuccess

    你可以听 $locationChangeSuccess ,你可以执行一些逻辑,甚至是那里的异步逻辑 . 基于该逻辑,您可以让函数返回undefined,这将导致状态转换正常继续,或者如果用户需要进行身份验证,您可以执行 $state.go('logInPage') . 这是一个例子:

    angular.module('App', ['ui.router'])
    
    // In the run phase of your Angular application  
    .run(function($rootScope, user, $state) {
    
      // Listen to '$locationChangeSuccess', not '$stateChangeStart'
      $rootScope.$on('$locationChangeSuccess', function() {
        user
          .logIn()
          .catch(function() {
            // log-in promise failed. Redirect to log-in page.
            $state.go('logInPage')
          })
      })
    })
    

    请记住,这实际上并不会阻止加载目标状态,但如果用户未经授权,它会重定向到登录页面 . 这没关系,因为无论如何真正的保护在服务器上 .

    解决方案2:使用状态解析

    在此解决方案中,您使用ui-router resolve feature .

    如果用户未经过身份验证,您基本上拒绝 resolve 中的承诺,然后将其重定向到登录页面 .

    这是怎么回事:

    angular.module('App', ['ui.router'])
    
    .config(
      function($stateProvider) {
        $stateProvider
          .state('logInPage', {
            url: '/logInPage',
            templateUrl: 'sections/logInPage.html',
            controller: 'logInPageCtrl',
          })
          .state('myProtectedContent', {
            url: '/myProtectedContent',
            templateUrl: 'sections/myProtectedContent.html',
            controller: 'myProtectedContentCtrl',
            resolve: { authenticate: authenticate }
          })
          .state('alsoProtectedContent', {
            url: '/alsoProtectedContent',
            templateUrl: 'sections/alsoProtectedContent.html',
            controller: 'alsoProtectedContentCtrl',
            resolve: { authenticate: authenticate }
          })
    
        function authenticate($q, user, $state, $timeout) {
          if (user.isAuthenticated()) {
            // Resolve the promise successfully
            return $q.when()
          } else {
            // The next bit of code is asynchronously tricky.
    
            $timeout(function() {
              // This code runs after the authentication promise has been rejected.
              // Go to the log-in page
              $state.go('logInPage')
            })
    
            // Reject the authentication promise to prevent the state from loading
            return $q.reject()
          }
        }
      }
    )
    

    与第一种解决方案不同,此解决方案实际上可以防止目标状态加载 .

  • 2

    首先,您需要一个可以注入到控制器中的服务,该服务对应用程序身份验证状态有所了解 . 使用本地存储保留auth详细信息是一种很好的方法 .

    接下来,您需要在状态更改之前检查auth的状态 . 由于您的应用程序有一些需要进行身份验证的页面而其他页面没有进行身份验证,因此请创建一个检查身份验证的父路由,并使所有其他需要该页面的页面成为该父项的子项 .

    最后,您需要一些方法来判断您当前登录的用户是否可以执行某些操作 . 这可以通过在您的身份验证服务中添加“can”功能来实现 . 可以采用两个参数: - action - required - (即'manage_dashboards'或'create_new_dashboard') - object - 可选 - 正在操作的对象 . 例如,如果您有仪表板对象,则可能需要检查dashboard.ownerId是否=== loggedInUser.id . (当然,永远不应该信任从客户端传递的信息,并且在将其写入数据库之前,应始终在服务器上对此进行验证) .

    angular.module('myApp', ['ngStorage']).config([
       '$stateProvider',
    function(
       $stateProvider
    ) {
       $stateProvider
         .state('home', {...}) //not authed
         .state('sign-up', {...}) //not authed
         .state('login', {...}) //not authed
         .state('authed', {...}) //authed, make all authed states children
         .state('authed.dashboard', {...})
    }])
    .service('context', [
       '$localStorage',
    function(
       $localStorage
    ) {
       var _user = $localStorage.get('user');
       return {
          getUser: function() {
             return _user;
          },
          authed: function() {
             return (_user !== null);
          },
          // server should return some kind of token so the app 
          // can continue to load authenticated content without having to
          // re-authenticate each time
          login: function() {
             return $http.post('/login.json').then(function(reply) {
                if (reply.authenticated === true) {
                   $localStorage.set(_userKey, reply.user);
                }
             });
          },
          // this request should expire that token, rendering it useless
          // for requests outside of this session
          logout: function() {
             return $http.post('logout.json').then(function(reply) {
                if (reply.authenticated === true) {
                   $localStorage.set(_userKey, reply.user);
                }
             });
          },
          can: function(action, object) {
             if (!this.authed()) {
                return false;
             }
    
             var user = this.getUser();
    
             if (user && user.type === 'admin') {
                 return true;
             }
    
             switch(action) {
                case 'manage_dashboards':
                   return (user.type === 'manager');
             }
    
             return false;
    
    
          }
       }
    }])
    .controller('AuthCtrl', [
       'context', 
       '$scope', 
    function(
       context, 
       $scope
    ) {
       $scope.$root.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
          //only require auth if we're moving to another authed page
          if (toState && toState.name.indexOf('authed') > -1) {
             requireAuth();
          }
       });
    
       function requireAuth() {
          if (!context.authed()) {
             $state.go('login');
          }
       }
    }]
    

    免责声明:上面的代码是伪代码,不保证

  • 602

    我想与ui路由器1.0.0.X共享另一个解决方案

    您可能知道,现在不推荐使用stateChangeStart和stateChangeSuccess . https://github.com/angular-ui/ui-router/issues/2655

    相反,你应该使用$ transitions http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html

    这就是我实现它的方式:

    首先,我有 AuthService 和一些有用的功能

    angular.module('myApp')
    
            .factory('AuthService',
                    ['$http', '$cookies', '$rootScope',
                        function ($http, $cookies, $rootScope) {
                            var service = {};
    
                            // Authenticates throug a rest service
                            service.authenticate = function (username, password, callback) {
    
                                $http.post('api/login', {username: username, password: password})
                                        .success(function (response) {
                                            callback(response);
                                        });
                            };
    
                            // Creates a cookie and set the Authorization header
                            service.setCredentials = function (response) {
                                $rootScope.globals = response.token;
    
                                $http.defaults.headers.common['Authorization'] = 'Bearer ' + response.token;
                                $cookies.put('globals', $rootScope.globals);
                            };
    
                            // Checks if it's authenticated
                            service.isAuthenticated = function() {
                                return !($cookies.get('globals') === undefined);
                            };
    
                            // Clear credentials when logout
                            service.clearCredentials = function () {
                                $rootScope.globals = undefined;
                                $cookies.remove('globals');
                                $http.defaults.headers.common.Authorization = 'Bearer ';
                            };
    
                            return service;
                        }]);
    

    然后我有这个配置:

    angular.module('myApp', [
        'ui.router',
        'ngCookies'
    ])
            .config(['$stateProvider', '$urlRouterProvider',
                function ($stateProvider, $urlRouterProvider) {
                    $urlRouterProvider.otherwise('/resumen');
                    $stateProvider
                            .state("dashboard", {
                                url: "/dashboard",
                                templateUrl: "partials/dashboard.html",
                                controller: "dashCtrl",
                                data: {
                                    authRequired: true
                                }
                            })
                            .state("login", {
                                url: "/login",
                                templateUrl: "partials/login.html",
                                controller: "loginController"
                            })
                }])
    
            .run(['$rootScope', '$transitions', '$state', '$cookies', '$http', 'AuthService',
                function ($rootScope, $transitions, $state, $cookies, $http, AuthService) {
    
                    // keep user logged in after page refresh
                    $rootScope.globals = $cookies.get('globals') || {};
                    $http.defaults.headers.common['Authorization'] = 'Bearer ' + $rootScope.globals;
    
                    $transitions.onStart({
                        to: function (state) {
                            return state.data != null && state.data.authRequired === true;
                        }
                    }, function () {
                        if (!AuthService.isAuthenticated()) {
                            return $state.target("login");
                        }
                    });
                }]);
    

    你可以看到我使用的

    data: {
       authRequired: true
    }
    

    标记只有经过身份验证才可访问的状态 .

    然后,在 .run 上,我使用转换来检查验证状态

    $transitions.onStart({
        to: function (state) {
            return state.data != null && state.data.authRequired === true;
        }
    }, function () {
        if (!AuthService.isAuthenticated()) {
            return $state.target("login");
        }
    });
    

    我使用$ transitions文档中的一些代码构建了这个示例 . 我对ui路由器很新,但它有效 .

    希望它可以帮助任何人 .

  • 116

    最简单的解决方案是在未对用户进行身份验证时使用 $stateChangeStartevent.preventDefault() 来取消状态更改,并将其重定向到作为登录页面的 auth 状态 .

    angular
      .module('myApp', [
        'ui.router',
      ])
        .run(['$rootScope', 'User', '$state',
        function ($rootScope, User, $state) {
          $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
            if (toState.name !== 'auth' && !User.authenticaded()) {
              event.preventDefault();
              $state.go('auth');
            }
          });
        }]
      );
    
  • 14

    我认为您需要 service 来处理身份验证过程(及其存储) .

    在这项服务中,您需要一些基本方法:

    • isAuthenticated()

    • login()

    • logout()

    • 等......

    应该在每个模块的控制器中注入此服务:

    • 在仪表板部分中,使用此服务检查用户是否已通过身份验证( service.isAuthenticated() 方法) . 如果没有,重定向到/ login

    • 在您的登录部分,只需使用表单数据通过 service.login() 方法对用户进行身份验证

    这个行为的一个好的和强大的例子是项目angular-app,特别是它的security module,它基于令人敬畏的HTTP Auth Interceptor Module

    希望这可以帮助

相关问题