AngularJS ui-router 登录认证 [英] AngularJS ui-router login authentication

查看:25
本文介绍了AngularJS ui-router 登录认证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 AngularJS 的新手,我对如何在以下场景中使用 angular-"ui-router" 有点困惑:

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

我已经为 home 部分创建了一个 index.html 和它的 angular 应用程序和 ui-router 配置来处理 /login 和 <代码>/signup 视图,并且还有另一个文件 dashboard.html 用于仪表板部分及其应用程序和 ui-router 配置来处理许多子视图.

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

解决方案

我正在制作一个更好的演示,并将其中的一些服务清理成一个可用的模块,但这是我提出的和.这是解决一些警告的复杂过程,因此请坚持下去.您需要将其分解为几部分.

看看这个 plunk.

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

.factory('principal', ['$q', '$http', '$timeout',函数($q,$http,$timeout){var _identity = 未定义,_认证=假;返回 {isIdentityResolved: function() {返回 angular.isDefined(_identity);},isAuthenticated: function() {返回 _authenticated;},isInRole:函数(角色){if (!_authenticated || !_identity.roles) 返回假;返回 _identity.roles.indexOf(role) != -1;},isInAnyRole:函数(角色){if (!_authenticated || !_identity.roles) 返回假;for (var i = 0; i 

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

.factory('授权', ['$rootScope', '$state', 'principal',函数($rootScope,$state,主体){返回 {授权:函数(){返回 principal.identity().then(function() {var isAuthenticated = principal.isAuthenticated();如果($rootScope.toState.data.roles&&$rootScope.toState.data.roles.length >0&&!principal.isInAnyRole($rootScope.toState.data.roles)){如果(isAuthenticated){//用户已登录但未登录//获得所需状态的授权$state.go('访问被拒绝');} 别的 {//用户未通过身份验证.斯托//在你之前他们想要的状态//将它们发送到登录状态,所以//你可以在完成后返回它们$rootScope.returnToState= $rootScope.toState;$rootScope.returnToStateParams= $rootScope.toStateParams;//现在,将它们发送到登录状态//这样他们就可以登录了$state.go('登录');}}});}};}])

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

.run(['$rootScope', '$state', '$stateParams','授权', '委托人',函数($rootScope,$state,$stateParams,授权,委托人){$rootScope.$on('$stateChangeStart',功能(事件,toState,toStateParams){//跟踪用户想要进入的状态;//授权服务需要这个$rootScope.toState = toState;$rootScope.toStateParams = toStateParams;//如果主体已解析,则执行//立即进行授权检查.除此以外,//它会在它解决的状态时完成.如果(principal.isIdentityResolved())授权.授权();});}]);

如果您已经通过身份验证(例如,您在上一次会话之后访问该页面,并将身份验证令牌保存在 cookie 中,或者您可能硬刷新了一个页面,或从链接拖放到 URL 上).由于 ui-router 的工作方式,您需要在身份验证检查之前进行一次身份解析.您可以使用状态配置中的 resolve 选项执行此操作.我有一个站点的父状态,所有状态都从它继承,这迫使主体在发生任何其他事情之前得到解决.

$stateProvider.state('site', {'抽象':真的,解决: {授权:['授权',功能(授权){返回授权.授权();}]},模板:'

'})

这里还有一个问题... resolve 只被调用一次.一旦您的身份查找承诺完成,它就不会再次运行解析委托.因此,我们必须在两个地方进行您的身份验证检查:一次根据您的身份承诺在 resolve 中解析,它涵盖您的应用程序首次加载,一次在 $stateChangeStart如果解决方案已完成,则涵盖您在各州之间导航的任何时间.

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

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

我们从这里去哪里?好吧,您可以将您的状态组织到需要登录的区域中.您可以通过将带有 rolesdata 添加到这些状态(或它们的父级,如果你想使用继承).在这里,我们将资源限制为管理员:

.state('受限', {父:'网站',url: '/受限',数据: {角色:['管理员']},意见:{'内容@': {templateUrl: 'restricted.html'}}})

现在您可以逐州控制哪些用户可以访问路线.还有其他顾虑吗?也许根据他们是否登录只改变视图的一部分?没问题.将 principal.isAuthenticated() 甚至 principal.isInRole() 与您可以有条件地显示模板或元素的多种方式中的任何一种一起使用.

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

.scope('HomeCtrl', ['$scope', 'principal',功能($范围,主体){$scope.principal = 委托人;});

显示或隐藏元素:

我已登录

<div ng-hide="principal.isAuthenticated()">我没有登录

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

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

I am new to AngularJS, and I am a little confused of how I can use angular-"ui-router" in the following scenario:

I am building a web application which consists of two sections. The first section is the homepage with its login and signup views, and the second section is the dashboard (after successful login).

I have created an index.html for the home section with its angular app and ui-router config to handle /login and /signup views, and there is another file dashboard.html for the dashboard section with its app and ui-router config to handle many sub views.

Now I finished the dashboard section and don't know how to combine the two sections with their different angular apps. How could I tell the home app to redirect to the dashboard app?

解决方案

I'm in the process of making a nicer demo as well as cleaning up some of these services into a usable module, but here's what I've come up with. This is a complex process to work around some caveats, so hang in there. You'll need to break this down into several pieces.

Take a look at this plunk.

First, you need a service to store the user's identity. I call this principal. It can be checked to see if the user is logged in, and upon request, it can resolve an object that represents the essential information about the user's identity. This can be whatever you need, but the essentials would be a display name, a username, possibly an email, and the roles a user belongs to (if this applies to your app). Principal also has methods to do role checks.

.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;
      }
    };
  }
])

Second, you need a service that checks the state the user wants to go to, makes sure they're logged in (if necessary; not necessary for signin, password reset, etc.), and then does a role check (if your app needs this). If they are not authenticated, send them to the sign-in page. If they are authenticated, but fail a role check, send them to an access denied page. I call this service 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');
              }
            }
          });
      }
    };
  }
])

Now all you need to do is listen in on ui-router's $stateChangeStart. This gives you a chance to examine the current state, the state they want to go to, and insert your authorization check. If it fails, you can cancel the route transition, or change to a different route.

.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();
      });
    }
  ]);

The tricky part about tracking a user's identity is looking it up if you've already authenticated (say, you're visiting the page after a previous session, and saved an auth token in a cookie, or maybe you hard refreshed a page, or dropped onto a URL from a link). Because of the way ui-router works, you need to do your identity resolve once, before your auth checks. You can do this using the resolve option in your state config. I have one parent state for the site that all states inherit from, which forces the principal to be resolved before anything else happens.

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

There's another problem here... resolve only gets called once. Once your promise for identity lookup completes, it won't run the resolve delegate again. So we have to do your auth checks in two places: once pursuant to your identity promise resolving in resolve, which covers the first time your app loads, and once in $stateChangeStart if the resolution has been done, which covers any time you navigate around states.

OK, so what have we done so far?

  1. We check to see when the app loads if the user is logged in.
  2. We track info about the logged in user.
  3. We redirect them to sign in state for states that require the user to be logged in.
  4. We redirect them to an access denied state if they do not have authorization to access it.
  5. We have a mechanism to redirect users back to the original state they requested, if we needed them to log in.
  6. We can sign a user out (needs to be wired up in concert with any client or server code that manages your auth ticket).
  7. We don't need to send users back to the sign-in page every time they reload their browser or drop on a link.

Where do we go from here? Well, you can organize your states into regions that require sign in. You can require authenticated/authorized users by adding data with roles to these states (or a parent of them, if you want to use inheritance). Here, we restrict a resource to Admins:

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

Now you can control state-by-state what users can access a route. Any other concerns? Maybe varying only part of a view based on whether or not they are logged in? No problem. Use the principal.isAuthenticated() or even principal.isInRole() with any of the numerous ways you can conditionally display a template or an element.

First, inject principal into a controller or whatever, and stick it to the scope so you can use it easily in your view:

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

Show or hide an element:

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

Etc., so on, so forth. Anyways, in your example app, you would have a state for home page that would let unauthenticated users drop by. They could have links to the sign-in or sign-up states, or have those forms built into that page. Whatever suits you.

The dashboard pages could all inherit from a state that requires the users to be logged in and, say, be a User role member. All the authorization stuff we've discussed would flow from there.

这篇关于AngularJS ui-router 登录认证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆