AngularJS UI路由器登录身份验证

本文翻译自:AngularJS ui-router login authentication

I am new to AngularJS, and I am a little confused of how I can use angular-"ui-router" in the following scenario: 我是AngularJS的新手,在以下情况下我对如何使用angular-“ ui-router”感到有些困惑:

I am building a web application which consists of two sections. 我正在构建一个包含两个部分的Web应用程序。 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. 我已经使用它的角度应用程序和ui-router配置为home部分创建了一个index.html来处理/login/signup视图,并且对于它的app和ui-router配置来了dashboard部分的另一个文件dashboard.html处理许多子视图。

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? 我如何告诉家用应用程序重定向到仪表板应用程序?


#1楼

参考:https://stackoom.com/question/1WYz1/AngularJS-UI路由器登录身份验证


#2楼

I think you need a service that handle the authentication process (and its storage). 我想你需要一个service是处理身份验证过程(及其存储)。

In this service you'll need some basic methods : 在此服务中,您需要一些基本方法:

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

This service should be injected in your controllers of each module : 该服务应该注入到每个模块的控制器中:

  • In your dashboard section, use this service to check if user is authenticated ( service.isAuthenticated() method) . 在仪表板部分中,使用此服务来检查用户是否已通过身份验证( service.isAuthenticated()方法)。 if not, redirect to /login 如果不是,请重定向到/ login
  • In your login section, just use the form data to authenticate the user through your service.login() method 在登录部分中,只需使用表单数据通过service.login()方法对用户进行身份验证

A good and robust example for this behavior is the project angular-app and specifically its security module which is based over the awesome HTTP Auth Interceptor Module 关于此行为的一个很好且强大的示例是angular-app项目,尤其是其安全模块 ,该模块基于出色的HTTP Auth拦截器模块

Hope this helps 希望这可以帮助


#3楼

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 . 看看这个pl 。

First, you need a service to store the user's identity. 首先,您需要一项服务来存储用户的身份。 I call this principal . 我称这个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. 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;
      }
    };
  }
])

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 . 我称此服务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 . 现在,您需要做的就是监听ui-router$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). 跟踪用户身份的棘手部分是如果您已通过身份验证,则查找该身份(例如,您在上一个会话后访问该页面,并将auth令牌保存在cookie中,或者您可能难以刷新页面,或者从链接拖放到URL上)。 Because of the way ui-router works, you need to do your identity resolve once, before your auth checks. 由于ui-router工作方式,您需要在身份验证之前进行一次身份解析。 You can do this using the resolve option in your state config. 您可以使用状态配置中的resolve选项来执行此操作。 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: '
' })

There's another problem here... resolve only gets called once. 这里还有另一个问题... resolve只被调用一次。 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. 因此,我们必须在两个地方进行身份验证检查:一次是根据您的身份承诺在resolveresolve ,这涵盖了您的应用程序的首次加载;一次是在$stateChangeStart如果解决方案已完成),它涵盖了您每次浏览的时间状态。

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). 好吧,您可以将状态组织到需要登录的区域中。可以通过向这些状态(或如果要使用继承,则为它们的父级)添加具有roles data来要求经过身份验证/授权的用户。 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. 可以通过多种可有条件地显示模板或元素的方式中的任何一种,使用principal.isAuthenticated()principal.isInRole()

First, inject principal into a controller or whatever, and stick it to the scope so you can use it easily in your view: 首先,将principal注入控制器或其他任何东西,并将其粘贴到示波器上,以便可以在视图中轻松使用它:

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

Show or hide an element: 显示或隐藏元素:

I'm logged in
I'm not logged in

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. 仪表板页面都可以从要求用户登录并成为User角色成员的状态继承。 All the authorization stuff we've discussed would flow from there. 我们讨论过的所有授权内容都将从那里流淌。


#4楼

Here is how we got out of the infinite routing loop and still used $state.go instead of $location.path 这是我们摆脱无限路由循环并仍然使用$state.go而不是$location.path

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

#5楼

I Created this module to help make this process piece of cake 我创建了这个模块来帮助使这一过程变得轻松

You can do things like: 您可以执行以下操作:

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

Or also 或者也

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

It's brand new but worth checking out! 这是全新的,但值得一试!

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


#6楼

The solutions posted so far are needlessly complicated, in my opinion. 我认为到目前为止发布的解决方案不必要地复杂。 There's a simpler way. 有一种更简单的方法。 The documentation of ui-router says listen to $locationChangeSuccess and use $urlRouter.sync() to check a state transition, halt it, or resume it. ui-router的文档说监听$locationChangeSuccess并使用$urlRouter.sync()检查状态转换,中止或恢复状态转换。 But even that actually doesn't work. 但这实际上是行不通的。

However, here are two simple alternatives. 但是,这里有两个简单的选择。 Pick one: 选一个:

Solution 1: listening on $locationChangeSuccess 解决方案1:监听$locationChangeSuccess

You can listen to $locationChangeSuccess and you can perform some logic, even asynchronous logic there. 您可以收听$locationChangeSuccess并且可以执行一些逻辑,甚至在那里的异步逻辑。 Based on that logic, you can let the function return undefined, which will cause the state transition to continue as normal, or you can do $state.go('logInPage') , if the user needs to be authenticated. 根据该逻辑,可以让函数返回未定义的状态,这将导致状态转换照常进行,或者,如果需要验证用户,可以执行$state.go('logInPage') Here's an example: 这是一个例子:

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

Keep in mind that this doesn't actually prevent the target state from loading, but it does redirect to the log-in page if the user is unauthorized. 请记住,这实际上并不能阻止加载目标状态,但是如果用户未经授权,它确实会重定向到登录页面。 That's okay since real protection is on the server, anyway. 没关系,因为无论如何服务器上都有真正的保护。

Solution 2: using state resolve 解决方案2:使用状态resolve

In this solution, you use ui-router resolve feature . 在此解决方案中,您将使用ui-router resolve功能 。

You basically reject the promise in resolve if the user is not authenticated and then redirect them to the log-in page. 如果用户未通过身份验证,您基本上会拒绝resolve承诺,然后将其重定向到登录页面。

Here's how it goes: 这是怎么回事:

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

Unlike the first solution, this solution actually prevents the target state from loading. 与第一个解决方案不同,此解决方案实际上阻止了目标状态的加载。

你可能感兴趣的:(javascript,angularjs)