7

我编写了一个应用程序,在处理路由之前,我需要在应用程序运行时检索当前登录的用户信息。我使用ui-router来支持多个/嵌套视图并提供更丰富的有状态路由。

当用户登录时,他们可能会存储一个代表其身份验证令牌的 cookie。我将该令​​牌包含在对服务的调用中,以检索用户的信息,其中包括他们所属的组。然后在服务中设置生成的身份,在该服务中可以检索并在应用程序的其余部分中使用它。更重要的是,路由器将使用该身份来确保它们已登录并属于适当的组,然后再将它们转换为请求的状态。

我有这样的代码:

app
    .config(['$stateProvider', function($stateProvider) {

        // two states; one is the protected main content, the other is the sign-in screen

        $stateProvider
            .state('main', {
                url: '/',
                data: {
                    roles: ['Customer', 'Staff', 'Admin']
                },
                views: {} // omitted
            })
            .state('account.signin', {
                url: '/signin',
                views: {} // omitted
            });
    }])
    .run(['$rootScope', '$state', '$http', 'authority', 'principal', function($rootScope, $state, $http, authority, principal) {

        $rootScope.$on('$stateChangeStart', function (event, toState) { // listen for when trying to transition states...
            var isAuthenticated = principal.isAuthenticated(); // check if the user is logged in

            if (!toState.data.roles || toState.data.roles.length == 0) return; // short circuit if the state has no role restrictions

            if (!principal.isInAnyRole(toState.data.roles)) { // checks to see what roles the principal is a member of
                event.preventDefault(); // role check failed, so...
                if (isAuthenticated) $state.go('account.accessdenied'); // tell them they are accessing restricted feature
                else $state.go('account.signin'); // or they simply aren't logged in yet
            }
        });

        $http.get('/svc/account/identity') // now, looks up the current principal
            .success(function(data) {
                authority.authorize(data); // and then stores the principal in the service (which can be injected by requiring "principal" dependency, seen above)
            }); // this does its job, but I need it to finish before responding to any routes/states
    }]);

如果我登录、导航、注销等,一切都会按预期工作。问题是,如果我在登录时刷新或删除 URL,我会被发送到登录屏幕,因为身份服务调用没有在状态改变之前完成。但是,在该呼叫完成后,如果有链接或其他东西到状态,我可以继续按预期工作main,所以我快到了。

我知道您可以让状态在转换之前等待解析参数,但我不确定如何继续。

4

3 回答 3

10

好的,在拉了很多头发之后,这就是我想出来的。

  1. 如您所料,resolve这是启动任何异步调用并确保它们在状态转换到之前完成的适当位置。
  2. 您将希望为确保您resolve发生的所有状态创建一个抽象的父状态,这样如果有人刷新浏览器,您的异步解析仍然会发生并且您的身份验证正常工作。您可以使用parenta 上的属性state来创建另一个不会通过命名/点表示法继承的状态。这有助于防止您的州名变得难以管理。
  3. 虽然您可以将所需的任何服务注入到您的resolve中,但您无法访问它尝试转换到的状态的toStateor 。toStateParams但是,该$stateChangeStart事件将在您resolve解决之前发生。toState因此,您可以将事件 args和toStateParams从事件 args复制到您的$rootScope, 并注入$rootScope到您的resolve函数中。现在您可以访问它尝试转换到的状态和参数。
  4. 一旦你解决了你的资源,你可以使用 Promise 来做你的授权检查,如果失败,使用$state.go()将它们发送到登录页面,或者做任何你需要做的事情。当然,对此有一个警告。
  5. 一旦resolve在父状态下完成,ui-router 将不会再次解析它。这意味着您的安全检查不会发生!啊!解决此问题的方法是进行两部分检查。正如resolve我们已经讨论过的那样。第二次是在$stateChangeStart活动中。这里的关键是检查资源是否已解决。如果是,请执行与您在resolve事件中所做的相同的安全检查。如果资源解决,则签入resolve将获取它。要实现这一点,您需要在服务中管理资源,以便适当地管理状态。

其他一些杂项。笔记:

  • 不要试图将所有 authz 逻辑塞进$stateChangeStart. 虽然您可以阻止该事件并进行异步解析(这会有效地停止更改,直到您准备好),然后尝试在您的承诺成功处理程序中恢复状态更改,但有一些问题会阻止它正常工作。
  • 您不能在当前状态的onEnter方法中更改状态。

这个 plunk是一个工作示例。

于 2014-01-22T20:30:49.000 回答
1

我们遇到了类似的问题。我们觉得我们需要使处理响应的逻辑可以访问 HTTP 调用的逻辑。它们在代码中是分开的,因此服务是实现此目的的好方法。

我们通过将$http.get调用包装在缓存响应并在缓存已填充时立即调用成功回调的服务中解决了这种分离问题。例如

app.service('authorizationService', ['authority', function (authority) {
    var requestData = undefined;

    return {
        get: function (successCallback) {
            if (typeof requestData !== 'undefined') {
                successCallback(requestData);
            }
            else {
                $http.get('/svc/account/identity').success(function (data) {
                    requestData = data;
                    successCallback(data);
                });
            }
        }
    };
}]);

这充当了请求成功的保护措施。authorizationService.get()然后,您可以安全地在您的处理程序中调用$stateChangeStart

如果在调用时请求已经在进行中,这种方法很容易受到竞争条件的影响authorizationService.get()。可能会引入一些 XHR 簿记来防止这种情况。

或者,您可以在 HTTP 请求完成后发布自定义事件,并在处理程序中为该事件注册订阅 $stateChangeStart者。不过,您稍后可能需要取消注册该处理程序,可能在$stateChangeEnd处理程序中。

在授权完成之前,这一切都无法完成,因此您应该通过显示加载视图来通知用户他们需要等待。

ui-router此外,在 How to Filter Routes?中有一些关于身份验证的有趣讨论?在 AngularJS 谷歌组上。

于 2013-12-17T11:48:07.417 回答
0

我认为处理这种情况的一种更简洁的方法是停止$urlRouterProvider. deferIntercept()uiRouter$urlRouter.listen()直到从服务器检索到一些数据。

有关更多信息,请参阅此答案文档

于 2015-09-30T07:32:19.243 回答