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

如何在我的webmvc应用程序中实现访问控制列表?

  •  93
  • Kirzilla  · 技术社区  · 14 年前

    第一个问题

    请你解释一下在MVC中最简单的ACL是如何实现的。

    下面是在控制器中使用Acl的第一种方法。。。

    <?php
    class MyController extends Controller {
    
      public function myMethod() {        
        //It is just abstract code
        $acl = new Acl();
        $acl->setController('MyController');
        $acl->setMethod('myMethod');
        $acl->getRole();
        if (!$acl->allowed()) die("You're not allowed to do it!");
        ...    
      }
    
    }
    ?>
    

    下一个方法是使所有控制器的方法 private 并将ACL代码添加到控制器的 __call 方法。

    <?php
    class MyController extends Controller {
    
      private function myMethod() {
        ...
      }
    
      public function __call($name, $params) {
        //It is just abstract code
        $acl = new Acl();
        $acl->setController(__CLASS__);
        $acl->setMethod($name);
        $acl->getRole();
        if (!$acl->allowed()) die("You're not allowed to do it!");
        ...   
      }
    
    }
    ?>
    

    它比以前的代码好,但主要缺点是。。。

    • 我们必须在每个控制器的调用方法中添加ACL代码。

    下一种方法是将Acl代码放入父控制器,但是我们仍然需要保持所有子控制器的方法私有。

    解决办法是什么?最佳实践是什么? 我应该在哪里调用Acl函数来决定是否允许执行方法。

    第二个问题是关于使用Acl获取角色。假设我们有客人、用户和用户的朋友。用户只能查看其个人资料,只有朋友才能查看。所有来宾都无法查看此用户的配置文件。所以,逻辑是这样的。。

    • 我们必须确保被调用的方法是profile
    • 我们必须检测这个档案的所有者
    • 我们必须阅读有关此配置文件的限制规则
    • 我们必须决定是否执行profile方法

    我希望我的想法是清楚的。对不起我的英语。

    非常感谢。

    3 回复  |  直到 10 年前
        1
  •  186
  •   tereÅ¡ko    12 年前

    在我看来,最好的方法是使用 decorator pattern ,基本上,这意味着你拿着你的物体 里面 另一个物体,就像一个保护壳。这不需要您扩展原始类。举个例子:

    class SecureContainer
    {
    
        protected $target = null;
        protected $acl = null;
    
        public function __construct( $target, $acl )
        {
            $this->target = $target;
            $this->acl = $acl;
        }
    
        public function __call( $method, $arguments )
        {
            if ( 
                 method_exists( $this->target, $method )
              && $this->acl->isAllowed( get_class($this->target), $method )
            ){
                return call_user_func_array( 
                    array( $this->target, $method ),
                    $arguments
                );
            }
        }
    
    }
    

    // assuming that you have two objects already: $currentUser and $controller
    $acl = new AccessControlList( $currentUser );
    
    $controller = new SecureContainer( $controller, $acl );
    // you can execute all the methods you had in previous controller 
    // only now they will be checked against ACL
    $controller->actionIndex();
    

    您可能注意到,此解决方案有几个优点:

    1. Controller
    2. 检查授权是否发生在目标对象之外,这意味着:
      • 原始对象不负责访问控制,遵守 SRP
    3. 你可以注射这个 安全实例 在任何其他物体上,它将保留保护
    4. 假装 如果它是原始对象,它会做出相同的反应

    ,这个方法也有一个主要问题-您不能以本机方式检查安全对象是否实现和接口(这也适用于查找现有方法)或者是某个继承链的一部分。

    第二部分/答案(对象的RBAC)

    在这种情况下,您应该认识到的主要区别是 Profile

    $this->acl->isAllowed( get_class($this->target), $method )
    

    基本上你有两个选择:

    • 为ACL提供有问题的对象。但是你必须小心不要违反 Law of Demeter :

    • 请求所有相关的细节,并仅提供ACL所需的内容,这也将使其更易于单元测试:

      $command = array( get_class($this->target), $method );
      /* -- snip -- */
      $this->acl->isAllowed( $this->target->getPermissions(), $command )
      

    两个视频可能会帮助您提出自己的实现:

    您似乎对MVC中的模型有着非常普遍(而且完全错误)的理解。 模型不是类 . 如果你有一个叫 FooBarModel AbstractModel 那你就做错了。

    域业务逻辑

    ( 阅读更多 here here ):

    这组类中的实例处理值的计算、检查不同的条件、实现销售规则以及完成所有其他您称之为“业务逻辑”的工作。他们不知道数据是如何存储的,存储在哪里,甚至不知道存储是否首先存在。

    数据存取和存储

    Data Mapper 模式(不要与同名的ORMs混淆。。没有关系)。这就是SQL语句的位置(或者DomDocument,因为它存储在XML中)。

    除了两个主要部分之外,还有一组实例/类,应该提到:

    服务

    这就是您和第三方组件发挥作用的地方。例如,您可以将“身份验证”视为服务,它可以由您自己提供,也可以由一些外部代码提供。“邮件发送者”也是一种服务,它可以将一些域对象与PHPMailer或SwiftMailer或您自己的邮件发送者组件组合在一起。

    另一个来源 services 是对域和数据访问层的抽象。创建它们是为了简化控制器使用的代码。例如:创建新用户帐户可能需要与多个 域对象 映射器 . 但是,通过使用服务,它只需要控制器中的一条或两条线。

    在创建服务时,您必须记住的是,整个层应该是 . 服务中没有业务逻辑。它们只用于处理域对象、组件和映射器。

    它们的一个共同点是,服务不会以任何直接的方式影响视图层,并且在某种程度上是自治的,因此它们可以(并且经常)在MVC结构本身之外使用。此外,由于服务和应用程序其余部分之间的耦合性极低,这种自我维持的结构使得迁移到不同的框架/体系结构变得更加容易。

        2
  •  16
  •   Community THelper    7 年前

    首先:这些通常是不同的事物/层次。当您批评示例性控制器代码时,它将两者结合在一起—最明显的是过于紧密。

    tereško already outlined

    我会先退一步,找出你面临的最初问题,然后再讨论一下。

    另一方面,您希望能够在应用程序中放置ACL。这些ACL的工作领域应该是——如果我理解你的问题的话——控制对应用程序某些命令的访问。

    因此,这种访问控制需要将这两者结合在一起的其他东西。根据执行命令的上下文,ACL启动,需要决定特定的主体(例如用户)是否可以执行特定的命令。

    • 命令
    • 国际计算语言学协会

    ACL组件在这里是中心的:它至少需要知道一些关于命令的信息(准确地说是识别命令),并且需要能够识别用户。用户通常很容易通过一个唯一的ID来识别。但是在Web应用程序中,通常有一些用户根本不被识别,通常称为guest、anonymous、everybody等。。对于本例,我们假设ACL可以使用一个用户对象并封装这些细节。用户对象绑定到应用程序请求对象,ACL可以使用它。

    如何识别命令?您对MVC模式的解释表明,命令是类名和方法名的复合。如果我们仔细看,甚至还有一个命令的参数。因此,问什么确切地标识一个命令是有效的?类名,方法名,参数的个数或名称,甚至参数中的数据,还是所有这些的混合?

    因此,这三个部分(ACL、Command和User)是如何相互归属的上下文现在变得更加清楚了。

    我们可以说,使用一个虚构的ACL组件,我们已经可以执行以下操作:

    $acl->commandAllowedForUser($command, $user);
    

    看看这里发生了什么:通过使命令和用户都可识别,ACL可以完成它的工作。ACL的作业与用户对象和具体命令的工作无关。

    只有一个部分不见了,这不能在空气中生存。但事实并非如此。所以你需要找到访问控制需要启动的地方。让我们看看标准Web应用程序中会发生什么:

    User -> Browser -> Request (HTTP)
       -> Request (Command) -> Action (Command) -> Response (Command) 
       -> Response(HTTP) -> Browser -> User
    

    要定位该位置,我们知道它必须在具体命令执行之前,因此我们可以减少该列表,只需要查看以下(潜在)位置:

    User -> Browser -> Request (HTTP)
       -> Request (Command)
    

    在应用程序的某个时刻,您知道某个特定的用户请求执行一个具体的命令。您已经在这里执行了某种ACL'ing:如果用户请求的命令不存在,则不允许执行该命令。因此,应用程序中发生的任何情况都可能是添加“真正的”ACL检查的好地方:

    命令已经被定位,我们可以创建它的标识,这样ACL就可以处理它了。如果用户不允许该命令,则不会执行该命令(操作)。也许是一个 CommandNotAllowedResponse 而不是 CommandNotFoundResponse

    将具体HTTPRequest的映射映射到命令的位置通常称为 路由 路由 已经有了定位命令的作业,为什么不扩展它来检查每个ACL是否允许该命令呢?例如,通过延长 Router 到ACL感知路由器: RouterACL . 如果你的路由器还不知道 User 路由器 不是正确的位置,因为要使ACL'ing工作,不仅必须标识命令,而且还必须标识用户。因此,这个地方可能会有所不同,但我相信您可以很容易地找到需要扩展的地方,因为它满足了用户和命令的需求:

    用户->浏览器->请求(HTTP)
    

    用户从一开始就可用,先用命令 Request(Command) .

    所以不要把你的ACL检查放进去 命令的具体实现,你把它放在它前面。你不需要任何繁重的模式,魔法或其他什么,ACL做它的工作,用户做它的工作,尤其是命令做它的工作:只是命令,没有别的。命令没有兴趣知道角色是否应用于它,它是否在某个地方受到保护。

    单一责任原则(SRP) :更改命令的原因只有一个-因为命令已更改。不是因为您现在在应用程序中引入了ACL'ing。不是因为你切换了用户对象。不是因为您从HTTP/HTML接口迁移到SOAP或命令行接口。

    本例中的ACL控制对命令的访问,而不是命令本身。

        3
  •  13
  •   Artefacto    14 年前

    一种可能是将所有控制器封装在另一个扩展控制器的类中,并让它在检查授权后将所有函数调用委托给封装的实例。

    您还可以在调度程序(如果您的应用程序确实有一个)的上游执行更多操作,并基于url而不是控制方法查找权限。

    编辑 :是否需要访问数据库、LDAP服务器等与问题正交。我的观点是,您可以实现基于url的授权,而不是基于控制器方法。这些功能更强大,因为您通常不会更改URL(类似于公共接口的URL区域),但您也可以更改控制器的实现。

    通常,您有一个或多个配置文件,可以在其中将特定的URL模式映射到特定的身份验证方法和授权指令。调度器在将请求分派给控制器之前,确定用户是否被授权,如果用户没有被授权,则中止分派。