【读书笔记】《Go With The Domain》12. 存储库设计安全
📔【读书笔记】《Go With The Domain》12. 存储库设计安全
2023-10-4
| 2024-3-24
0  |  0 分钟
type
status
date
slug
summary
tags
category
icon
password
Sub-item
Last edited time
Mar 24, 2024 03:12 AM
Parent item
领域
通过测试和代码审查,您可以使您的项目没有错误。是吗?嗯……实际上,可能不是。那太简单了。这些技术降低了出现错误的可能性,但不能完全消除它们。但这是否意味着我们需要忍受错误的风险直到生命的尽头?
一年多前,我在Harbor项目中发现了一个非常有趣的 PR。这是对允许普通用户创建管理员用户这个问题的修复。这显然是一个严重的安全问题。当然,自动化测试之前并没有发现这个bug。错误修复如下所示:
一条 if 语句修复了该错误。添加新的测试还应该确保将来不会出现回归。够了吗?它是否可以保护应用程序在未来免受类似错误的影响?我很确定没有。
在更复杂的系统中,如果有一个大团队在处理这些系统,问题就会变得更大。如果有人是该项目的新手并且忘记添加此 if 语句怎么办?您可能会惊讶于您编写的代码的寿命有多长。我们不应该相信人们会按照预期的方式使用我们创建的代码——他们不会。
在某些情况下,能够保护我们免受此类问题影响的解决方案就是良好的设计。好的设计不应允许以无效的方式使用我们的代码。好的设计应该保证你可以毫无恐惧地接触现有的代码。新加入该项目的人会觉得引入变更更安全。
在本章中,我将展示如何确保只有允许的人才能查看和编辑训练。
在我们的例子中,只有训练所有者(学员)和教练才能看到训练。我将以一种不允许以非预期方式使用我们的代码的方式来实现它。通过设计。我们当前的应用程序假设访问数据的唯一方式是通过存储库。因此,我将在存储库添加授权级别。由此,我们确信未经授权的用户不可能访问这些数据。
💡
什么是存储库 tl;dr 如果您没有机会阅读我们前面的章节,那么存储库是一种帮助我们从应用程序逻辑中抽象数据库实现的模式。如果您想更多地了解它的优点并了解如何将其应用到您的项目中,请阅读存储库模式。
但是等等,存储库是管理授权的正确位置吗?嗯,我可以想象有些人可能会对这种方法持怀疑态度。当然,我们可以开始一些关于存储库中可以包含哪些内容和不应该包含哪些内容的哲学讨论。另外,谁可以看到训练的实际逻辑将被放置在领域层。我没有看到任何重大缺点,而且优点也很明显。在我看来,实用主义应该在这里获胜。
💡
提示 本书的另一个有趣之处是我们重点关注面向业务的应用程序。但即使 Harbor 项目是一个纯粹的系统应用程序,大多数提出的模式也可以应用。在向我们团队介绍 Clean Architecturea 后,我们的队友在他的游戏中使用了这种方法来抽象渲染引擎。

请告诉我代码!

为了实现我们的稳健设计,我们需要实现三件事:
  1. 谁能看到训练的逻辑(领域层)
  1. 用于获取训练的函数(存储库中的 GetTraining
  1. 用于更新训练的函数(存储库中的UpdateTraining

领域层

第一部分是负责决定某人是否可以看到训练的逻辑。因为它是领域逻辑的一部分(你可以和你的业务或产品团队讨论谁可以看到培训),所以它应该进入领域层。它是通过 CanUserSeeTraining 函数实现的。将其保留在存储库级别也是可以接受的,但很难重用。我不认为这种方法有任何优势——特别是如果将其放入领域不需要任何成本的话。

Repository

现在当我们有了 CanUserSeeTraining 函数后,我们需要使用这个函数。就这么简单。
是不是太简单了?我们的目标是创建简单而不复杂的设计和代码。这是一个很好的迹象,表明它非常简单。我们正在以同样的方式更改 UpdateTraining
就这样!有什么办法可以让有人以错误的方式使用它吗?只要用户有效 – 就不会。这种方法类似于领域驱动设计精简版中介绍的方法。这一切都是为了创建我们不能以错误的方式使用的代码。这是 UpdateTraining 现在的用法:
当然,如果可以重新安排 Training,还需要设置其他一些规则,但同样也是由 Training 领域类型处理的

处理集合

即使这种方法非常适合单个训练的操作,您也需要确保对训练集合的访问得到适当的保护。这里没有魔法:
在应用程序层上执行 CanUserSeeTraining 函数将非常缓慢且代价大。最好创建一些重复的逻辑。
如果此逻辑在您的应用程序中更复杂,您可以尝试在领域层中将其抽象为可转换为数据库驱动程序中的查询参数的格式。我做过一次,效果非常好。
但在Wild Workouts中,它会增加不必要的复杂性 - 让我们保持简单。

处理内部更新

我们通常希望拥有允许开发人员或公司运营部门进行一些“后门”更改的端点。在这种情况下,你能做的最糟糕的事情就是创建任何类型的“假用户”和黑客攻击。
根据我的经验,它需要通过代码中的大量 if 语句来处理。它还会混淆审核日志(如果有的话)。与其创建“假用户”,不如创建一个特殊角色并显式定义该角色的权限。
如果您需要不用任何用户的存储库方法(对于 Pub/Sub 消息处理程序或迁移),最好创建单独的存储库方法。在这种情况下,命名至关重要——我们需要确保使用该方法的人知道安全隐患。
根据我的经验,如果不同参与者的更新变得非常不同,甚至值得为每个参与者引入单独的 CQRS 命令。在我们的例子中,它可能是 UpdateTrainingByOperations

通过 context.Context 传递身份验证

据我所知,有些人通过 context.Context 传递身份验证详细信息。
我强烈建议不要通过 context.Context 传递应用程序正常工作所需的任何内容。原因很简单——当通过 context.Context 传递值时,我们失去了 Go 最重要的优势之一——静态类型。它还隐藏了函数的输入到底是什么。
如果您出于某种原因需要通过上下文传递值,这可能是您服务中某处设计不良的症状。也许该函数做了太多的事情,并且很难将所有参数传递到那里?也许是时候分解它了?
这就是今天的全部内容!如您所见,所提出的方法易于快速实施。希望对您的项目有所帮助,让您对未来的发展更有信心。
架构设计
  • 读书笔记
  • 技术架构
  • 【Linux常用命令】SSH免密连接远端服务器【读书笔记】《Go With The Domain》7. 高质量的数据库集成测试
    目录