我如何在我的Web MVC应用程序中实现访问控制列表?
第一个问题
请你能解释一下在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!"); ... } } ?>
这是非常糟糕的做法,而且我们不得不将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代码放到父控制器中,但是我们仍然需要保持所有的子控制器的私有方法。
解决办法是什么? 什么是最佳做法? 我应该在哪里调用Acl函数来决定是否允许执行方法。
第二个问题
第二个问题是关于使用Acl获取angular色。 假设我们有客人,用户和用户的朋友。 用户限制访问查看他的个人资料,只有朋友可以查看它。 所有访客都无法查看此用户的个人资料。 所以,这是逻辑..
- 我们必须确保被调用的方法是profile
- 我们必须检测这个configuration文件的所有者
- 我们必须检测查看器是这个configuration文件的所有者或不是
- 我们必须阅读有关此configuration文件的限制规则
- 我们必须决定执行还是不执行configuration文件方法
主要问题是关于检测configuration文件的所有者。 我们可以检测谁是configuration文件的所有者,只执行模型的方法$ model-> getOwner(),但是Acl没有访问模型的权限。 我们怎样才能实现呢?
我希望我的想法很清楚。 对不起我的英语不好。
谢谢。
第一部分/答案(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();
您可能会注意到,该解决scheme有几个优点:
- 遏制可用于任何对象,而不仅仅是
Controller
实例 - 检查授权发生在目标对象之外,这意味着:
- 原始对象不负责访问控制,坚持SRP
- 当你得到“权限被拒绝”时,你并没有被locking在一个控制器里面,更多的select
- 您可以将此安全实例注入任何其他对象,它将保留该保护
- 包裹它忘记它..你可以假装它是原始的对象,它会作出同样的反应
但是 ,这个方法也有一个主要问题 – 你不能在本地检查受保护对象的实现和接口(这也适用于查找现有的方法),或者是一些inheritance链的一部分。
第二部分/答案(对象的RBAC)
在这种情况下,您应该认识到的主要区别是,您的域对象 (例如: Profile
)本身包含有关所有者的详细信息。 这意味着,为了检查,如果(以及在哪个级别)用户有权访问它,则需要您更改以下行:
$this->acl->isAllowed( get_class($this->target), $method )
基本上你有两个select:
-
为ACL提供有关的对象。 但是你要小心,不要违犯得墨忒耳法 :
$this->acl->isAllowed( get_class($this->target), $method )
-
请求所有相关的细节,并只提供它所需要的ACL,这也使得它更友好一些:
$command = array( get_class($this->target), $method ); /* -- snip -- */ $this->acl->isAllowed( $this->target->getPermissions(), $command )
几个video,可能会帮助你拿出你自己的实现:
- 遗传,多态性和testing
- 不要找东西!
旁注
您似乎对MVC中的模型有相当普遍的(完全错误的)理解。 模型不是一个类 。 如果你有名为FooBarModel
类或者inheritance了AbstractModel
那么你做错了。
在适当的MVC模型是一个层,其中包含了很多类。 大部分class级可以分成两组,分别负责:
– 领域业务逻辑
( 阅读更多 : 这里和这里 ):
来自这组类的实例处理值的计算,检查不同的条件,执行销售规则,并完成所有其余的你称之为“业务逻辑”。 他们不知道如何存储数据,存储数据的位置或存储位置。
域业务对象不依赖于数据库。 在创build发票时,数据来自哪里并不重要。 它可以来自SQL或远程REST API,甚至可以是MSWord文档的屏幕截图。 业务逻辑没有改变。
– 数据访问和存储
从这组类中创build的实例有时称为数据访问对象。 通常是实现Data Mapper模式的结构(不要和同名的ORM混淆。 这就是你的SQL语句的位置(或者你的DomDocument,因为你把它存储在XML中)。
除了两个主要部分之外,还有一组实例/类,应该提到:
– 服务
这是你和第三方组件进场的地方。 例如,您可以将“身份validation”视为服务,可以由您自己提供,也可以使用一些外部代码。 此外,“邮件发件人”将是一个服务,它可能与PHPMailer或SwiftMailer或您自己的邮件发件人组件组成一个域对象。
服务的另一个来源是对域和数据访问层的抽象。 它们是为了简化控制器使用的代码而创build的。 例如:创build新的用户帐户可能需要使用多个域对象和映射器 。 但是,通过使用服务,控制器中只需要一两行。
在提供服务时你必须记住的是,整个图层应该是薄的 。 服务中没有业务逻辑。 他们只是在那里玩弄域对象,组件和映射器。
其中一个共同点就是服务不会以任何直接的方式影响View层,并且自主到这样的程度,他们可以(经常退出)在MVC结构本身之外使用。 此外,这种自我维持的结构使得迁移到不同的框架/体系结构变得容易得多,因为服务与其他应用程序之间的耦合度非常低。
ACL和控制器
首先:这些是最经常不同的事情/层次。 当你批评示例性的控制器代码时,它将两者放在一起 – 显然太严格了。
tereško已经概述了一种方式,你可以解耦这个更多的装饰模式。
我会先退后一步,寻找你面临的原始问题,然后讨论一下。
一方面,你想要控制器只是做他们被命令的工作(命令或行动,让我们称之为命令)。
另一方面,您希望能够将ACL应用于您的应用程序中。 这些ACL的工作领域应该是 – 如果我理解你的问题 – 控制访问你的应用程序的某些命令。
因此,这种访问控制需要其他的东西来将这两者结合在一起。 基于执行命令的上下文,ACL启动并决定是否需要执行特定命令(例如用户)的特定命令。
让我们总结一下,我们有什么:
- 命令
- ACL
- 用户
ACL组件在这里是至关重要的:它至less需要了解一些有关命令的内容(以确定命令的准确性),并且需要能够识别用户。 用户通常可以通过唯一的ID轻松识别。 但是在web应用程序中,通常有一些用户根本没有被识别,通常被称为guest,anonymous,everyone等等。在这个例子中,我们假设ACL可以使用一个用户对象并将这些细节封装起来。 用户对象绑定到应用程序请求对象,并且ACL可以使用它。
怎么识别命令? 你对MVC模式的解释表明,一个命令是一个类名和一个方法名的组合。 如果我们仔细观察,甚至有一个命令的参数(参数)。 因此,询问一个命令到底是什么是有效的。 类名,方法名,参数的数量或名称,甚至是任何参数中的数据,还是所有这些的混合?
根据你需要在ACL中识别命令的详细程度,这可能会有很大的不同。 例如让我们简单地保留它,并指定一个命令由类名和方法名来标识。
所以这三个部分(ACL,命令和用户)是如何相互关联的上下文现在更加清晰。
我们可以说,用一个虚构的ACL组件,我们已经可以做到以下几点:
$acl->commandAllowedForUser($command, $user);
只要看看这里发生了什么:通过使命令和用户可识别,ACL可以做到这一点。 ACL的工作与用户对象和具体命令的工作无关。
只有一个部分缺失,这不能活在空中。 而事实并非如此。 所以你需要find访问控制所需的地方。让我们来看看标准的Web应用程序中会发生什么:
User -> Browser -> Request (HTTP) -> Request (Command) -> Action (Command) -> Response (Command) -> Response(HTTP) -> Browser -> User
为了find这个地方,我们知道它必须在具体的命令执行之前,所以我们可以减less这个列表,只需要看看下面的(潜在的)地方:
User -> Browser -> Request (HTTP) -> Request (Command)
在你的应用程序的某个时候,你知道一个特定的用户已经请求执行一个具体的命令。 你已经在这里做了一些ACL:如果用户请求一个不存在的命令,你不允许执行这个命令。 因此,在应用程序中发生的情况可能是添加“真实”ACL检查的好地方:
该命令已经定位,我们可以创build它的标识,以便ACL可以处理它。 如果用户不允许该命令,则该命令将不会被执行(动作)。 也许一个CommandNotAllowedResponse
而不是CommandNotFoundResponse
的情况下,一个请求无法parsing到一个具体的命令。
一个具体的HTTPRequest映射到一个命令的地方通常称为路由 。 由于路由已经具有查找命令的工作,为什么不扩展它来检查命令是否实际上是每个ACL允许的呢? 例如通过将Router
扩展到ACL感知路由器: RouterACL
。 如果你的路由器还不知道User
,那么Router
就不是正确的地方,因为ACL不仅要工作,而且用户也必须被标识。 所以这个地方可能会有所不同,但我相信你可以很容易find你需要扩展的地方,因为这是满足用户和命令要求的地方:
User -> Browser -> Request (HTTP) -> Request (Command)
用户自始至今都可用,先用Request(Command)
。
因此,不要将ACL检查放在每个命令的具体实现中,而要放在它之前。 你不需要任何沉重的模式,魔法或其他任何东西,ACL就是工作,用户是工作,特别是命令,这是工作:只是命令,没有别的。 如果在某个地方守卫着某个angular色,这个命令就不知道angular色是否适用于它。
所以,把事情分开,不要彼此分开。 使用单一责任原则(SRP)的稍微重新说明:应该只有一个理由来改变一个命令 – 因为命令已经改变。 不是因为你现在在你的应用程序中引入了ACL。 不是因为你切换用户对象。 不是因为您从HTTP / HTML界面迁移到SOAP或命令行界面。
您的案例中的ACL控制对命令的访问,而不是命令本身。
一种可能性是将所有的控制器包装在扩展Controller的另一个类中,并在检查授权之后将所有的函数调用委托给包装的实例。
您也可以在调度程序中更上游(如果您的应用程序确实有一个),并根据URL查找权限,而不是控制方法。
编辑 :无论您需要访问数据库,LDAP服务器等都是正确的问题。 我的意思是,你可以实现基于URL而不是控制器方法的授权。 这些更健壮,因为您通常不会更改您的URL(URLs区域types的公共接口),但是您也可以更改控制器的实现。
通常情况下,您有一个或多个configuration文件,可将特定的URL模式映射到特定的身份validation方法和授权指令。 调度员在向控制器发送请求之前确定用户是否被授权,如果不是,则中止调度。