分离特性和授权。在中层实现更多功能并提高质量。






4.60/5 (3投票s)
使用依赖注入和拦截进行应用程序管理的授权
引言
本文旨在讨论一种应用程序管理的授权方法,该方法旨在通过使用依赖注入和拦截来分离和抽象软件中的授权逻辑与业务功能逻辑,从而提高软件开发团队的生产力。
背景
在软件中提供对功能的运行时访问取决于对尝试访问对象的主体进行成功识别。随后,根据诸如组成员身份、一天中的时间、历史记录等多种变量来决定授予还是拒绝访问。一种传统且普遍的方法是构建功能,然后通过安全和访问权限检查代码来装饰和穿插这些代码。成熟的实践和开发框架可能会使其更容易,但本质上,功能的开发必须先于编写“保护”这些功能的代码。这两类活动通常需要经过多个迭代。此外,在应用程序的不同层(数据库、中层和表示层(用户界面))中保护资产的方法因定义不同环境的不同范例而异。例如,可以使用数据库安全功能来保护对数据库数据和操作的运行时访问,该功能依赖于具有表、存储过程和函数权限的用户帐户。尽管如此,当大量功能在存储过程中开发时,通常也会在数据库代码中包含自定义安全检查。然而,本次讨论的重点是软件应用程序中的中层,该中层通常使用 C# 和 Java 等高级计算机语言开发。
一些分析
将业务规则和安全规则与应用程序数据分开并存储在单独的存储库中,然后针对该存储库执行授权检查,这并非新想法或新实践,但业务逻辑代码仍然与安全检查代码穿插在一起,并且必须像传统的基于角色的访问控制 (RBAC) 软件一样,一个接一个地编写。无论是在数据库的存储过程中进行安全检查,还是在中层代码中进行安全检查,都会发生这种情况。因此,维护这些“检查点”需要一个人对从一个点到另一个点进行必要修改的总体代码布局有很好的了解。
要说明的是,必须先编写业务功能,然后编写安全检查,这种繁琐的工作可以通过分离来实现,从而使开发和系统构建(安全和其他配置)活动能够针对共同的对象和系统模型同时运行。
一些信息安全专业人士经常说,软件从业者需要从这些“不安全”的软件开发实践的坏习惯中解脱出来,因为这些习惯会在系统中引入弱点,而这些弱点可能只有在发生漏洞后才会显现出来。这意味着,软件开发人员往往对安全掉以轻心。原因可能是他们倾向于专注于构建能够促进职业安全的酷炫小部件、屏幕布局和其他华而不实的装饰,而将软件安全留作枯燥的后续考虑。在我看来,尽管这种有争议的观点在现实生活中有些根据,但如果它没有明确的补救措施,就无法真正提高生产力。
也可以认为,这种观点的基础是信息安全与软件开发体验的典型分离。信息安全的角色通常只在软件开发和部署进行测试时才变得突出,此时才会出现问题。然而,我们必须强烈同意,软件安全责任在于设计者和开发人员。
这引出了一个问题:如何在软件项目的初期就获得更好的安全关注?如何从一开始就充分解决机密性、完整性和可用性?能否在 Java 和 .NET 等传统语言中将安全规则和业务功能并排开发,并以某种方式持续合并以达到预期结果?我的看法是,从软件项目的最初阶段就应该有人佩戴信息安全帽子,并参与整个过程。这是可以快速有效地实现更好安全的一种方式。
以下是使用 .NET 平台解决上述问题的途径,该途径一般依赖于依赖注入和拦截的概念,具体而言,则依赖于对象创建和安全规则的抽象。
进一步阐述,考虑这样一种场景:开发人员仅编写功能代码 — 经过自动化单元测试、功能要求和代码检查,将其发送到中央存储库,而不必担心在常规业务逻辑中插入显式的基于角色的检查。到头来,你会得到一个适合概念验证演示的软件,但几乎无法在现实世界中使用,因为无法区分职责并强制执行控制。在同一场景下,考虑另一个人,他拥有信息安全技能,并用某种领域特定语言针对同一代码编写安全规则,到头来,第一个开发人员编写的代码将根据第二个开发人员定义的规则运行。以下概述了实现这一目标的策略。
在此上下文中,安全规则实际上就是业务规则,一般而言,应用程序授权可以实现以下场景:
- 保护对操作的访问。 在这种情况下,主体要么能够访问操作,要么不能。例如,保存费用报告或检索活动项目列表。操作是对象的某个方法。无论是 CRUD(创建、检索、更新、删除)调用,还是计算固定收益证券的累计利息的调用,都包含在方法调用中。限制对特定 URL 集合的访问实例也可以属于这种情况。
- 设置对象属性。 在这种情况下,即使我们希望允许访问某个操作,我们也可能希望根据主体将某些参数限制在一定值的范围内。例如,在检索所有费用报告的情况下,我们可能希望将访问限制在仅属于主体(已登录用户)的费用报告。我们可以通过始终将某个项目 ID 参数的值设置为用户配置文件中的某个值来实现这一点。
在这些场景中,访问功能的入口是基于对各种复杂度的表达式求值的,该表达式会产生布尔结果。例如:
- 用户是否允许在此时间登录?
- 用户是否属于允许访问此操作的组?
- 用户是否被允许查看所有实体,无论这些实体是否“属于”他/她?
- 用户是否在规定时间内执行操作的阈值?
请注意,同一系统通常为每个安装都有不同的规则集,即每个客户的规则都不同。当然,这些规则中的两个或多个规则可以组合起来,并且可以基于主体的属性以及计算环境的属性,例如一天中的时间、剩余磁盘空间等。
建议方法 - 概览
依赖注入(DI)是该策略的核心,它是一种动态指定代码依赖关系的方式。关于此主题有一个 维基百科文章,并且有大量的实现。我将使用 Microsoft 的 Patterns and Practices 组提供的 Unity framework。此外,我们需要理解拦截,这是一种在运行时动态注入代码的方式,对于处理日志记录、验证和授权等横切关注点非常有用。(请参阅 此链接。)
简而言之,我们打算在运行时动态地在方法调用之前注入代码,以执行授权检查。要做到这一点,对象将不使用传统的实例化方法(例如 C# 中的 Object o = new Object()
)创建,而是通过 DI 框架创建。由于代码是注入的,它无法提前知道适用的上下文(主体、对象等),因此授权检查无法硬编码。因此,在运行时注入的代码将根据支持上述两个场景的编码规则集执行授权。可以选择任何您喜欢的领域特定语言作为格式,例如基于 XML 的。
该策略总结如下:
- 设计系统的对象模型,即定义实体、服务和接口。这就像在实际实现之前为 Web 服务定义 WSDL 文件一样。
- 为每个需要安全访问的对象配置依赖注入。
- 开发用于注入的授权检查代码和规则格式,以符合两个场景。
- 配置对象的拦截,以便在代码中的方法被调用时运行授权代码。
- 定义要在方法调用被拦截时在运行时评估的安全规则。
- 在运行时,使用依赖注入实例化对象,而不是使用内置语言实例化。
- 在尝试进行调用时,拦截会调用代码以针对已定义的规则执行授权检查。
这看起来是一个相当直接的方法,但通常细节决定成败。我尝试实现此方法时,发现了以下需要一些计划和注意的领域:
- 以对象上的方法和条件表达式的形式定义安全规则的格式。
- 自动 DI 配置对象的机制。
- 开发将在运行时执行授权检查的实际代码。这需要大量使用反射。(https://en.wikipedia.org/wiki/Reflection_(computer_programming))
- 决定使用哪些框架进行依赖注入和身份验证。
这是对该方法进行概括性介绍的尝试。稍后,我将深入探讨并提供实际实现的详细信息。
该方法的优势包括:
- 有助于更轻松地维护不同站点/安装相同软件的安全和业务规则。
- 它促进了关注点分离,使不同的团队可以专注于系统的不同方面。
- 它支持良好的编程实践,如 DRY(不要重复自己),
- 允许按需以“完全不受限制”的状态测试系统功能。
- 采用的安全模型可以更容易地跟踪和审查,因为它被抽象到自己的存储库中,并且可以更直接地进行维护。
- 更轻松的团队管理,开发人员在缺勤时可以更容易地将活动移交给彼此。从事功能开发的人员可以看到得更清楚,因为他们不必担心添加安全性。
- 支持不同的安全模型 — 例如,传递性权限:将对象权限分配给任务/项目,一旦人员被分配到该项目,他们就拥有该对象的相应权限。
没有什么是完美的,以下是关于此方法的一些担忧:
- 如何安全地存储和检索运行时安全规则? 如果规则在部署后很容易被破解和修改,那么整个目的就落空了。规则可以被加密存储在文件中,或者硬编码到程序集或 DLL 中,而这些程序集或 DLL 本身也可以被混淆。
- 安全对象必须使用依赖注入框架创建。 不使用 DI 创建对象,就无法进行拦截。这是因为 DI 框架会创建一个代理对象,使其能够管理对象的生命周期和方法调用。
- 这种方法是否适用于数据库存储过程? 我个人不喜欢将业务功能放在数据库中。我更愿意使用数据库查询仅用于数据检索,尤其是在需要进行一些复杂聚合的情况下。数据库就是为此而设计和优化的,不一定是为了进行复杂的 XML 操作。因此,我乐于接受使用帐户进行数据库安全以提供对对象的安全访问。
- GUI 中的安全性。 用户可能希望根据可用权限禁用/隐藏按钮。通过利用所述策略,这仍然非常可行。安全规则可以命名,并且可以根据安全规则的底层布尔条件来控制对小部件的访问。
- 管理权限优先级: 如果分配了多个适用于主体的权限集,我们如何处理优先级,从而使有效权限是最多限制性或最少限制性的?
- 用户的配置文件应包含限制参数值所需的必要值: 主体的配置文件必须包含做出决策和设置参数值所需的所有必要信息。例如,用户的 ID,将在运行时用于查询。
- 性能影响如何? 我计划在后续文章中针对此方法运行性能基准测试。但是,Daniel Palme 提供了一套非常有用的、维护良好的基准测试,展示了不同框架在底层 DI 和拦截场景下的速度,详情请参阅 此链接。
- 进程安全。 这种方法对进程安全有什么作用?它会产生任何黑客可以用来劫持进程并执行恶意操作的漏洞吗?我不知道有任何因这种方法而产生的漏洞。话虽如此,这并不能阻止从进程外部加强安全的努力,例如 .NET 的代码访问安全、使用特殊操作系统帐户、混淆等机制。
如前所述,我将发布另一篇文章来实际演示这一点。
非常感谢您的阅读。也提前感谢您的评论。
历史
- 2016年8月 - 初始版本