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

Kir*_*lla 94 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!");
    ...    
  }

}
?>
Run Code Online (Sandbox Code Playgroud)

这是一个非常糟糕的方法,它的缺点是我们必须将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!");
    ...   
  }

}
?>
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

第二个问题

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

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

主要问题是关于检测个人资料的所有者.我们可以检测谁只是执行模型的方法$ model-> getOwner()的配置文件的所有者,但是Acl无权访问模型.我们怎么能实现这个呢?

我希望我的想法很清楚.对不起我的英语不好.

谢谢.

ter*_*ško 183

第一部分/答案(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
            );
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

这将是你如何使用这种结构:

// 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();
Run Code Online (Sandbox Code Playgroud)

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

  1. 包含可用于任何对象,而不仅仅是实例 Controller
  2. 检查授权是否发生在目标对象之外,这意味着:
    • 原始对象不负责访问控制,遵守SRP
    • 当你得到"权限被拒绝"时,你没有被锁在控制器内,有更多选择
  3. 您可以将此安全实例注入任何其他对象,它将保留保护
  4. 包装它并忘记它...你可以假装它是原始物体,它会做出同样的反应

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

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

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

$this->acl->isAllowed( get_class($this->target), $method )
Run Code Online (Sandbox Code Playgroud)

基本上你有两个选择:

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

旁注

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

在适当的MVC中,Model是一个包含很多类的层.根据责任,大部分课程可分为两组:

- 域业务逻辑

(阅读更多:这里这里):

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

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

- 数据访问和存储

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

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

- 服务

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

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

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

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

  • 我只是在5分钟内重读了这篇文章,而不是几个月.您是否同意:瘦控制器调度到收集视图数据的服务?此外,如果您直接接受问题,请给我发消息. (33认同)
  • 使用简化的解释:控制器"写入"模型和视图,从模型中查看"读取".模型层是受MVC启发的所有Web相关模式中的被动结构. (4认同)
  • 我部分同意.当您初始化`Request`实例(或其某些模拟)时,视图中的数据集合发生在MVC三元组之外.控制器只从"请求"实例中提取数据,并将大部分数据传递给适当的服务(其中一些也可以查看).服务执行您命令他们执行的操作.然后,当视图生成响应时,它从服务请求数据,并根据该信息生成响应.所述响应可以是由多个模板制作的HTML,也可以是HTTP位置标题.取决于控制器设置的状态. (2认同)

hak*_*kre 16

ACL和控制器

首先:这些是最常见的不同的事物/层次.当你批评示例性控制器代码时,它将两者结合在一起 - 最明显的是太紧.

tereško已经概述了一种如何用装饰器模式将其解耦的方法.

我先退后一步,寻找你所面临的原始问题然后再讨论一下.

一方面,您希望控制器只执行他们所要求的工作(命令或操作,让我们称之为命令).

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

因此,这种访问控制需要将这两者结合在一起的其他东西.基于执行命令的上下文,ACL启动并且需要做出决定,无论特定命令是否可以由特定主题(例如,用户)执行.

让我们总结一下我们拥有的东西:

  • 命令
  • ACL
  • 用户

ACL组件在这里是核心:它至少需要知道关于命令的一些信息(以确定命令是否准确),并且它需要能够识别用户.用户通常可以通过唯一ID轻松识别.但往往在web应用中有未标识在所有在这个例子中,我们假设ACL会消耗用户对象和封装这些细节远的用户,通常被称为客,匿名的,大家等.用户对象绑定到应用程序请求对象,ACL可以使用它.

如何识别命令?您对MVC模式的解释表明命令是类名和方法名的复合.如果我们仔细观察,甚至有一个命令的参数(参数).那么问一下究竟是什么命令是有效的吗?类名,方法名,参数的数量或名称,甚至是任何参数中的数据或所有这些的混合?

根据您在ACL中识别命令所需的详细程度,这可能会有很大差异.对于该示例,让我们保持简单,并指定命令由类名和方法名标识.

因此,这三个部分(ACL,Command和User)如何相互依赖的上下文现在更加清晰.

我们可以说,假想的ACL组件我们已经可以做到以下几点:

$acl->commandAllowedForUser($command, $user);
Run Code Online (Sandbox Code Playgroud)

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

只有一部分缺失,这不能生活在空中.它没有.因此,您需要找到访问控制需要启动的位置.让我们来看看标准Web应用程序中发生的情况:

User -> Browser -> Request (HTTP)
   -> Request (Command) -> Action (Command) -> Response (Command) 
   -> Response(HTTP) -> Browser -> User
Run Code Online (Sandbox Code Playgroud)

要找到那个地方,我们知道它必须在具体命令执行之前,所以我们可以减少该列表,只需要查看以下(潜在)地点:

User -> Browser -> Request (HTTP)
   -> Request (Command)
Run Code Online (Sandbox Code Playgroud)

在应用程序中的某个时刻,您知道特定用户已请求执行具体命令.您已在此处执行某种ACL:如果用户请求不存在的命令,则不允许执行该命令.因此,在您的应用程序中发生的任何地方都可能是添加"真正的"ACL检查的好地方:

该命令已被定位,我们可以创建它的标识,以便ACL可以处理它.如果用户不允许该命令,则不执行该命令(动作).也许一个请求无法解决到一个具体的命令,CommandNotAllowedResponse而不是在CommandNotFoundResponse这种情况下.

将具体HTTPRequest的映射映射到命令的位置通常称为路由.由于路由已经有找到命令的工作,为什么不扩展它以检查每个ACL是否实际允许该命令?例如,通过扩展Router 到ACL识别路由器:RouterACL.如果您的路由器还不知道User,那么这Router不是正确的地方,因为ACL不仅要工作,而且必须识别用户.所以这个地方可能会有所不同,但我确信您可以轻松找到需要扩展的地方,因为它是满足用户和命令要求的地方:

User -> Browser -> Request (HTTP)
   -> Request (Command)
Run Code Online (Sandbox Code Playgroud)

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

因此,不要将ACL检查放在每个命令的具体实现中,而是将它放在它之前.你不需要任何沉重的模式,魔法或其他任何东西,ACL做它的工作,用户做它的工作,特别是命令完成它的工作:只是命令,没有别的.该命令没有兴趣知道角色是否适用于它,如果它被保护在某处.

所以只要保持彼此不属于的东西.使用单一责任原则(SRP)的略微重写:更改命令应该只有一个原因 - 因为命令已更改.不是因为你现在在你的应用程序中引入了ACL.不是因为你切换了User对象.不是因为您从HTTP/HTML接口迁移到SOAP或命令行界面.

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


Art*_*cto 13

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

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

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

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