AngularJS UI路由器登录身份验证

2020/09/29 18:41 · javascript ·  · 0评论

我是AngularJS的新手,在以下情况下我对如何使用angular-“ ui-router”感到有些困惑:

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

index.html为home部分创建了一个带有角度应用程序和ui-router配置的,用于处理/login/signup查看,还有一个dashboard.html针对仪表板部分的文件,其应用程序和ui-router配置用于处理许多子视图。

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

我正在做一个更好的演示,并将其中的一些服务清理到一个可用的模块中,但这就是我想出的。要解决一些警告,这是一个复杂的过程,所以请继续。您需要将其分解为几部分。

看看这个pl

首先,您需要一项服务来存储用户的身份。我叫这个principal可以检查它是否查看用户是否登录,并且可以根据请求解析一个代表有关用户身份的基本信息的对象。这可以是您需要的任何内容,但必不可少的是显示名称,用户名(可能是电子邮件)以及用户所属的角色(如果这适用于您的应用程序)。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();
      });
    }
  ]);

跟踪用户身份的棘手部分是如果您已通过身份验证,则查找它(例如,您在上一个会话之后访问该页面,并将auth令牌保存在cookie中,或者您可能难以刷新页面,或者从链接拖放到URL上)。由于这种方式ui-router有效,因此需要在身份验证之前完成一次身份解析。您可以使用resolve状态配置中选项来执行此操作对于所有状态都继承自该站点的站点,我有一个父状态,这会迫使主体在发生其他任何事情之前先进行解析。

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

这里还有另一个问题... resolve只会被调用一次。您对身份查询的承诺完成后,将不再运行解析委托。因此,我们必须在两个地方进行身份验证:一次是根据身份承诺解析到中resolve,这涉及到您的应用程序首次加载;一次是$stateChangeStart在解决方案完成后,这涉及到您在各州之间进行导航的任何时间。

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

  1. 如果用户已登录,我们会检查该应用何时加载。
  2. 我们跟踪有关登录用户的信息。
  3. 我们将它们重定向到需要用户登录的状态的登录状态。
  4. 如果他们没有访问权限,我们会将他们重定向到拒绝访问状态。
  5. 如果需要用户登录,我们有一种机制可以将用户重定向回他们请求的原始状态。
  6. 我们可以注销用户(需要与管理身份验证票证的任何客户端或服务器代码保持一致)。
  7. 每当用户重新加载浏览器或断开链接时,我们都不需要将用户带回到登录页面。

我们从这里去哪里?嗯,你可以组织你的状态到需要的标志地区。您可以通过添加需要身份验证/授权用户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角色成员的状态继承我们讨论过的所有授权内容都将从那里流淌。

我认为,到目前为止发布的解决方案不必要地复杂。有一种更简单的方法。文档ui-router监听$locationChangeSuccess并用于$urlRouter.sync()检查状态转换,中止或恢复状态。但是,即使这样实际上也不起作用。

但是,这里有两个简单的选择。选一个:

解决方案1:听 $locationChangeSuccess

您可以听,$locationChangeSuccess并且可以执行一些逻辑,甚至在那里执行异步逻辑。根据该逻辑,您可以让函数返回未定义的状态,这将导致状态转换照常进行$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:使用状态 resolve

在此解决方案中,您将使用ui-router解析功能

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()
      }
    }
  }
)

与第一个解决方案不同,此解决方案实际上阻止了目标状态的加载。

最简单的解决方案是在用户未通过身份验证时使用$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');
        }
      });
    }]
  );

我认为您需要一个service处理身份验证过程(及其存储)的程序。

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

  • isAuthenticated()
  • login()
  • logout()
  • 等...

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

  • 在仪表板部分中,使用此服务检查用户是否已通过身份验证(service.isAuthenticated()方法)。如果不是,请重定向到/ login
  • 在登录部分中,只需使用表单数据即可通过您的service.login()方法对用户进行身份验证

关于此行为的一个很好且强大的示例是angular-app项目,尤其是其安全模块模块基于出色的HTTP Auth拦截器模块

希望这可以帮助

我创建了这个模块来帮助使这一过程变得轻松

您可以执行以下操作:

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

或者也

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

这是全新的,但值得一试!

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

我想分享使用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路由器很陌生,但是可以用。

希望它可以帮助任何人。

这是我们摆脱无限路由循环并仍然$state.go代替$location.path

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

我有另一个解决方案:当您仅具有要在登录时显示的内容时,该解决方案将非常有效。定义一个规则,检查您是否登录,而不是白名单路由路径。

$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 */ }

使用$ http拦截器

通过使用$ http拦截器,您可以将标头发送到后端或其他方式,然后以这种方式进行检查。

关于$ http拦截器的好文章

例:

$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函数中。

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

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

最后,您将需要某种方式来判断您当前登录的用户是否可以执行某些操作。这可以通过在身份验证服务中添加“可以”功能来实现。可以采用两个参数:-操作-必需-(即“ manage_dashboards”或“ create_new_dashboard”)-对象-可选-被操作的对象。例如,如果您有一个仪表板对象,则可能要检查一下dashboard.ownerId === loggingInUser.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');
      }
   }
}]

**免责声明:以上代码为伪代码,不提供任何保证**

本文地址:http://javascript.askforanswer.com/angularjs-uiluyouqidenglushenfenyanzheng.html
文章标签: ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!