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 una internal
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 una Cache-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:
# Cache for internal auth checks
proxy_cache_path /usr/local/var/nginx/cache/auth levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=10m use_temp_path=off;
# Cache for content
proxy_cache_path /usr/local/var/nginx/cache/resx levels=1:2 keys_zone=content_cache:16m max_size=128m inactive=5m use_temp_path=off;
server {
listen 443 ssl http2;
server_name ........;
include /usr/local/etc/nginx/include-auth-internal.conf;
location /api/v1 {
# Auth magic happens here
auth_request /auth;
auth_request_set $user $upstream_http_X_User_Id;
auth_request_set $customer $upstream_http_X_Customer_Id;
auth_request_set $permissions $upstream_http_X_Permissions;
# The backend app, once Nginx has performed internal auth.
proxy_pass http://127.0.0.1:5000;
proxy_set_header X-User-Id $user;
proxy_set_header X-Customer-Id $customer;
proxy_set_header X-Permissions $permissions;
# Cache content
proxy_cache content_cache;
proxy_cache_key "$request_method-$request_uri";
}
location /api/v1/Logout {
auth_request /auth/logout;
}
}
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
:
# Called before every request to backend
location = /auth {
internal;
proxy_cache auth_cache;
proxy_cache_methods GET HEAD POST;
proxy_cache_key "$cookie_token";
# Valid tokens cache duration is set by backend returning a properly set Cache-Control header
# Invalid tokens are shortly cached to protect backend but not flood Nginx cache
proxy_cache_valid 401 30s;
# Valid tokens are cached for 5 minutes so we can get the backend to re-validate them from time to time
proxy_cache_valid 200 5m;
proxy_pass http://127.0.0.1:1234/auth/_Internal;
proxy_set_header Host ........;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Accept application/json;
}
# To invalidate a not expired token, use a specific backend endpoint.
# Then we cache the token invalid/401 response itself.
location = /auth/logout {
internal;
proxy_cache auth_cache;
proxy_cache_key "$cookie_token";
# Proper caching duration (> token expire date) set by backend, which will override below default duration
proxy_cache_valid 401 30m;
# A Logout requests forces a cache refresh in order to store a 401 where there was previously a valid authorization
proxy_cache_bypass 1;
# This backend endpoint always returns 401, with a cache header set to the expire date of the token
proxy_pass http://127.0.0.1:1234/auth/_Internal/Logout;
proxy_set_header Host ........;
proxy_pass_request_body off;
}
.
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!