【问题标题】:Angular route async authorization角度路由异步授权
【发布时间】:2015-10-08 12:50:01
【问题描述】:

Angular 不提供任何关于路由的授权/访问权限(我说的是默认的 Angular 路由 1.x,而不是 beta 2.0 或 UI 路由)。但我必须实现它。

我遇到的问题是我有一个服务调用服务器来提供此信息并返回一个承诺。但是这个数据只获取一次,然后缓存在客户端,但还是需要获取一次。

我现在想处理$routeChangeStart 事件,该事件检查下一条路由是否定义了特定属性authorize: someRole。然后,此处理程序应使用我之前提到的服务获取该数据,并根据返回的数据采取相应措施。

除了将 resolves 添加到我的所有路线之外还有什么想法吗?我可以以某种方式集中执行此操作吗?适用于所有路线一次?

最终解决方案

在接受的答案的帮助下,我能够实现一个相当简单且集中的解决方案来执行异步授权。 Click here 以查看它的运行情况或检查其内部工作代码。

【问题讨论】:

    标签: angularjs routing authorization


    【解决方案1】:

    最简单的方法是处理当前路由的解析依赖,$routeChangeStart 是管理这个的好地方。 Here's an example.

    app.run(function ($rootScope, $location) {
      var unrestricted = ['', '/login'];
    
      $rootScope.$on('$routeChangeStart', function (e, to) {
        if (unrestricted.indexOf(to.originalPath) >= 0)
          return;
    
        to.resolve = to.resolve || {};
        // can be overridden by route definition
        to.resolve.auth = to.resolve.auth || 'authService';
      });
    
      $rootScope.$on('$routeChangeError', function (e, to, from, reason) {
        if (reason.noAuth) {
          // 'to' path and params should be passed to login as well
          $location.path('/login');
        }
      });
    });
    

    另一种选择是将default 方法添加到$routeProvider 并修补$routeProvider.when 以从默认对象扩展路由定义。

    【讨论】:

    • 有时我会打补丁,但在这种情况下我不会这样做,因为我每次都会更新这个库。与其他技术相比,这可能会引发更多问题。但是您的解决方案可能是正确的方向。因此,您添加了对受限路线的解析。伟大的。剩下的一件事是还要处理$routeChangeSuccess,您将在其中检查授权已解决的承诺,并在授权失败时使用$location.path() 重定向到登录。我不确定是否可以在此路由点更改位置,但值得一试,对吗?你能提供一个可行的例子吗?
    • 目前事情并没有那么复杂。您必须在 $routeChangeError 中检查解析器拒绝原因。我添加了一个 plunker。
    • 修补现有对象以改进它们的功能是一种光荣和成熟的做法,许多 3 星和 4 星 ng 扩展都是这样做的。它带有责任,测试补丁的规范是必不可少的,因为补丁可能会被破坏。考虑到我提到的补丁, $routeProvider.when 补丁在正确完成时会导致零问题。它只是捕获原始“何时”返回的对象并将“默认”对象与其合并。始终坚持'.apply(this,arguments)'。
    • 基本上,我发现您的想法是使用 $routeChangeStart$routeChangeError 更好、更可靠的方法。它非常优雅,不会改变 Angular 的任何内在部分(无需修补)。基本上我们可以考虑这个 normal 实现 vs hack
    • 是的,这就是 ngRoute 期望的解决方法。它以目前的形式看起来很整洁。但是我发现事件丰富(也与路由相关)的代码往往是混乱且不可维护的,我认为这是一个问题。
    【解决方案2】:

    ui-router 有很多可以轻松操作的事件。我总是用它。

    State Change Events 拥有您需要的一切。这样的东西将在 AngularJS 2.x 中实现。

    但是,如果您正在寻找原生 Angular 1.x.y 路由器的解决方案,这个解决方案对您没有帮助。对不起

    【讨论】:

    • 但是 afaik $stateChangeStart 与 Angular 的路由器 $routeChangeStart 并没有太大区别。您可以在其中一个中取消路由事件,但我不认为 ui-router 的事件可以在其处理程序中处理异步承诺...您能否提供一个示例如何使用 ui-router 执行此操作?
    • $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){ event.preventDefault(); /*here I can do something with my promise and then use toState variable for $state.go function as a parameter I would use some spinner for this, too*/ })
    • 你可以编辑你的答案,你知道...通常这样做是一个更好的选择。并且对于其他人在未来得到您的答案也更好,因为他们不必阅读 cmets(希望不会崩溃)。
    • 但是除了你在评论中的代码我敢你做一个简单的jsfiddle来证明你的观点。因为当你到达这里我可以用我的承诺做某事我敢打赌你会面临真正的问题。
    【解决方案3】:

    如果你可以使用 ui-router,你可以这样做:

    .state('root', {
          url: '',
          abstract: true,
          templateUrl: 'some-template.html',
          resolve: {
            user: ['Auth', function (Auth) {
              return Auth.resolveUser();
            }]
          }
        })
    

    Auth.resolveUser() 只是一个加载当前用户的后端调用。它返回一个 Promise,因此路由会在更改之前等待它加载。

    路由是抽象的,因此其他控制器必须从它继承才能工作,并且您还有一个额外的好处,即所有子控制器都可以通过解析访问当前用户。

    现在您在app.run() 中捕获$stateChangeStart 事件:

    $rootScope.$on('$stateChangeStart', function (event, next) {
            if (!Auth.signedIn()) { //this operates on the already loaded user
              //user not authenticated
              // all controllers need authentication unless otherwise specified
              if (!next.data || !next.data.anonymous) {
                event.preventDefault();
                $state.go('account.login');
              }
            }else{
             //authenticated 
             // next.data.roles - this is a custom property on a route.
             if(!Auth.hasRequiredRole(next.data.roles)){
                event.preventDefault();
                $state.go('account.my'); //or whatever
             }             
            }
          });
    

    那么需要角色的路由可以是这样的:

    .state('root.dashboard', {
             //it's a child of the *root* route
              url: '/dashboard',
              data: {
                 roles: ['manager', 'admin']
              }
              ...
            });
    

    希望它有意义。

    【讨论】:

    • 确实如此。因此,普通与 ui-router 实现之间的唯一(也是主要区别)区别是抽象路由。其他一切都差不多。我说的对吗?
    • 是的,差不多。您是否考虑过不进行异步路由?并在呈现初始 html 时从服务器开始加载用户?然后你已经加载了你的用户/权限等 - 不需要额外的请求(如果你有后端服务器)
    【解决方案4】:

    我已经多次处理这个问题,我还开发了一个模块 (github)。 我的模块(建立在ui.router 之上)基于$stateChangeStart(ui.router 事件),但概念与默认的ng.route 模块相同,只是实现方式不同。

    总之,我认为处理路由更改事件并不是执行身份验证检查的好方法: 例如,当我们需要通过 ajax 获取 acl 时,事件就帮不上忙了。

    我认为,一个好方法可能是自动将解析附加到每个“受保护”状态...

    不幸的是 ui.Router 没有提供 API 来拦截状态创建,所以我开始我的模块返工,在 $stateProvider.state 方法之上使用了一些解决方法。

    确实,我正在寻找不同的意见,以便找到在 AngularJS 中实现身份验证服务的正确方法。

    如果有人对这项研究感兴趣...请在我的 github 上打开一个问题并进行讨论

    【讨论】:

    • 我认为您可以使用 ui-router 非常相似地实现它,就像我在 this example 中使用 ngRoute 接受的答案的帮助下所做的那样。虽然 ui-router 有一个额外的 gem 形式为urlRouter.sync(),它会在停止后继续路由。
    猜你喜欢
    • 2020-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-07
    • 1970-01-01
    • 2017-05-03
    • 1970-01-01
    相关资源
    最近更新 更多