代码之家  ›  专栏  ›  技术社区  ›  Alex

$state.go不遵守承诺

  •  4
  • Alex  · 技术社区  · 10 年前

    我正在尝试实施一项基本的安全检查,以阻止用户根据其权限集访问某些状态:

    'use strict';
    
    var autoExecApp = angular.module("myApp", ['ui.router']);
    
    autoExecApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider){
        $stateProvider
            .state('index', {
                url: '/index',
                templateUrl: 'partials/index.html'
            })
            .state('permission', {
                url: '/permission',
                templateUrl: 'partials/permissions.html'
            });
    }]);
    
    autoExecApp.run(['$rootScope', '$state', '$userRoles', function($rootScope, $state, $userRoles) {
    
        $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
    
            event.preventDefault(); // prevent page from being loaded anyway
            $userRoles.hasPermissions(toState.name).then(function(data) {
                var result = data === "true";
                if (!result) {
                    $state.go('permission');
                }
                else {
                    $state.go(toState.name, toParams ,{notify:false});
                }
            });
        });
    }]);
    

    现在,问题围绕着这样一个事实:我的状态变化发生在一个承诺之内。我很乐意让它成为同步的——在这种情况下它确实应该是同步的,但我很困惑它为什么不能异步工作。

    如果我移动 event.preventDefault() 在承诺中,它起作用。但是,状态会分两步改变。首先,它将始终转到原始页面,然后在转换到“被阻止”页面(如果被阻止)后很快就会返回。我认为这是因为它从$on成功返回一次并更新状态,然后在从promise返回时再次更新。

    然而,如果承诺是你的看法,那么国家不会改变。一切似乎都正常,但页面不会更新视图。我不知道为什么会这样。由于某种原因,如果事件未被阻止,稍后的状态更改将起作用。但是,如果阻止了它,并且从返回的promise调用了$state.go,那么更新$state似乎太晚了。

    编辑:我成功地使用了广播解决方案,这就是它的样子:

    event.preventDefault();
    $state.go(toState.name, toParams, {notify: false}).then(function() {
         // line 907 state.js
         $rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
    });
    

    显然,由于它是一个承诺,preventDefault();禁止此“$stateChangeSuccess”自然激发。手动执行它可以恢复预期的行为。

    3 回复  |  直到 10 年前
        1
  •  5
  •   Vlad Gurovich    10 年前

    这是一个众所周知的问题。 通常有效的解决方法是从“$stateChangeStart”处理程序中广播自定义事件,然后从该自定义事件的处理程序中进行状态转换。

    $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
    
        event.preventDefault(); // prevent page from being loaded anyway
        $userRoles.hasPermissions(toState.name).then(function(data) {
            var result = data === "true";
            if (!result) {
    
                $rootScope.$broadcast('goToPermission');
            }
            else {
                $rootScope.$broadcast('goToState', toState.name);
            }
        });
    });
    

    借用自: https://github.com/angular-ui/ui-router/issues/178#issuecomment-49156829

        2
  •  1
  •   Roamer-1888    10 年前

    我认为弗拉基米尔·古罗维奇的“广播”解决方案是可行的,特别是他表示这是一个既定的解决方案。

    但我想知道是否有一个缓存了权限的解决方案 同步 ,可能也是可行的。

    autoExecApp.run(['$rootScope', '$state', '$userRoles', function($rootScope, $state, $userRoles) {
        // Create a permissions cache, seeded with 
        // permission to go to the 'permission' state.
        var permissionCache = {
            'permission': true
        };
    
        // Create a researched list of all possible state names.
        var stateNames = ['stateA', 'stateB', 'stateC'];
    
        // Now load the cache asynchronously with 
        // `true` for each permission granted.
        stateNames.forEach(function(state) {
            $userRoles.hasPermissions(state).then(function(data) {
                if(data === "true") {
                    permissionCache[state] = true;
                }
            });
        });
    
        // And finally, establish a $stateChangeStart handler 
        // that looks up permissions **synchronously**, 
        // in the permissionCache.
        $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
            if (!permissionCache[toState.name]) {
                event.preventDefault(); // prevent page from being loaded
                $state.go('permission');
            }
        });
    }]);
    

    当然,这种解决方案也有其自身的问题:

    • 依赖于对所有可能的州名的良好研究。
    • 加载缓存之前尝试的任何状态更改都将被拒绝。
    • 在页面生命周期中,用户角色的任何更改都需要清除并重新加载缓存。

    如果没有研究/测试,我无法确定这些问题是否重要。

        3
  •  0
  •   pasine    10 年前

    为什么不使用解析功能来检查用户是否可以访问您的路线?
    类似于:

    autoExecApp.config(['$stateProvider', '$urlRouterProvider', function (
        $stateProvider, $urlRouterProvider) {
        $stateProvider
            .state('index', {
                url: '/index',
                templateUrl: 'partials/index.html'
            })
            .state('permission', {
                url: '/permission',
                templateUrl: 'partials/permissions.html'
            })
        .state('restricted-route', {
          url: '/restricted',
          templateUrl: 'partials/restricted.html',
          resolve: {
            authenticated: function ($q) {
              var deferred = $q.defer();
              $userRoles.hasPermissions(this)
                .then(function (data) {
                  var result = data === "true";
                  if (result) {
                    deferred.resolve();
                  } else {
                    deferred.reject('unauthorized');
                  }
                });
              return deferred.promise;
            }
          }
        });
    }]);
    
    $rootScope.$on('$stateChangeError',
      function (event, toState, toParams, fromState, fromParams, rejection) {
    
        if (rejection.error === 'unauthorized') {
          $state.go('permission');
        }
      }
    }