【问题标题】:Displaying different content within a single view based on the user's role根据用户的角色在单个视图中显示不同的内容
【发布时间】:2018-05-22 12:04:53
【问题描述】:

假设我们在我的 Angular SPA 应用程序中有一个菜单,现在我希望向所有用户显示基本选项,例如家庭、关于我们、运营商机会等。

我还希望有几个其他选项,例如管理用户、管理帖子等,这些选项只会显示给管理员。

我们还假设我们有一个 API 访问点为我提供用户角色,或者更好的是,用户角色位于从 /api/users/me 检索的对象中。

封装这些管理工具不被普通用户查看的最佳方式是什么?

视图之间是否存在某种继承?就像在 Django 中一样?,有没有办法向未经授权的用户隐藏 DOM 元素?(是的,我知道它是客户端)。

我真的不想为菜单使用不同的视图,因为它应该是一个通用组件。

我想如果我之前所有问题的答案都是否定的,那么剩下的问题是:什么是最好的实现?自定义指令(“E”+“A”) 说:

<limitedAccss admin>Edit page</limitedAccess>
 <limitedAccss user>view page</limitedAccess>

或者只是使用带有用户对象条件的常规 ng-show?

【问题讨论】:

  • 如果您要走 ng-show 之路,请考虑改用 ng-if。不同之处在于 ng-if 删除 DOM 元素,而 ng-show / hide 只是切换“显示:无”样式。不过,Ng-if 仅在 1.1.5 中可用。
  • 按照我的建议使用自定义指令然后删除 $compile 函数中不需要的标记怎么样?
  • 抱歉,我还不太熟悉 $compile 函数来对此发表评论。也希望看到更多的答案。 :)
  • 一个不错的选择是使用 ng-include 指令来加载只有管理员可以访问的特权模板。你可以在 ng-include 的 src 属性中有一个范围变量,它最初是一个空字符串。现在,一旦您知道角色,您就可以使用模板的路径更新变量。这将为您动态加载模板。
  • 如果我有一个不同角色的 SPA,比如说一个带有 cmets 的线程,用户可以在其中发表和评论,版主和管理员也可以删除和编辑其他人的帖子?对于这些用户,我只需要 2 个额外的按钮,而不是整个模板

标签: angularjs angularjs-directive


【解决方案1】:

解决方案在这个小提琴中:

http://jsfiddle.net/BmQuY/3/

var app = angular.module('myApp', []);

app.service('authService', function(){

  var user = {};
  user.role = 'guest';
  return{
    getUser: function(){
      return user;
    },
    generateRoleData: function(){
      /* this is resolved before the 
         router loads the view and model.
         It needs to return a promise. */
      /* ... */
    }
  }
});

app.directive('restrict', function(authService){
    return{
        restrict: 'A',
        priority: 100000,
        scope: false,
        compile:  function(element, attr, linker){
            var accessDenied = true;
            var user = authService.getUser();

            var attributes = attr.access.split(" ");
            for(var i in attributes){
                if(user.role == attributes[i]){
                    accessDenied = false;
                }
            }


            if(accessDenied){
                element.children().remove();
                element.remove();           
            }


            return function linkFn() {
                /* Optional */
            }
        }
    }
});

如果你想在 IE 7 或 8 中使用这个指令,你需要手动移除元素的子元素,否则会抛出错误:

  angular.forEach(element.children(), function(elm){
    try{
      elm.remove();
    }
    catch(ignore){}
  });

可能的用法示例:

<div data-restrict access='superuser admin moderator'><a href='#'>Administrative options</a></div>

使用 Karma + Jasmine 进行单元测试: 注意:done回调函数仅适用于Jasmine 2.0,如果您使用的是1.3,请改用waitsFor

  describe('restrict-remove', function(){
    var scope, compile, html, elem, authService, timeout;
    html = '<span data-restrict data-access="admin recruiter scouter"></span>';
    beforeEach(function(){
      module('myApp.directives');
      module('myApp.services');
      inject(function($compile, $rootScope, $injector){
        authService = $injector.get('authService');
        authService.setRole('guest');
        scope = $rootScope.$new();
        // compile = $compile;
        timeout = $injector.get('$timeout');
        elem = $compile(html)(scope);
        elem.scope().$apply();
      });
    });
    it('should allow basic role-based content discretion', function(done){
        timeout(function(){
          expect(elem).toBeUndefined(); 
          done(); //might need a longer timeout;
        }, 0);
    });
  });
  describe('restrict-keep', function(){
    var scope, compile, html, elem, authService, timeout;
    html = '<span data-restrict data-access="admin recruiter">';
    beforeEach(function(){
      module('myApp.directives');
      module('myApp.services');
      inject(function($compile, $rootScope, $injector){
        authService = $injector.get('authService');
        timeout = $injector.get('$timeout');
        authService.setRole('admin');
        scope = $rootScope.$new();
        elem = $compile(html)(scope);
        elem.scope().$apply();
      });
    });

    it('should allow users with sufficient priviledsges to view role-restricted content', function(done){
      timeout(function(){
        expect(elem).toBeDefined();
        expect(elem.length).toEqual(1);
        done(); //might need a longer timeout;
      }, 0)
    })
  });

元素的通用访问控制指令,不使用 ng-if(仅从 V1.2 开始 - 目前不稳定)或 ng-show,它实际上不会从 DOM 中删除元素。

【讨论】:

  • 我也有这个指令的单元测试,我马上贴出来
  • 发布了单元测试,如果还有什么我可以帮助你的,请告诉我:)
  • @OlegTikhonov 嗯...我有一个类似的实现,但在我的情况下,expect(element).toBeUndefined() 失败,因为它实际上是定义的。是不是在你的情况下测试通过只是因为你周围有一个timeout(..),谁的函数永远不会被调用??
  • 不幸的是,这个 hack 不适用于较新版本的 Jasmine,您需要定义一个异步测试,将 done 参数传递给测试用例,然后在超时函数中调用它.我将发布一个示例。也许还需要更长的超时时间。
  • @OlegTikhonov 但它真的运行了吗?因为当我执行它时我没有得到一个未定义的元素,而是它总是被定义的,因为你将它存储在 elem 变量中。 element.remove() 仅将其从某些父元素中删除,在这种情况下不存在。相反,我不得不将它包装在html 中,类似于'&lt;div&gt;&lt;span data-restrict data-access="admin recruiter scouter"&gt;&lt;/span&gt;&lt;/div&gt;'。然后在期望中检查span 是否存在。我在这里错过了什么吗?
【解决方案2】:

ng-if绝对是我会做的!只需将审核工具放在它们所属的整个视图中,如果用户应该拥有它们,它们就会出现。 ng-show/ng-hide 也可以,如果您使用的是 1.1.5 之前的 Angular 版本。

Live demo! (click here)

确保您的后端/服务器/api 不会仅仅因为您的 js 发出要求主持人操作的调用而接受请求,这一点非常重要!始终让服务器在每次调用时验证他们的授权。

【讨论】:

  • 如果角色是一个有很多角色的(数组)对象怎么办?像角色 = {'Admin', 'Members', 'Editor'}
  • @DavidKEgghead 看起来像是 OP 想到了这一点并做出了处理它的指令。我在想ng-if="userIs('admin moderator contributor')
  • 这种方法没有任何好处,因为它可以通过取消选中display: none; 属性来轻松操作。然后该按钮将可见并被单击。这在任何情况下都需要服务器端验证,尽管无论如何都应该这样做。我会选择@OlegTikhonov 的方法,因为这将删除当前用户角色不应该看到的所有元素。
  • @LordTribual 我建议ng-if,因此您将无法取消隐藏该元素。它根本不会在 DOM 中。 Oleg 的回答中的指令应该按照我的建议使用ng-if,而不是直接使用.remove() 手动操作DOM。如果使用未实现 ng-if 的 Angular 版本,您可以对其进行填充或仅使用 ng-show。如果元素只是隐藏而不是不存在,则无需担心。如果您的服务器不安全,那么任何知道打开控制台并显示元素的人也可以直接向服务器发送错误请求。
【解决方案3】:

ng-if 或 ng-hide 都可以,

html只是一个视图,不应该负责处理安全,你的安全

拥有与用户关联的权限对象可能很有用。 当我检索用户数据时,我获得了一个权限 json,然后在角度上

<div ng-if="user.permissions.restrictedArea">Very restricted area</div>

而用户 Json 看起来像这样:

{name:"some one", permissions:{restrictedArea:false,sandbox:true}} //and so on...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-29
    • 2019-06-07
    • 2014-12-20
    • 1970-01-01
    • 2013-07-04
    • 1970-01-01
    相关资源
    最近更新 更多