事件采购和密码更改安全隐患

时间:2018-05-19 14:36:42

标签: events password-protection event-sourcing

当密码更新问题出现时,我最近开始致力于事件采购

我的理解如下:

  1. 事件存储在事件存储中,它充当当前应用程序和对象状态的单一事实源。我们可能会在创建所述对象后重放给定对象的一系列事件,并找到该对象的当前状态。

  2. 事件必须无限期存储,因为链中的中断会导致潜在的不一致状态。如果某些视图每次都要处理的事件太多,我们可能会制作一个事件链的快照(即对象的当前状态)。

  3. 对我来说,这对user updated password之类的东西有明显的安全隐患。在这种情况下,我们会看到类似的内容:

    - UserCreatedEvent(user)
    - ... // other events that might change the state of the User object
    - UserChangedPasswordEvent(updatedPassword)
    

    我用这种方法看到的问题是,为了使应用程序保持一致状态,我们必须存储用户的所有先前密码,因为我们无法判断给定密码是否是当前密码或者只是用户之前的密码之一(仅给出UserChangedPasswordEvent)。

    为了论证,让我们说应用程序使用除BCrypt之外的较弱算法来存储密码,并且在给定的时间范围之后密码是可破解的(即暴力/彩虹表)

    在事件来源的情况下,设法获得UserChangedPasswordEvent商店的攻击者现在将拥有所有用户在应用程序中使用过的密码列表。在这种情况下,他们也不可能访问UserCreatedEvent商店,因此也可以访问用户的(通常)唯一电子邮件。

    由于大多数临时用户不幸在各种平台上重复使用密码,因此攻击者现在可能会访问用户可能在多个平台上使用的任意数量的密码。如果在X时间之后存在诸如强制性密码更新等机制,情况会更糟。

    尽管如此,这是最常见的事件源和密码更新方法,还是有一种标准化的方法来处理这部分应用程序?我承认场景的前提(弱密码哈希)是一个弱点,但它最好得到我的观点。

    我可以想到两种方法来解决这个问题:

    • 加密事件存储和/或文件系统;影响绩效
    • UserChangedPasswordEvent仅通知更改本身,密码通过其他渠道存储在其他位置;然而,违反了事件采购的想法

    我是否在这里推翻这个问题?如果使用适当的哈希算法, 是否存在问题?

2 个答案:

答案 0 :(得分:5)

实际上,将密码存储在事件中是一件非常危险的事情,但您没有真正的理由将密码存储在事件有效负载中。事实上,您甚至可能不会使用UserCredentialsSubdomain或整个AuthenticationDomain的事件来源。

如果您仍然决定使用AuthenticationDomain的事件采购(这不一定是坏事),您不需要将密码存储在UserChangedPasswordEvent内,因为您不会将密码存储在UserChangedPasswordEvent内。在Write模型中需要整个密码历史记录(散列或明文)。最后一个密码(或哈希)仅由身份验证服务用于验证用户的身份。没有其他Read模型需要这个;您需要最近密码历史记录(即不允许更改为旧密码)的用例可以使用密码日志或类似的东西来实现,您不需要事件来源。 {{1}}可能很有用,例如向用户显示更改密码的最后日期,但不包含密码本身。

评论后更新:

您不需要在整个应用程序中使用ES,它不是所有ES或所有非ES。通常,对于身份验证,人们使用平面模型。但是,如果您选择使用ES,即使您没有用户密码,您仍然可以使用它重建UserAggregate状态,因为您实际上并不需要密码这个州。

在我所指的这种情况下,密码检查是在UserAggregate(事件流的所有者)处理LoginCommand之前完成的,通过调用使用平面持久性的PasswordCheckingService。这在应用程序层中完成:首先检查密码,然后由UserAggregate进一步检查登录(即,如果用户仍处于活动状态,则可以登录)。

事实上,从事件流中排除密码会让您意识到验证用户身份应该是一个单独的子系统,以及通过电话或生物识别扫描进行验证。在我看来,这会改变你的架构,使其更加清晰。整个身份验证可以隐藏在具有多个实现的简单界面后面。

  

您所描述的场景不会明确地进行其他类型的调用

不,它不会或至少不会重建聚合州。该远程调用将在Application层中完成。

重建状态时调用外部服务是针对事件来源的 - 事件流必须足够。

答案 1 :(得分:2)

从根本上说,存储密码历史与存储一个密码并不完全不同 - 出现了同样的问题。

据我所知,没有一种标准化的方式"处理事情,但各种人正在尝试的许多不同的方法

  1. 将实际密码存储在事件流之外。

  2. 将密码存储在事件中,但加密该值,以便仅供授权进程使用。

  3. 这是一个相当热门的话题,因为GDPR;当数据主体具有right to erasure时,将机密存储在持久数据存储中意味着什么。

    我经常讨论的实现是为每个数据主题使用唯一的加密密钥,因此如果您需要"擦除"丢弃密钥的数据。 (注意:并不清楚这种方法是否符合法院的要求。)

    更广泛地说,人们需要记住"事件"为多种目的服务,我们不需要为两者使用相同的事件或相同的表示。你已经拥有了#34;私人"持久性存储,它允许域模型重建自己的状态,然后你拥有" public"表示,发布供他们认为合适的其他服务使用。

    因此,例如,作为密码管理服务,我可能向全世界播放一个Bob更改密码的事件,但在公开表示中包含新密码。

    回顾Udi Dahan on service boundaries或他关于finding service boundaries in health care的演讲。