一尘不染

如何在Web MVC应用程序中实现访问控制列表?

php

第一个问题

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

这是在Controller中使用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!");
    ...    
  }

}
?>

这是非常糟糕的方法,它的缺点是我们必须将Acl代码添加到每个控制器的方法中,但是我们不需要任何其他依赖项!

下一种方法是制作所有控制器的方法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代码添加到每个控制器的__call方法中。

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

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

第二个问题

第二个问题是关于使用Acl的角色。假设我们有客人,用户和用户的朋友。用户具有查看其个人资料的访问权限,只有朋友可以查看。所有访客都无法查看该用户的个人资料。所以,这是逻辑。

  • 我们必须确保被调用的方法是配置文件
  • 我们必须检测此个人资料的所有者
  • 我们必须检测出查看者是此配置文件的所有者,还是没有
  • 我们必须阅读有关此配置文件的限制规则
  • 我们必须决定执行或不执行配置文件方法

主要问题是关于检测配置文件的所有者。我们只能通过执行模型的方法$ model->
getOwner()来检测谁是配置文件的所有者,但是Acl没有访问模型的权限。我们如何实现呢?

我希望我的想法是明确的。对不起我的英语不好。

谢谢。


阅读 392

收藏
2020-05-26

共1个答案

一尘不染

第一部分/答案(ACL实施)

以我的拙见,解决此问题的最佳方法是使用装饰器模式,基本上,这意味着您要拿起您的对象并将其放置
另一个对象中,该对象将起到保护壳的作用。这将不需要您扩展原始类。这是一个例子:

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提供相关对象。但您必须注意不要违反Demeter法则

    $this->acl->isAllowed( get_class($this->target), $method )
    
  • 请求所有相关的详细信息,并仅向ACL提供所需的内容,这也将使其对单元测试更加友好:

    $command = array( get_class($this->target), $method );
    

    / – snip – /
    $this->acl->isAllowed( $this->target->getPermissions(), $command )

旁注

您似乎对MVC中的模型有相当普遍的理解(并且完全是错误的)。 模式不是一类
。如果您有命名的类FooBarModel或继承的类,AbstractModel那么您做错了。

在适当的MVC中,模型是一层,其中包含许多类。根据职责,大部分课程可以分为两组:

- 领域业务逻辑

这组类中的实例处理值的计算,检查不同的条件,实施销售规则并执行所有其余的事情,即所谓的“业务逻辑”。他们不知道数据的存储方式,存储位置或存储位置。

域业务对象不依赖于数据库。创建发票时,数据来自哪里都没有关系。它可以来自SQL或来自远程REST
API,甚至可以来自MSWord文档的屏幕截图。业务逻辑没有改变。

- 数据访问和存储

由这组类制成的实例有时称为数据访问对象。通常实现DataMapper模式的结构(不要与同名..无关联的ORM混淆)。这就是您的SQL语句所在的位置(或您的DomDocument,因为您将其存储在XML中)。

除了这两个主要部分,还有另外一组实例/类应该被提及:

- 服务

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

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

进行服务时,您必须记住的是整个层应该 很薄 。服务中没有业务逻辑。它们只是在杂耍领域对象,组件和映射器。

它们共有的一件事是,服务不会以任何直接方式影响View层,并且具有一定程度的自治性,因此可以在MVC结构本身之外使用(并且经常退出)它们。而且,由于服务和应用程序其余部分之间的耦合极低,因此这种自我维持的结构使向其他框架/体系结构的迁移更加容易。

2020-05-26