AngularJS ui-router登录认证

22 浏览
0 Comments

AngularJS ui-router登录认证

我是AngularJS的新手,我有点困惑如何在以下情况下使用angular-\"ui-router\":

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

我为主页部分创建了一个带有其angular应用程序和ui-router配置的index.html,以处理/login/signup视图,

并且还有另一个文件dashboard.html用于仪表板部分,并带有其应用程序和ui-router配置来处理许多子视图。

现在我完成了仪表板部分,不知道如何将两个具有不同angular应用程序的部分组合起来。我该如何告诉主页应用程序重定向到仪表板应用程序?

admin 更改状态以发布 2023年5月21日
0
0 Comments

到目前为止贴出的解决方案都过于复杂了,我认为有一种更简单的方法。在我看来,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()
      }
    }
  }
)

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

0
0 Comments

我正在进行一个更好的演示和整理一些服务以形成可用的模块,但这是我想到的做法。这是一个复杂的过程,为了解决一些问题,所以请耐心等待。你需要将其分成几个部分。

看看这个plunk

首先,你需要一个服务来存储用户的身份。我称之为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();
      });
    }
  ]);

追踪用户身份的棘手之处在于如果您已经进行身份验证(比如说,您在前一个会话后访问页面,并在 cookie 中保存了一个身份验证令牌,或者可能您强制刷新了页面或者通过链接进入了 URL),则需要查找它。由于 ui-router 的工作方式,您需要在进行身份验证之前仅执行一次身份验证。您可以使用状态配置中的 resolve 选项来执行此操作。我有一个站点的父状态,所有状态都继承自该状态,这要求在发生任何其他事情之前必须解决主体

$stateProvider.state('site', {
  'abstract': true,
  resolve: {
    authorize: ['authorization',
      function(authorization) {
        return authorization.authorize();
      }
    ]
  },
  template: ''
})

这里还有另一个问题...... 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;
});

显示或隐藏元素:

   I'm logged in
  I'm not logged in

等等,此类情况等等。无论如何,在您的示例应用程序中,您将拥有一个首页状态,让未经验证的用户路过。他们可以有指向登录或注册状态的链接,或者在该页面中构建这些表单。适合您的任何方法都可以。

仪表板页面都可能继承自需要用户已登录并且是User角色成员的状态。所有我们讨论过的授权内容都从那里流出。

0