授权应该是模型还是控制器的一部分?

33

我正在编写一个具有一些ACL要求的Web应用程序:用户可以更改某些项目,某些项目可以由多个用户编辑,管理员可以编辑任何内容,而经理可以在其组织内编辑所有内容等。

我正在使用Play!框架,并根据Secure模块的外观,似乎将授权问题放在控制器中是合适的。但是,我认为授权问题是业务逻辑的一部分,因此应该在模型中处理。此外,我开始看到我需要重构的控制器中重复的逻辑。

另一方面,将授权添加到模型中意味着我必须有一种方法从模型内获取当前用户,这似乎不太对。或者,我可以将“current_user”参数添加到每个模型方法中,但那似乎更糟糕。

那么,常见做法是什么?我可以/应该将授权代码放在模型中,还是保留在控制器中?

7个回答

17

我认为这是一个灰色地带。有人可以认为用户访问是HTTP世界和面向对象世界之间的映射的一部分。这就是控制器的目的(因此大量使用了静态方法),将传入的请求转换为准备好处理领域模型业务规则的请求。

我建议控制器逻辑绝对是控制访问模型的正确位置,特别是在很大程度上在注释级别上进行管理,并将身份验证抽象到Security类中。


4
我认为这是完全正确的,但这是一个灰色地带,因此需要解释。所以,取决于你是否同意我的解释 :o) - Codemwnci

16

授权不应该作为控制器或领域模型的一部分。

相反,它应该在服务层中。

控制器应该只充当调度程序和HTTP与应用程序服务之间的代理。 它是应用程序服务进行编排的地方。 这是放置授权的最佳位置。

假设用户A被授权访问来自域X的数据,但未获得甚至读取来自域Y的数据的权限。 如果将授权放置在控制器中,则用户A会在控制器X中获得授权,并通过服务调用可以访问来自域Y的数据,这不是我们所期望的。

由于领域模型在服务层上彼此通信,因此最好将授权放置在同一级别上。


那么,如果有一个逻辑,超级管理员可以访问所有资源,而管理员只能访问一些资源,那么这个逻辑应该放在哪里:控制器(假设这样的逻辑可以通过基于策略的授权来处理,而基于策略的授权又使用httpcontext。我不想在服务中使用httpcontext),服务还是领域模型? - arjun
2
这可能会产生另一个副作用,控制器可能不是您服务的唯一入口。如果我突然想要gRPC端点,1个gRPC端点将映射到所有服务,同样的事情也适用于Websockets,控制器只应将请求转换为域对象。 一旦它被传递进去,服务就会处理一切,这样您以后可以升级终点,如果需要的话。 - user1267177

9
在大多数情况下,安全性应该是模型上方的一层(或多层)。安全性是一个独立的领域,限制对较低级别层的访问。
我认为安全性不应该在控制器层面完成。
在我看来,应该是这样的:
视图 -> 控制器 -> 安全性 -> 模型
安全层可以是模型的外观或代理,保护访问,但对控制器透明。
然而,如果根据用户的访问权限修改视图,则可能需要在控制器层面进行一些检查(例如,在ViewModel上设置CanEdit布尔属性的值)。

你混淆了安全和授权。安全问题必须在应用程序的每个层面上处理 - 参见:深度防御。问题是“授权属于哪个方面?”,而不是安全。 - Francois Bourgeois

1

我个人非常喜欢Play! Secure模块处理这个问题的方式(教程在这里非常有帮助)。如果您不介意使用@Before注释,那么这将是相当轻松的。


1
我现在的计划如下:
  • 不使用JS进行表单验证,而是通过HTTPS ajax进行验证

  • 一个Ajax php类

  • 将表单数据发送到模型作为其数据以进行具体验证,例如电子邮件和密码(可能会被其他类重用,因此这肯定是模型区域)

  • 如果没有错误,则在用户表中查找凭据电子邮件/密码,然后将其传递给控制器进行身份验证类型,例如登录/注册/密码重置

  • 然后,控制器生成所需的输出视图或设置用户已登录的会话等

这基于Laravel,但我有自己的库,希望它独立于laravel,并仅松散地基于此关键要求。

重点在于模型将所需的凭据作为数据查找,然后将其发送到控制器,因为它不关心应该如何处理。我认为这是使此区域成为每个组件之间明确责任的唯一方法。


0
我将以Rails为例。授权库pundit将授权牢固地置于“模型”领域中,这是通过它们的辅助方法来实现的。
假设您有一个ShoppingBag模型。您可能想要创建一个ShoppingBag。
class ShoppingBagController
  def create
      authorize ShoppingBag.new, current_user
  end
end

如果你的模型和控制器之间有一对一的映射,那么它会非常有效。但是如果你需要在同一个模型上使用第二个控制器呢?现在你就卡住了!

class DiscountedShoppingBagController
  def create
      authorize ShoppingBag.new, current_user # does not work for us. we want a slightly different authorization, on the same model.
  end
end

这就是为什么我不喜欢Pundit和CanCanCan。对我来说,在控制器级别进行授权是理想的,而在模型级别进行授权会限制我太多,没有相应的收益。


0
从我个人对MVC框架的经验来看,我会说:
  1. 模型是表示数据库表的对象,它应该是纯粹的,不应包含任何额外的逻辑。
  2. 控制器是做出决策和其他自定义逻辑的地方,因此授权应该在控制器中进行。可以设计一些钩子来检查用户是否被授权,并避免代码重复(DRY)。
  3. 如果你正在使用典型的REST架构,给用户权限的最佳方式是生成一个令牌,在数据库和客户端保存这个令牌,并在每个请求中验证该令牌。如果你正在使用Web浏览器应用程序,可以使用服务器端会话进行授权(这更加简单)。
所以我的建议是将授权逻辑保留在控制器中。

那么,如果有一个逻辑,超级管理员可以访问所有资源,而管理员只能访问一些资源,那么这个逻辑应该放在哪里:控制器(授权)、服务还是领域模型? - arjun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接