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

时间:2010-08-07 11:11:02

标签: php oop model-view-controller acl

第一个问题

拜托,您能解释一下在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!");
    ...   
  }

}
?>

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

  • 所有控制器的方法都应该是私有的
  • 我们必须在每个控制器的__call方法中添加ACL代码。

接下来的方法是将Acl代码放入父Controller中,但我们仍然需要将所有子控制器的方法保密。

解决方案是什么?什么是最佳做法? 我应该在哪里调用Acl函数来决定允许还是禁止执行方法。

第二个问题

第二个问题是关于使用Acl获取角色。让我们假设我们有客人,用户和用户的朋友。用户限制访问查看他的个人资料,只有朋友才能查看。所有客人都无法查看此用户的个人资料。所以,这是逻辑..

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

主要问题是检测个人资料的所有者。我们可以检测谁只是执行模型的方法$ model-&gt; getOwner()的配置文件的所有者,但是Acl无权访问模型。我们怎样才能实现这个目标?

我希望我的想法很明确。抱歉我的英文。

谢谢。

2 个答案:

答案 0 :(得分:180)

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

以我的拙见,处理此问题的最佳方法是使用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. 的实例
  3. 检查授权是否发生在目标对象之外,这意味着:
    • 原始对象不对访问控制负责,遵守SRP
    • 当您获得“权限被拒绝”时,您不会被锁定在控制器内,有更多选项
  4. 您可以在任何其他对象中注入此安全实例,它将保留保护
  5. 包装&amp;忘记它..你可以假装它是原始对象,它会反应相同
  6. ,此方法也存在一个主要问题 - 您无法本机检查安全对象是否实现和接口(这也适用于查找现有方法)或是某些继承链的一部分。

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

    在这种情况下,您应该认识到的主要区别是域对象(例如:Profile)本身包含有关所有者的详细信息。这意味着,您可以检查,如果(以及在哪个级别)用户有权访问它,则需要您更改此行:

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

    基本上你有两个选择:

    • 为ACL提供相关对象。但是你必须小心不要违反Law of 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中,Model是一个包含很多类的层。根据责任,大部分课程可分为两组:

    - 域名业务逻辑

    了解更多herehere):

    这组类中的实例处理值的计算,检查不同的条件,实现销售规则,并完成所有其他所谓的“业务逻辑”。他们不知道如何存储数据,存储数据的位置,甚至存储都存在于首位。

    域业务对象不依赖于数据库。在创建发票时,数据的来源无关紧要。它可以来自SQL,也可以来自远程REST API,甚至是MSWord文档的屏幕截图。业务逻辑没有变化。

    - 数据访问和存储

    从这组类中生成的实例有时称为数据访问对象。通常是实现Data Mapper模式的结构(不要与同名的ORM混淆..没有关系)。这是您的SQL语句的位置(或者您的DomDocument,因为您将其存储在XML中)。

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

    - 服务

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

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

    制作服务时你必须记住的是,整个图层应该是 thin 。服务中没有业务逻辑。它们仅用于处理域对象,组件和映射器。

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

答案 1 :(得分:13)

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

您也可以在调度程序中更多地进行上游(如果您的应用程序确实有一个)并根据URL而不是控制方法查找权限。

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

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