角度身份验证:避免针对不同路由的多个解析

时间:2015-11-24 12:33:11

标签: javascript angularjs

我正在开发一个Angular应用程序。在此,我在进入仪表板之前对用户进行身份验证。为实现这一目标,我将signIn function写为

登录功能

this.signIn = function(credentials) {
        console.info('AccountController[signIn] Called');

        AuthService
            .login(credentials)
            .then(function(authenticatedUser) {
                $scope.globals['currentUser'] = authenticatedUser;

                AuthService.setCurrentUser(authenticatedUser);

                $scope.globals['isAuthenticated'] = true;
                $location.path('/dashboard');                    

            }).catch(function(error) {
                console.warn('AccountController[signIn] :: ', error);
                Flash.Error(error);
                $scope.credentials.password = '';
            });
    };

我还想限制用户访问路由,如果他们没有登录。为了达到这个目的,我想出了这个dirty code

路线

$stateProvider
        .state('signIn', {
            url: '/signIn',
            templateUrl: 'partials/signIn/signIn.html',
            data: {
                pageTitle: 'SignIn'
            },
            controller: 'AccountController',
            controllerAs: 'ac',
            resolve: {
                auth: ['$q', 'AuthService', function($q, AuthService) {
                    var userInfo = AuthService.isAuthenticated();
                    console.info('SignIn Route[isAuthenticated] :: ', userInfo);
                    if (!userInfo) {
                        return $q.when(userInfo);
                    } else {
                        return $q.reject({
                            isAuthenticated: true
                        });
                    }
                }]
            }
        })
        .state('dashboard', {
            url: '/dashboard',
            templateUrl: 'partials/dashboard.html',
            controller: 'DashboardController',
            access: {
                requiredLogin: true
            },
            resolve: {
                auth: ['$q', 'AuthService', function($q, AuthService) {
                    var authenticated = AuthService.isAuthenticated();
                    console.info('dashboard Route[isAuthenticated] :: ', authenticated);
                    if (authenticated) {
                        return $q.when(authenticated);
                    } else {
                        return $q.reject({
                            isAuthenticated: false
                        });
                    }
                }]
            }
        })
        .state('manageStudent', {
            url: '/manageStudent',
            templateUrl: 'partials/manageStudent.html',
            access: {
                requiredLogin: true
            },
            resolve: {
                auth: ['$q', 'AuthService', function($q, AuthService) {
                    var authenticated = AuthService.isAuthenticated();
                    if (authenticated) {
                        return $q.when(authenticated);
                    } else {
                        return $q.reject({
                            isAuthenticated: false
                        });
                    }
                }]
            }
        });


App.run(['$rootScope', 'settings', '$state', 'AuthService', '$location', function($rootScope, settings, $state, AuthService, $location) {
    $rootScope.$state = $state; // state to be accessed from view
    $rootScope.$settings = settings; // state to be accessed from view

    $rootScope.$on('$stateChangeStart', function(event, next,nextParams,prev,prevParams) {

        // If the user is logged in don't allow him to land on the Login Page


        if (next.access !== undefined) {
            if (next.access.requiredLogin && !AuthService.isAuthenticated()) {

                $location.path('/signIn');
            }
        }


    });


    $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {

        event.preventDefault();
        if (!error.isAuthenticated) {
            console.warn("I'm not Authenticated.Going to Sign-in");

            return $location.path('/signIn');
        } else {
            console.info("I'm Authenticated");
            $location.path('/dashboard');

        }
    });
}]);

原因我说上面的代码 DIRTY 是因为,如果我有10条路由我想要保护未经验证的用户,我必须在所有路由中复制相同的解析功能。

所以我的问题是,我该怎样做才能摆脱多个解析函数并能够编写DRY代码?

4 个答案:

答案 0 :(得分:3)

由于auth应该在每次路由更改时得到解决,因此将其包装到单独的factory(这是一个单独的并且只运行一次)是不够的。为了解决这个限制,它应该是一个函数

app.factory('authResolver', function ($q, AuthService) {
  return function () {
    // ...
  };
});

在每个路线上运行解析

...
resolve: {
  auth: function (authResolver) {
    return authResolver();
  }
}

仍然不是干,但这是建议的湿度水平。

更加激进的方法可以保存样板resolve并保存几行代码to that

app.run(function ($rootScope, authResolver) {
  $rootScope.$on('$stateChangeStart', function (e, to) {
    if (to.doAuthPlease)
      to.resolve.auth = authResolver();
  });
});

...
doAuthPlease: true,
resolve: {}

在上述答案中与ngRoute的明显区别在于,在UI路由器中,您需要定义resolve对象,以便能够动态地向状态添加新的解析器。它可以被视为like that或按原样保留。

答案 1 :(得分:0)

到目前为止,你走在正确的轨道上。您在状态对象上看起来像自定义数据成员access: { requiredLogin: true}

下一步是将其与ui-router提供的State Change Events一起使用:

$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) {
    if (toState.access.requiredLogin) {
       if(!AuthService.isAuthenticated()) {
           event.preventDefault();
           // redirect to signIn?
       }
    }
});

这将放在你的.run区块中,这意味着AuthService也需要注入。这应该不需要在每个路由上使用resolve块。

希望有所帮助。

<强>更新

如果您的AuthService.isAuthenticated()函数返回一个promise,依赖promise在事件处理程序中解析可能会有危险(它可能会在promise解析之前继续)。最好在块之前运行AuthService函数(当应用程序启动时),然后将其存储在变量中:

var isAuth;
AuthService.isAuthenticated().then(function (result) { isAuth = result });

$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) {
    if (toState.access.requiredLogin) {
       if(!isAuth) {
           event.preventDefault();
           // redirect to signIn?
       }
    }
});

答案 2 :(得分:0)

var $delegate = $stateProvider.state;
    $stateProvider.state = function(name, definition) {
        var unrestricted = ['signIn'];

        if (unrestricted.indexOf(name) === -1) {
            definition.resolve = angular.extend({}, definition.resolve, {
                auth: ['$q', 'AuthService', function($q, AuthService) {
                    var authenticated = AuthService.isAuthenticated();
                    if (authenticated) {
                        return $q.when(authenticated);
                    } else {
                        return $q.reject({
                            isAuthenticated: false
                        });
                    }
                }]
            });
        }


        return $delegate.apply(this, arguments);
    };

我在这里动态地将解析添加到我想要限制的路线。

答案 3 :(得分:0)

因为您正在使用 ui.router状态(假设您正在使用v0.2.0或更高版本),您可以使用状态继承来解决此问题{{1并且不必在各种状态下复制它。

  

儿童国家从父母国家继承什么?

     

子状态 DO 从父状态继承以下内容:

     
      
  • 通过解决方案解决了依赖关系
  •   
  • 自定义数据属性
  •   
     

没有其他任何东西被继承(没有控制器,模板,网址等)。

     

继承已解决的依赖项

     

版本0.2.0中的新功能

     

子状态将从父状态继承已解析的依赖关系,它们可以覆盖它们。然后,您可以将已解析的依赖项注入控制器并解析子状态的功能。

     

src - https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#what-do-child-states-inherit-from-parent-states

我通过使用抽象基本状态来实现这一点,该基本状态将基本上定义您正在执行的操作,检查是否允许用户继续操作。由于我的所有UI状态都从抽象父状态继承,因此将为每个状态解析身份验证依赖关系。

抽象基础状态

resolve

其他州

.state('baseState', {
    url: '',
    abstract: true,
    template: '<ui-view></ui-view>'
    resolve: {
        auth: ['$q', 'AuthService', function($q, AuthService) {
            var authenticated = AuthService.isAuthenticated();
            console.info('dashboard Route[isAuthenticated] :: ', authenticated);
            if (authenticated) {
                return $q.when(authenticated);
            } else {
                return $q.reject({
                    isAuthenticated: false
                });
            }
        }]
    }
})