Estoy trabajando en una aplicación web que debe lidiar con impulsos muy grandes de usuarios simultáneos, que necesitan autorización, para solicitar contenido idéntico. En su estado actual, es totalmente paralizante incluso para una instancia de AWS de 32 núcleos.
(Tenga en cuenta que estamos usando Nginx como proxy inverso)
La respuesta no puede simplemente almacenarse en caché ya que, en el peor de los casos, debemos verificar si el usuario está autenticado decodificando su JWT. Esto requiere que activemos Laravel 4, que la mayoría estaría de acuerdo, es lento , incluso con PHP-FPM y OpCache habilitados. Esto se debe principalmente a la fuerte fase de arranque.
Uno podría hacer la pregunta "¿Por qué usaste PHP y Laravel en primer lugar si sabías que esto iba a ser un problema?" ¡Pero ya es demasiado tarde para volver a tomar esa decisión!
Solución posible
Una solución que se ha presentado es extraer el módulo Auth de Laravel a un módulo externo liviano (escrito en algo rápido como C) cuya responsabilidad es decodificar el JWT y decidir si el usuario está autenticado.
El flujo de una solicitud sería:
- Verifique si la memoria caché golpeó (si no pasa a PHP de manera normal)
- Decodifica la ficha
- Comprueba si es válido
- Si es válido , servir desde caché
- Si no es válido , dígale a Nginx, y luego Nginx pasará la solicitud a PHP para que lo atienda de manera normal.
Esto nos permitirá no acceder a PHP una vez que hayamos enviado esta solicitud a un solo usuario y, en cambio, llegar a un módulo liviano para jugar con la decodificación de JWT y cualquier otra advertencia que venga con este tipo de autenticación.
Incluso estaba pensando en escribir este código directamente como un módulo de extensión Nginx HTTP.
Preocupaciones
Mi preocupación es que nunca había visto esto antes y me preguntaba si había una mejor manera.
Además, en el momento en que agrega cualquier contenido específico del usuario a la página, elimina totalmente este método.
¿Hay otra solución más simple disponible directamente en Nginx? ¿O tendríamos que usar algo más especializado como el barniz?
Mis preguntas:
¿Tiene sentido la solución anterior?
¿Cómo se aborda esto normalmente?
¿Hay una mejor manera de lograr un aumento de rendimiento similar o mejor?
fuente
Respuestas:
He estado tratando de abordar un problema similar. Mis usuarios deben autenticarse para cada solicitud que realicen. Me he centrado en hacer que los usuarios se autentiquen al menos una vez mediante la aplicación de back-end (validación del token JWT), pero después de eso, decidí que ya no necesitaría el backend.
Elegí evitar requerir cualquier complemento de Nginx que no esté incluido por defecto. De lo contrario, puede verificar nginx-jwt o Lua scripting y estas probablemente serían excelentes soluciones.
Direccionamiento de autenticación
Hasta ahora he hecho lo siguiente:
Delegó la autenticación a Nginx usando
auth_request
. Esto llama a unainternal
ubicación que pasa la solicitud a mi punto final de validación de token de back-end. Esto por sí solo no aborda el problema de manejar una gran cantidad de validaciones en absoluto.El resultado de la validación del token se almacena en caché mediante una
proxy_cache_key "$cookie_token";
directiva. Tras la validación exitosa del token, el backend agrega unaCache-Control
directiva que le dice a Nginx que solo guarde en caché el token por hasta 5 minutos. En este punto, cualquier token de autenticación validado una vez está en el caché, ¡las solicitudes posteriores del mismo usuario / token ya no tocan el backend de autenticación!Para proteger mi aplicación back-end contra posibles inundaciones por tokens no válidos, también cacheé las validaciones rechazadas, cuando mi punto final backend devuelve 401. Estas solo se almacenan en caché por un corto período de tiempo para evitar potencialmente llenar el caché Nginx con tales solicitudes.
He agregado un par de mejoras adicionales, como un punto final de cierre de sesión que invalida un token al devolver 401 (que también está en caché por Nginx) para que si el usuario hace clic en cerrar sesión, el token ya no se puede usar, incluso si no ha expirado.
Además, mi caché Nginx contiene para cada token, el usuario asociado como un objeto JSON, lo que me evita recuperarlo de la base de datos si necesito esta información; y también me salva de descifrar el token.
Acerca del token de por vida y los tokens de actualización
Después de 5 minutos, el token habrá expirado en el caché, por lo que se volverá a consultar el backend. Esto es para asegurarse de que puede invalidar un token, porque el usuario cierra sesión, porque se ha visto comprometido, etc. Dicha revalidación periódica, con una implementación adecuada en el back-end, me evita tener que usar tokens de actualización.
Tradicionalmente, los tokens de actualización se usarían para solicitar un nuevo token de acceso; se almacenarían en su backend y usted verificaría que una solicitud de un token de acceso se realice con un token de actualización que coincida con el que tiene en la base de datos para este usuario específico. Si el usuario cierra la sesión, o los tokens se ven comprometidos, eliminará / invalidará el token de actualización en su base de datos para que la próxima solicitud de un nuevo token que use el token de actualización invalidado falle.
En resumen, los tokens de actualización generalmente tienen una validez larga y siempre se verifican en el backend. Se utilizan para generar tokens de acceso que tienen una validez muy corta (unos minutos). Estos tokens de acceso normalmente llegan a su backend, pero solo verifica su firma y fecha de vencimiento.
Aquí, en mi configuración, estamos usando tokens con una validez más larga (pueden ser horas o un día), que tienen el mismo rol y características que un token de acceso y un token de actualización. Debido a que Nginx tiene su validación e invalidación en caché, el backend solo las verifica completamente una vez cada 5 minutos. Por lo tanto, mantenemos el beneficio de usar tokens de actualización (poder invalidar rápidamente un token) sin la complejidad adicional. Y la validación simple nunca llega a su backend que es al menos 1 orden de magnitud más lenta que la caché Nginx, incluso si se usa solo para la firma y la verificación de la fecha de vencimiento.
Con esta configuración, podría deshabilitar la autenticación en mi backend, ya que todas las solicitudes entrantes llegan a la
auth_request
directiva Nginx antes de tocarla.Eso no resuelve completamente el problema si necesita realizar algún tipo de autorización por recurso, pero al menos ha guardado la parte de autorización básica. E incluso puede evitar descifrar el token o hacer una búsqueda de base de datos para acceder a los datos del token ya que la respuesta de autenticación en caché de Nginx puede contener datos y devolverlos al back-end.
Ahora, mi mayor preocupación es que puedo estar rompiendo algo obvio relacionado con la seguridad sin darme cuenta. Dicho esto, cualquier token recibido todavía se valida al menos una vez antes de que Nginx lo almacene en caché. Cualquier token templado sería diferente, por lo que no golpearía el caché ya que la clave del caché también sería diferente.
Además, tal vez vale la pena mencionar que una autenticación del mundo real lucharía contra el robo de tokens al generar (y verificar) un Nonce adicional o algo así.
Aquí hay un extracto simplificado de mi configuración de Nginx para mi aplicación:
Ahora, aquí está el extracto de configuración para el
/auth
punto final interno , incluido anteriormente como/usr/local/etc/nginx/include-auth-internal.conf
:.
Abordar la publicación de contenido
Ahora la autenticación está separada de los datos. Como dijiste que era idéntico para cada usuario, el contenido en sí también puede ser almacenado en caché por Nginx (en mi ejemplo, en la
content_cache
zona).Escalabilidad
Este escenario funciona de manera inmediata, suponiendo que tenga un servidor Nginx. En un escenario del mundo real, es probable que tenga una alta disponibilidad, lo que significa múltiples instancias de Nginx, y posiblemente también aloje su aplicación de back-end (Laravel). En ese caso, cualquier solicitud que hagan sus usuarios podría enviarse a cualquiera de sus servidores Nginx, y hasta que todos hayan almacenado en caché localmente el token, seguirán llegando a su servidor para verificarlo. Para una pequeña cantidad de servidores, el uso de esta solución aún traería grandes beneficios.
Sin embargo, es importante tener en cuenta que con varios servidores Nginx (y, por lo tanto, cachés), pierde la capacidad de cerrar sesión en el lado del servidor porque no puede purgar (forzando una actualización) el caché de tokens en todos ellos, como
/auth/logout
lo hace en mi ejemplo. Solo le queda la duración de la memoria caché de token de 5mn que obligará a consultar su backend pronto y le dirá a Nginx que la solicitud ha sido denegada. Una solución parcial es eliminar el encabezado del token o la cookie en el cliente al cerrar sesión.Cualquier comentario sería muy bienvenido y apreciado!
fuente