Ory Hydra es un conocido servidor OAuth2 y OpenID Connect de código abierto que proporciona autenticación y autorización seguras para las aplicaciones. Uno de los desafíos clave en la creación de un servidor OAuth2 escalable y de alto rendimiento es administrar la capa de persistencia, que implica almacenar y recuperar datos de una base de datos.
Ory fue contactado por un popular proveedor de servicios para optimizar el rendimiento en su sistema de autenticación en condiciones de alta carga. A menudo tuvieron problemas para hacer frente a la gran afluencia de concesiones de autorización durante las horas pico (más de 600 inicios de sesión/seg). Buscando otra solución, comenzaron a evaluar Ory Hydra, investigando si puede manejar esta cantidad de subvenciones. Después de comunicarnos con el equipo, comenzamos a investigar formas de mejorar el rendimiento general para hacer que Ory Hydra sea más rápido y escalable que nunca. La clave del éxito fue rediseñar partes de la capa de persistencia de Hydra para reducir el tráfico de escritura en la base de datos, pasando a un flujo OAuth2 transitorio.
Una de las partes centrales de este trabajo fue mover una gran parte del estado de flujo transitorio de OAuth2, que se intercambia entre las tres partes involucradas en un flujo de OAuth2, del servidor al cliente. En lugar de mantener el estado transitorio en la base de datos, el estado ahora se transmite entre las partes como cookies codificadas por AEAD o como parámetros de consulta codificados por AEAD en URL de redirección. AEAD significa cifrado autenticado con datos asociados, lo que significa que los datos son confidenciales y no se pueden manipular sin conocer una clave secreta (simétrica).
Luego, el flujo solo persiste en la base de datos una vez cuando se otorga el consentimiento final.
Este cambio tiene varios beneficios. Primero, reduce la cantidad de datos que deben almacenarse en la base de datos, lo que a su vez reduce el tráfico de escritura. En segundo lugar, elimina la necesidad de múltiples índices en la tabla de flujo que se usaron previamente durante el intercambio.
La parte relevante del flujo de OAuth2 que queríamos optimizar es un intercambio entre el cliente (que actúa en nombre de un usuario), Hydra (el servidor de autorización de OAuth2 de Ory) y las pantallas de inicio de sesión y consentimiento. Cuando un cliente solicita un código de autorización a través de Authorization Code Grant , el usuario será redirigido primero a la interfaz de usuario de inicio de sesión para autenticarse y luego a la interfaz de usuario de consentimiento para otorgar acceso a los datos del usuario (como la dirección de correo electrónico o la información del perfil).
A continuación se muestra un diagrama de secuencia del intercambio. Observe que cada interfaz de usuario obtiene un CHALLENGE
como parte de los parámetros de URL (pasos 3 y 12) y luego usa este CHALLENGE
como parámetro para recuperar más información (pasos 4 y 13). Finalmente, ambas IU aceptan o rechazan la solicitud del usuario, generalmente en función de la interacción del usuario con la IU (de los pasos 6 a 8 y 15 a 17). Este contrato de API mantiene a Ory Hydra sin cabeza y desacoplado de las IU personalizadas.
Para reducir el acceso a la base de datos, ahora pasamos como LOGIN_CHALLENGE
, LOGIN_VERIFIER
, CONSENT_CHALLENGE
y CONSENT_VERIFIER
un flujo codificado en AEAD. De esta manera, confiamos en las partes involucradas en el flujo de OAuth2 para transmitir el estado relevante.
Antes | Después |
---|---|
Los desafíos y verificadores de inicio de sesión y consentimiento son UUID aleatorios almacenados en la base de datos. | Los desafíos y verificadores de inicio de sesión y consentimiento son el flujo codificado por AEAD. |
Aceptar o rechazar una solicitud de la interfaz de usuario implica una búsqueda en la base de datos para el desafío específico. | Aceptar o rechazar una solicitud de la interfaz de usuario implica descifrar el flujo en el desafío y generar un flujo actualizado como parte del verificador. |
Dado que Ory Hydra es de código abierto, puede revisar los cambios de código en los repositorios de Ory GitHub. Este es el compromiso relevante.
Aquí es donde codificamos el flujo en los desafíos y 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) }
En el persistente (nuestro repositorio de base de datos) luego decodificamos el flujo contenido en el desafío. Por ejemplo, aquí está el código para manejar un desafío de consentimiento:
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 }
Veamos el impacto de los cambios en comparación con el código sin optimizaciones:
Los flujos ahora son mucho más rápidos y hablan menos con la base de datos.
Al introducir un nuevo índice en la tabla hydra_oauth2_flow
, pudimos aumentar el rendimiento y disminuir el uso de la CPU en PostgreSQL. La siguiente captura de pantalla muestra la ejecución de los puntos de referencia sin los índices mejorados, donde el uso de la CPU aumenta al 100 %, y con índices mejorados, donde el uso de la CPU se mantiene por debajo del 10 %.
Con los índices recién agregados, se elimina el uso de la CPU (barras verdes), lo que reduce la probabilidad de BufferLocks y problemas relacionados:
Los cambios en el código y la base de datos redujeron el total de viajes de ida y vuelta a la base de datos entre 4 y 5 veces (dependiendo de la cantidad de almacenamiento en caché realizado) y redujeron las escrituras en la base de datos en aproximadamente un 50 %.
Evaluación comparativa de la nueva implementación en Microsoft Azure con las siguientes especificaciones:
Servicios | Configuración | Máximo total de conexiones SQL | notas |
---|---|---|---|
Aplicación de consentimiento de Ory Hydra Aplicación de cliente OAuth2 rakyll/hey (herramienta de referencia http) | 3x Estándar_D32as_v4; Centro Sur de EE. UU. 5x Standard_D8s_v3; Centro Sur de EE. UU. | 512 | Cada VM ejecutó todos los procesos mencionados. |
PostgreSQL 14 en configuración HA | Memoria optimizada, E64ds_v4, 64 vCores, 432 GiB RAM, 32767 GiB de almacenamiento; Centro Sur de EE. UU. | | La RAM supera a la CPU. |
Ory puede realizar hasta 1090 inicios de sesión por segundo en su punto máximo y 800 inicios de sesión por segundo de forma constante en la configuración anterior. Esto es posible haciendo que el flujo no tenga estado y optimizando los índices en las consultas de uso frecuente.
El trabajo de optimización del rendimiento realizado por el equipo de Ory ha dado como resultado una mejora significativa en el rendimiento y la escalabilidad de Hydra. Al reducir el tráfico de escritura en la base de datos y mejorar el código base y las dependencias, Hydra ahora es más rápida y receptiva que nunca. Al mejorar los índices, Hydra ahora escala mucho más eficientemente con la cantidad de instancias.
En el futuro, continuaremos optimizando el software de Ory para manejar aún más tráfico. Creemos que es posible obtener 5 veces más rendimiento en un solo nodo de PostgreSQL con optimizaciones del modelo de datos.
Si está creando un servidor OAuth2, le recomendamos que pruebe las implementaciones de OpenID Connect y OAuth2 completamente certificadas de Ory: Ory OAuth2, nuestro servicio completamente administrado que se ejecuta en la red global Ory, basado en el código abierto Ory Hydra, ya utiliza las optimizaciones descritas. en este artículo y configurarlo solo toma unos minutos.
También publicado aquí .