在运行应用程序并切换到路由或状态之前解析$ http请求

时间:2022-06-01 11:16:23

I have written an app where I need to retrieve the currently logged in user's info when the application runs, before routing is handled. I use ui-router to support multiple/nested views and provide richer, stateful routing.

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

When a user logs in, they may store a cookie representing their auth token. I include that token with a call to a service to retrieve the user's info, which includes what groups they belong to. The resulting identity is then set in a service, where it can be retrieved and used in the rest of the application. More importantly, the router will use that identity to make sure they are logged in and belong to the appropriate group before transitioning them to the requested state.

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

I have code something like this:

我有这样的代码:

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

It all works as expected if I log in, navigate around, log out, etc. The issue is that if I refresh or drop on a URL while I am logged in, I get sent to the signin screen because the identity service call has not finished before the state changes. After that call completes, though, I could feasibly continue working as expected if there is a link or something to- for example- the main state, so I'm almost there.

如果我登录,导航,退出等,这一切都按预期工作。问题是,如果我在登录时刷新或删除URL,我会被发送到登录屏幕,因为身份服务呼叫没有在州改变之前完成。然而,在该调用完成之后,如果存在链接或某些东西(例如主要状态),我可以按预期继续工作,所以我几乎就在那里。

I am aware that you can make states wait to resolve parameters before transitioning, but I'm not sure how to proceed.

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

3 个解决方案

#1


10  

OK, after much hair pulling, here is what I figured out.

好吧,经过多次拔毛,这就是我想到的。

  1. As you might expect, resolve is the appropriate place to initiate any async calls and ensure they complete before the state is transitioned to.
  2. 正如您所料,resolve是启动任何异步调用并确保它们在状态转换之前完成的适当位置。
  3. You will want to make an abstract parent state for all states that ensures your resolve takes place, that way if someone refreshes the browser, your async resolution still happens and your authentication works properly. You can use the parent property on a state to make another state that would otherwise not be inherited by naming/dot notation. This helps in preventing your state names from becoming unmanageable.
  4. 您将希望为确保您的解析发生的所有状态创建抽象父状态,这样如果有人刷新浏览器,您的异步解析仍然发生并且您的身份验证正常工作。您可以在状态上使用父属性来创建另一个状态,否则该状态将不会被命名/点表示法继承。这有助于防止您的州名变得无法管理。
  5. While you can inject whatever services you need into your resolve, you can't access the toState or toStateParams of the state it is trying to transition to. However, the $stateChangeStart event will happen before your resolve is resolved. So, you can copy toState and toStateParams from the event args to your $rootScope, and inject $rootScope into your resolve function. Now you can access the state and params it is trying to transition to.
  6. 虽然您可以将所需的任何服务注入到您的解析中,但您无法访问它尝试转换到的状态的toState或toStateParams。但是,$ stateChangeStart事件将在解析之前发生。因此,您可以将事件args中的toState和toStateParams复制到$ rootScope,并将$ rootScope注入到您的resolve函数中。现在您可以访问它尝试转换到的状态和参数。
  7. Once you have resolved your resource(s), you can use promises to do your authorization check, and if it fails, use $state.go() to send them to the login page, or do whatever you need to do. There is a caveat to that, of course.
  8. 一旦解决了资源,就可以使用promises进行授权检查,如果失败,可以使用$ state.go()将它们发送到登录页面,或者做任何你需要做的事情。当然,有一点需要注意。
  9. Once resolve is done in the parent state, ui-router won't resolve it again. That means your security check won't occur! Argh! The solution to this is to have a two-part check. Once in resolve as we've already discussed. The second time is in the $stateChangeStart event. The key here is to check and see if the resource(s) are resolved. If they are, do the same security check you did in resolve but in the event. if the resource(s) are not resolved, then the check in resolve will pick it up. To pull this off, you need to manage your resources within a service so you can appropriately manage state.
  10. 一旦在父状态下完成解析,ui-router将不再解析它。这意味着您的安全检查不会发生!哎呀!解决方案是进行两部分检查。一旦解决,我们已经讨论过了。第二次是在$ stateChangeStart事件中。这里的关键是检查并查看资源是否已解决。如果是,请执行相同的安全检查,但是在事件中。如果资源未得到解决,那么check in resolve将会解析它。要实现此目的,您需要在服务中管理资源,以便适当地管理状态。

Some other misc. notes:

其他一些misc。笔记:

  • Don't bother trying to cram all of the authz logic into $stateChangeStart. While you can prevent the event and do your async resolution (which effectively stops the change until you are ready), and then try and resume the state change in your promise success handler, there are some issues preventing that from working properly.
  • 不要试图将所有authz逻辑塞入$ stateChangeStart。虽然您可以阻止事件并执行异步解析(在您准备好之前有效地停止更改),然后尝试在promise承诺成功处理程序中恢复状态更改,但是存在一些阻止其正常工作的问题。
  • You can't change states in the current state's onEnter method.
  • 您无法更改当前状态的onEnter方法中的状态。

This plunk is a working example.

这个插件是一个工作的例子。

#2


1  

We hit a similar issue. We felt we needed to make the logic which makes the HTTP call accessible to the logic that handles the response. They're separate in the code, so a service is a good way to do this.

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

We resolved that separation by wrapping the $http.get call in a service which caches the response and calls the success callback immediately if the cache is already populated. E.g.

我们通过将$ 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);
                });
            }
        }
    };
}]);

This acts as a guard around the request being successful. You could then call authorizationService.get() within your $stateChangeStart handler safely.

这可以防止请求成功。然后,您可以安全地在$ stateChangeStart处理程序中调用authorizationService.get()。

This approach is vulnerable to a race condition if a request is in already progress when authorizationService.get() is called. It might be possible to introduce some XHR bookkeeping to prevent that.

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

Alternatively you could publish a custom event once the HTTP request has completed and register a subscriber for that event within the $stateChangeStart handler. You would need to unregister that handler later though, perhaps in a $stateChangeEnd handler.

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

None of this can complete until the authorisation is done, so you should inform the user that they need to wait by showing a loading view.

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

Also, there's some interesting discussion of authentication with ui-router in How to Filter Routes? on the AngularJS Google Group.

另外,在如何过滤路由的过程中,有一些关于ui-router认证的有趣讨论?在AngularJS Google Group上。

#3


0  

I think a cleaner way to handle this situation is t$urlRouterProvider. deferIntercept() together with $urlRouter.listen() in order to stop uiRouter until some data is retrieved from server.

我认为处理这种情况的一种更简洁的方法是t $ urlRouterProvider。 deferIntercept()与$ urlRouter.listen()一起使用,以便在从服务器检索到某些数据之前停止uiRouter。

See this answer and docs for more information.

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

#1


10  

OK, after much hair pulling, here is what I figured out.

好吧,经过多次拔毛,这就是我想到的。

  1. As you might expect, resolve is the appropriate place to initiate any async calls and ensure they complete before the state is transitioned to.
  2. 正如您所料,resolve是启动任何异步调用并确保它们在状态转换之前完成的适当位置。
  3. You will want to make an abstract parent state for all states that ensures your resolve takes place, that way if someone refreshes the browser, your async resolution still happens and your authentication works properly. You can use the parent property on a state to make another state that would otherwise not be inherited by naming/dot notation. This helps in preventing your state names from becoming unmanageable.
  4. 您将希望为确保您的解析发生的所有状态创建抽象父状态,这样如果有人刷新浏览器,您的异步解析仍然发生并且您的身份验证正常工作。您可以在状态上使用父属性来创建另一个状态,否则该状态将不会被命名/点表示法继承。这有助于防止您的州名变得无法管理。
  5. While you can inject whatever services you need into your resolve, you can't access the toState or toStateParams of the state it is trying to transition to. However, the $stateChangeStart event will happen before your resolve is resolved. So, you can copy toState and toStateParams from the event args to your $rootScope, and inject $rootScope into your resolve function. Now you can access the state and params it is trying to transition to.
  6. 虽然您可以将所需的任何服务注入到您的解析中,但您无法访问它尝试转换到的状态的toState或toStateParams。但是,$ stateChangeStart事件将在解析之前发生。因此,您可以将事件args中的toState和toStateParams复制到$ rootScope,并将$ rootScope注入到您的resolve函数中。现在您可以访问它尝试转换到的状态和参数。
  7. Once you have resolved your resource(s), you can use promises to do your authorization check, and if it fails, use $state.go() to send them to the login page, or do whatever you need to do. There is a caveat to that, of course.
  8. 一旦解决了资源,就可以使用promises进行授权检查,如果失败,可以使用$ state.go()将它们发送到登录页面,或者做任何你需要做的事情。当然,有一点需要注意。
  9. Once resolve is done in the parent state, ui-router won't resolve it again. That means your security check won't occur! Argh! The solution to this is to have a two-part check. Once in resolve as we've already discussed. The second time is in the $stateChangeStart event. The key here is to check and see if the resource(s) are resolved. If they are, do the same security check you did in resolve but in the event. if the resource(s) are not resolved, then the check in resolve will pick it up. To pull this off, you need to manage your resources within a service so you can appropriately manage state.
  10. 一旦在父状态下完成解析,ui-router将不再解析它。这意味着您的安全检查不会发生!哎呀!解决方案是进行两部分检查。一旦解决,我们已经讨论过了。第二次是在$ stateChangeStart事件中。这里的关键是检查并查看资源是否已解决。如果是,请执行相同的安全检查,但是在事件中。如果资源未得到解决,那么check in resolve将会解析它。要实现此目的,您需要在服务中管理资源,以便适当地管理状态。

Some other misc. notes:

其他一些misc。笔记:

  • Don't bother trying to cram all of the authz logic into $stateChangeStart. While you can prevent the event and do your async resolution (which effectively stops the change until you are ready), and then try and resume the state change in your promise success handler, there are some issues preventing that from working properly.
  • 不要试图将所有authz逻辑塞入$ stateChangeStart。虽然您可以阻止事件并执行异步解析(在您准备好之前有效地停止更改),然后尝试在promise承诺成功处理程序中恢复状态更改,但是存在一些阻止其正常工作的问题。
  • You can't change states in the current state's onEnter method.
  • 您无法更改当前状态的onEnter方法中的状态。

This plunk is a working example.

这个插件是一个工作的例子。

#2


1  

We hit a similar issue. We felt we needed to make the logic which makes the HTTP call accessible to the logic that handles the response. They're separate in the code, so a service is a good way to do this.

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

We resolved that separation by wrapping the $http.get call in a service which caches the response and calls the success callback immediately if the cache is already populated. E.g.

我们通过将$ 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);
                });
            }
        }
    };
}]);

This acts as a guard around the request being successful. You could then call authorizationService.get() within your $stateChangeStart handler safely.

这可以防止请求成功。然后,您可以安全地在$ stateChangeStart处理程序中调用authorizationService.get()。

This approach is vulnerable to a race condition if a request is in already progress when authorizationService.get() is called. It might be possible to introduce some XHR bookkeeping to prevent that.

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

Alternatively you could publish a custom event once the HTTP request has completed and register a subscriber for that event within the $stateChangeStart handler. You would need to unregister that handler later though, perhaps in a $stateChangeEnd handler.

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

None of this can complete until the authorisation is done, so you should inform the user that they need to wait by showing a loading view.

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

Also, there's some interesting discussion of authentication with ui-router in How to Filter Routes? on the AngularJS Google Group.

另外,在如何过滤路由的过程中,有一些关于ui-router认证的有趣讨论?在AngularJS Google Group上。

#3


0  

I think a cleaner way to handle this situation is t$urlRouterProvider. deferIntercept() together with $urlRouter.listen() in order to stop uiRouter until some data is retrieved from server.

我认为处理这种情况的一种更简洁的方法是t $ urlRouterProvider。 deferIntercept()与$ urlRouter.listen()一起使用,以便在从服务器检索到某些数据之前停止uiRouter。

See this answer and docs for more information.

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