paint-brush
在单个 PostgreSQL 数据库上将 Ory Hydra 扩展到每月 20 亿个 OAuth2 流量经过@orydev
493 讀數
493 讀數

在单个 PostgreSQL 数据库上将 Ory Hydra 扩展到每月 20 亿个 OAuth2 流量

经过 Ory6m2023/08/04
Read on Terminal Reader

太長; 讀書

Ory Hydra 是一款流行的开源 OAuth2 和 OpenID Connect 服务器,可为应用程序提供安全的身份验证和授权。构建可扩展且高性能的 OAuth2 服务器的关键挑战之一是管理持久层,这涉及到从数据库存储和检索数据。
featured image - 在单个 PostgreSQL 数据库上将 Ory Hydra 扩展到每月 20 亿个 OAuth2 流量
Ory HackerNoon profile picture
0-item

Ory Hydra 是一款流行的开源 OAuth2 和 OpenID Connect服务器,可为应用程序提供安全的身份验证和授权。构建可扩展且高性能的 OAuth2 服务器的关键挑战之一是管理持久层,这涉及到从数据库存储和检索数据。

动机

一家受欢迎的服务提供商与 Ory 接洽,要求优化其身份验证系统在高负载下的性能。他们经常难以应对高峰时段(超过 600 次登录/秒)大量涌入的授权。为了寻找另一种解决方案,他们开始评估 Ory Hydra,调查它是否能够处理这么多的资助。在与团队联系后,我们开始研究提高整体性能的方法,以使 Ory Hydra 比以往更快、更具可扩展性。成功的关键是重新设计 Hydra 持久层的部分内容,以减少数据库的写入流量,转向瞬态 OAuth2 流。

转向瞬态 OAuth2 流程

这项工作的核心部分之一是将大量瞬态 OAuth2 流状态从服务器转移到客户端,这些状态在 OAuth2 流涉及的三方之间交换。现在,状态在各方之间作为 AEAD 编码的 cookie 或重定向 URL 中的 AEAD 编码的查询参数进行传递,而不是将瞬态状态持久保存到数据库中。 AEAD 代表关联数据的经过身份验证的加密,这意味着数据是机密的,并且在不知道秘密(对称)密钥的情况下也无法被篡改。


当获得最终同意时,该流程仅在数据库中保留一次。


这一变化有几个好处。首先,它减少了需要存储在数据库中的数据量,从而减少了写入流量。其次,它消除了先前在交换期间使用的流表上的多个索引的需要。

OAuth2 授权码授予流程详细信息

我们想要优化的 OAuth2 流程的相关部分是客户端(代表用户)、Hydra(Ory 的 OAuth2 授权服务器)以及登录和同意屏幕之间的交换。当客户端通过授权代码授予 请求授权代码时,用户将首先被重定向到登录 UI 进行身份验证,然后重定向到同意 UI 以授予对用户数据(例如电子邮件地址或个人资料信息)的访问权限。


下面是交换的顺序图。观察每个 UI 获取一个CHALLENGE作为 URL 参数的一部分(步骤 3 和 12),然后使用此CHALLENGE作为参数来检索更多信息(步骤 4 和 13)。最后,两个 UI 都会接受或拒绝用户请求,通常基于用户与 UI 的交互(从步骤 6 到 8 以及 15 到 17)。该 API 合约使 Ory Hydra 保持无头状态并与自定义 UI 分离。

时序图

优化:在 URL 参数中传递 AEAD 编码的流

为了减少数据库访问,我们现在将 AEAD 编码流作为LOGIN_CHALLENGELOGIN_VERIFIERCONSENT_CHALLENGECONSENT_VERIFIER传递。这样,我们就依赖 OAuth2 流程中涉及的各方来传递相关状态。

登录和同意质询以及验证者是存储在数据库中的随机 UUID。

登录和同意质询以及验证程序是 AEAD 编码的流程。

接受或拒绝来自 UI 的请求涉及针对特定挑战的数据库查找。

接受或拒绝来自 UI 的请求涉及解密质询中的流并生成更新的流作为验证者的一部分。

执行

由于 Ory Hydra 是开源的,因此您可以在 Ory GitHub 存储库中查看代码更改。这是相关的提交


这是我们在特定挑战和验证者中对流程进行编码的地方:

 // ToLoginChallenge converts the flow into a login challenge. func (f *Flow) ToLoginChallenge(ctx context.Context, cipherProvider CipherProvider) (string, error) { return flowctx.Encode(ctx, cipherProvider.FlowCipher(), f, flowctx.AsLoginChallenge) } // ToLoginVerifier converts the flow into a login verifier. func (f *Flow) ToLoginVerifier(ctx context.Context, cipherProvider CipherProvider) (string, error) { return flowctx.Encode(ctx, cipherProvider.FlowCipher(), f, flowctx.AsLoginVerifier) } // ToConsentChallenge converts the flow into a consent challenge. func (f *Flow) ToConsentChallenge(ctx context.Context, cipherProvider CipherProvider) (string, error) { return flowctx.Encode(ctx, cipherProvider.FlowCipher(), f, flowctx.AsConsentChallenge) } // ToConsentVerifier converts the flow into a consent verifier. func (f *Flow) ToConsentVerifier(ctx context.Context, cipherProvider CipherProvider) (string, error) { return flowctx.Encode(ctx, cipherProvider.FlowCipher(), f, flowctx.AsConsentVerifier) }


然后,在持久化器(我们的数据库存储库)中,我们对挑战中包含的流程进行解码。例如,以下是处理同意质询的代码:

 func (p *Persister) GetFlowByConsentChallenge(ctx context.Context, challenge string) (*flow.Flow, error) { ctx, span := prTracer(ctx).Tracer().Start(ctx, "persistence.sql.GetFlowByConsentChallenge") defer span.End() // challenge contains the flow. f, err := flowctx.Decode[flow.Flow](ctx, prFlowCipher(), challenge, flowctx.AsConsentChallenge) if err != nil { return nil, errorsx.WithStack(x.ErrNotFound) } if f.NID != p.NetworkID(ctx) { return nil, errorsx.WithStack(x.ErrNotFound) } if f.RequestedAt.Add(p.config.ConsentRequestMaxAge(ctx)).Before(time.Now()) { return nil, errorsx.WithStack(fosite.ErrRequestUnauthorized.WithHint("The consent request has expired, please try again.")) } return f, nil }


让我们看看与没有优化的代码相比,更改的影响:

Barchart comparing baseline with improved performance

现在,流程速度更快,并且与数据库的对话更少。

改进的指数导致性能进一步提高

通过在hydra_oauth2_flow表上引入新索引,我们能够提高 PostgreSQL 的吞吐量并减少 CPU 使用率。下面的屏幕截图显示了在没有改进的指数的情况下(CPU 使用率飙升至 100%)和在采用改进的指数(其中 CPU 使用率保持在 10% 以下)的基准测试的执行情况。

CPU and memory with and without index usage

通过新添加的索引,CPU 使用率(绿色条)被删除,从而降低了 BufferLock 和相关问题的可能性:

bufferlock events with and without index usage

结果

代码和数据库更改将数据库的总往返次数减少了 4-5 倍(取决于完成的缓存量),并将数据库写入减少了约 50%。

基准测试

使用以下规范对 Microsoft Azure 上的新实施进行基准测试:

服务

配置

最大 SQL 连接总数

笔记

Ory Hydra Consent App OAuth2 客户端应用程序 rakyll/hey(http 基准工具)

3x 标准_D32as_v4;美国中南部 5x Standard_D8s_v3;美国中南部

第512章

每个虚拟机都运行上述的所有进程。

HA 配置中的 PostgreSQL 14

内存优化,E64ds_v4,64 个 vCore,432 GiB RAM,32767 GiB 存储;美国中南部


RAM 胜过 CPU。

在上述配置中,Ory 峰值时每秒最多可执行 1090 次登录稳定时每秒可执行 800 次登录。这可以通过使流程无状态并优化常用查询中的索引来实现。

结论

Ory团队所做的性能优化工作使得Hydra的性能和可扩展性得到了显着的提升。通过减少数据库的写入流量并改进代码库和依赖项,Hydra 现在比以往更快、响应更快。通过改进索引,Hydra 现在可以更有效地扩展实例数量。


未来,我们将继续优化Ory的软件以处理更多的流量。我们相信,通过数据模型优化,可以在单个 PostgreSQL 节点上获得 5 倍以上的吞吐量。


如果您正在构建 OAuth2 服务器,我们强烈建议您尝试一下 Ory 完全认证的 OpenID Connect 和 OAuth2 实现:Ory OAuth2 – 我们在全球 Ory 网络上运行的完全托管服务,基于开源Ory Hydra – 已经利用了所描述的优化在本文中,设置只需几分钟!


也发布在这里