paint-brush
Dimensionando o Ory Hydra para dois bilhões de fluxos OAuth2 mensais em um único banco de dados PostgreSQLpor@orydev
493 leituras
493 leituras

Dimensionando o Ory Hydra para dois bilhões de fluxos OAuth2 mensais em um único banco de dados PostgreSQL

por Ory6m2023/08/04
Read on Terminal Reader

Muito longo; Para ler

Ory Hydra é um popular servidor OAuth2 e OpenID Connect de código aberto que fornece autenticação e autorização seguras para aplicativos. Um dos principais desafios na construção de um servidor OAuth2 escalável e de alto desempenho é o gerenciamento da camada de persistência, que envolve o armazenamento e a recuperação de dados de um banco de dados.
featured image - Dimensionando o Ory Hydra para dois bilhões de fluxos OAuth2 mensais em um único banco de dados PostgreSQL
Ory HackerNoon profile picture
0-item

Ory Hydra é um popular servidor OAuth2 e OpenID Connect de código aberto que fornece autenticação e autorização seguras para aplicativos. Um dos principais desafios na construção de um servidor OAuth2 escalável e de alto desempenho é o gerenciamento da camada de persistência, que envolve o armazenamento e a recuperação de dados de um banco de dados.

Motivação

A Ory foi abordada por um provedor de serviços popular para otimizar o desempenho em seu sistema de autenticação em alta carga. Freqüentemente, eles lutavam para lidar com o grande fluxo de concessões de autorização durante os horários de pico (mais de 600 logins/s). Em busca de outra solução, passaram a avaliar a Ory Hydra, investigando se ela aguentaria essa quantidade de outorgas. Depois de entrar em contato com a equipe, começamos a investigar maneiras de melhorar o desempenho geral para tornar o Ory Hydra mais rápido e escalável do que nunca. A chave para o sucesso foi reprojetar partes da camada de persistência do Hydra para reduzir o tráfego de gravação no banco de dados, passando para um fluxo OAuth2 transitório.

Movendo para um fluxo OAuth2 transitório

Uma das partes principais deste trabalho foi mover uma grande parte do estado transitório do fluxo OAuth2, que é trocado entre as três partes envolvidas em um fluxo OAuth2, do servidor para o cliente. Em vez de persistir o estado transitório para o banco de dados, o estado agora é transmitido entre as partes como cookies codificados por AEAD ou parâmetros de consulta codificados por AEAD em URLs de redirecionamento. AEAD significa criptografia autenticada com dados associados, o que significa que os dados são confidenciais e também não podem ser adulterados sem o conhecimento de uma chave secreta (simétrica).


O fluxo é então mantido no banco de dados apenas uma vez quando o consentimento final é dado.


Essa mudança tem vários benefícios. Primeiro, reduz a quantidade de dados que precisam ser armazenados no banco de dados, o que, por sua vez, reduz o tráfego de gravação. Em segundo lugar, elimina a necessidade de vários índices na tabela de fluxo que foram usados anteriormente durante a troca.

O fluxo de concessão do código de autorização OAuth2 em detalhes

A parte relevante do fluxo OAuth2 que queríamos otimizar é uma troca entre o cliente (agindo em nome de um usuário), Hydra (Ory's OAuth2 Authorization Server) e as telas de login e consentimento. Quando um cliente solicita um código de autorização por meio da concessão de código de autorização , o usuário será redirecionado primeiro para a interface do usuário de login para autenticação e, em seguida, para a interface do usuário de consentimento para conceder acesso aos dados do usuário (como endereço de e-mail ou informações de perfil).


Abaixo está um diagrama de seqüência da troca. Observe que cada UI recebe um CHALLENGE como parte dos parâmetros de URL (etapas 3 e 12) e então usa esse CHALLENGE como um parâmetro para recuperar mais informações (etapas 4 e 13). Por fim, ambas as UIs aceitam ou rejeitam a solicitação do usuário, geralmente com base na interação do usuário com a UI (das etapas 6 a 8 e 15 a 17). Este contrato de API mantém o Ory Hydra sem controle e desacoplado de interfaces de usuário personalizadas.

Diagrama de sequência

Otimização: passando o fluxo codificado por AEAD nos parâmetros de URL

Para reduzir o acesso ao banco de dados, agora passamos como LOGIN_CHALLENGE , LOGIN_VERIFIER , CONSENT_CHALLENGE e CONSENT_VERIFIER um fluxo codificado em AEAD. Dessa forma, contamos com as partes envolvidas no fluxo OAuth2 para repassar o estado relevante.

Antes

Depois

Os desafios e verificadores de login e consentimento são UUIDs aleatórios armazenados no banco de dados.

Os desafios e verificadores de login e consentimento são o fluxo codificado por AEAD.

Aceitar ou rejeitar uma solicitação da interface do usuário envolve uma pesquisa no banco de dados para o desafio específico.

Aceitar ou rejeitar uma solicitação da IU envolve descriptografar o fluxo no desafio e gerar um fluxo atualizado como parte do verificador.

Implementação

Como o Ory Hydra é de código aberto, você pode revisar as alterações de código nos repositórios Ory GitHub. Este é o commit relevante.


Aqui é onde codificamos o fluxo nos desafios e verificadores específicos:

 // 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) }


No persister (nosso repositório de banco de dados) decodificamos o fluxo contido no desafio. Por exemplo, este é o código para lidar com um desafio de consentimento:

 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 }


Vejamos o impacto das mudanças quando comparado ao código sem otimizações:

Barchart comparing baseline with improved performance

Os fluxos agora são muito mais rápidos e conversam menos com o banco de dados.

Índices aprimorados levam a mais melhorias de desempenho

Ao introduzir um novo índice na tabela hydra_oauth2_flow , conseguimos aumentar a taxa de transferência e diminuir o uso da CPU no PostgreSQL. A captura de tela abaixo mostra a execução dos benchmarks sem os índices aprimorados, onde o uso da CPU atinge 100%, e com índices aprimorados, onde o uso da CPU fica abaixo de 10%.

CPU and memory with and without index usage

Com os índices recém-adicionados, o uso da CPU (barras verdes) é removido, o que reduz a probabilidade de BufferLocks e problemas relacionados:

bufferlock events with and without index usage

Resultados

As alterações no código e no banco de dados reduziram o total de viagens de ida e volta ao banco de dados em 4 a 5 vezes (dependendo da quantidade de armazenamento em cache feito) e reduziram as gravações do banco de dados em cerca de 50%.

Referências

Comparando a nova implementação no Microsoft Azure com as seguintes especificações:

Serviços

Configuração

Máximo total de conexões SQL

Notas

Ory Hydra Consent App OAuth2 Client App rakyll/hey (http benchmark tool)

3x Standard_D32as_v4; Centro-sul dos EUA 5x Standard_D8s_v3; centro-sul dos EUA

512

Cada VM executou todos os processos mencionados.

PostgreSQL 14 na configuração HA

Memória Otimizada, E64ds_v4, 64 vCores, 432 GiB de RAM, 32767 GiB de armazenamento; centro-sul dos EUA


A RAM supera a CPU.

Ory pode realizar até 1090 logins por segundo no pico e 800 logins/segundo consistentemente na configuração acima. Isso é possível tornando o fluxo sem estado e otimizando os índices em consultas usadas com frequência.

Conclusão

O trabalho de otimização de desempenho feito pela equipe Ory resultou em uma melhoria significativa no desempenho e escalabilidade do Hydra. Ao reduzir o tráfego de gravação no banco de dados e melhorar a base de código e as dependências, o Hydra agora é mais rápido e responsivo do que nunca. Ao melhorar os índices, Hydra agora escala com muito mais eficiência com o número de instâncias.


No futuro, continuaremos a otimizar o software da Ory para lidar com ainda mais tráfego. Acreditamos que é possível obter 5x mais throughput em um único nó PostgreSQL com otimizações de modelo de dados.


Se você estiver construindo um servidor OAuth2, é altamente recomendável experimentar as implementações OpenID Connect e OAuth2 totalmente certificadas da Ory: Ory OAuth2 – nosso serviço totalmente gerenciado em execução na rede Ory global, com base no código aberto Ory Hydra – já utiliza as otimizações descritas neste artigo e configurá-lo leva apenas alguns minutos!


Publicado também aqui .