De acuerdo, ha decidido convertir su antigua y obsoleta aplicación monolítica en una arquitectura de microservicios más moderna y eficiente.
Uno de los primeros problemas que surgirán proviene del hecho de que una arquitectura distribuida significa que sus pequeñas piezas ya no están atadas entre sí, tendrán que comunicarse entre sí a través de un patrón de comunicación llamado REST, y ya no lo harán. compartir datos en memoria entre ellos.
Bueno, simplemente sucede que uno de los datos más importantes para toda su aplicación se mantiene en la memoria y se comparte en todos los módulos de su aplicación: la sesión del usuario .
Cuando investiga un poco sobre la autenticación y autorización de microservicios, surge una tecnología como la mejor, si no la única, solución: JWT.
Básicamente, este enfoque sugiere que coloque todos los datos de su sesión en un hash firmado/encriptado (el token) y lo envíe de vuelta al cliente que ha iniciado sesión en su aplicación.
Entonces, con cada solicitud, el cliente devolverá ese token (generalmente en el encabezado de la solicitud), y luego puede verificar la autenticidad del token y extraer los datos de la sesión.
Datos de la sesión en manos, puede enviarlos a cualquier servicio que necesite hasta que cumpla con esa solicitud. Puede configurar una función sin servidor para descifrar y verificar la firma del token, o incluso delegar esta tarea en su API Gateway.
Esto se ve tan limpio y elegante al principio, pero espera un minuto... se ve bastante más complejo de lo que solía ser, ¿verdad? Echemos un vistazo a cómo solía funcionar con nuestra aplicación monolith y por qué tuvo que cambiar tan drásticamente.
Durante bastante tiempo, las sesiones de usuario HTTP se han almacenado en la memoria del servidor, indexadas por un hash generado aleatoriamente sin significado; incluso ha surgido el término "token opaco" para identificar un token sin datos. Luego, este token sin datos se envía de vuelta al navegador, y luego el servidor le pide amablemente al navegador que lo almacene en una Cookie .
Por naturaleza, las cookies se envían automáticamente al servidor con cada solicitud, por lo que después de iniciar sesión, su próxima solicitud al servidor contendrá la cookie, que a su vez contendrá el token necesario para recuperar los datos de usuario respectivos de la memoria del servidor.
Este enfoque se ha utilizado durante más de una década por muchos servidores de aplicaciones de nivel empresarial y se considera seguro.
Pero ahora tenemos microservicios, por lo que ya no podemos confiar en esto. Todos los servicios están aislados entre sí, por lo que no tenemos un lugar común para almacenar estos datos.
La solución entonces, como se propone, es enviar estos datos al cliente, dejar que se quede con los datos y devolverlos cuando sea necesario. Esto plantea una gran cantidad de problemas que no teníamos antes, e intentaré describir algunos de ellos ahora:
La mayoría de las implementaciones sugieren que envíe el token de regreso al servidor usando un encabezado HTTP llamado "Autorización". Mientras lo hace, su cliente debe tener la capacidad de recibir, almacenar y recuperar el token.
Si su cliente tiene acceso a estos datos, cualquier código malicioso también lo tiene. En posesión del token, cualquier atacante puede intentar descifrarlo para acceder a los datos que contiene, o simplemente usarlo para acceder a la aplicación.
En el monolito, el servidor simplemente devolvió el token opaco en un encabezado SetCookie, por lo que el código del cliente no tuvo que lidiar con la información porque el navegador lo hace automáticamente. Y la cookie se puede configurar de manera que ni siquiera se pueda acceder a ella mediante javascript, por lo que el código malicioso no puede alcanzarla.
Para mantener todo seguro, debe firmar el token ejecutando un algoritmo de cifrado para que nadie altere los datos y también cifrarlo para asegurarse de que nadie pueda leerlo.
Además, con cada solicitud recibida, sus servidores deberán ejecutar un descifrado y verificar la firma ejecutando otro cifrado. Todos sabemos que los algoritmos de encriptación son costosos en términos de computación, y nada de este encriptado/desencriptado tuvo que ocurrir con nuestro antiguo monolito, salvado por la única ejecución cuando tiene que comparar las contraseñas para iniciar sesión, que también debe ejecutar cuando utilizando JWT.
Los JWT no son los mejores para rastrear el vencimiento de la sesión por inactividad. Una vez que emite un token, es válido hasta su propio vencimiento, que se establece dentro del token. Entonces, emite un nuevo token con cada solicitud, o emite otro token llamado Refresh Token con un vencimiento más largo y lo usa solo para obtener un nuevo token después de que su token expire.
Como se habrá dado cuenta, esto simplemente coloca el mismo problema en otro lugar: la sesión caducará tan pronto como caduque el token de actualización, a menos que también lo actualice.
Como puede ver, la solución propuesta trae consigo una gran cantidad de problemas sin resolver. Pero, ¿cómo podemos lograr una gestión de sesiones de usuario eficaz y segura en una arquitectura de microservicios?
Recuerde el problema real: la sesión del usuario solía almacenarse en la memoria del servidor, y muchos servidores empresariales podrían replicar esta parte de la memoria entre todas sus instancias en un clúster, por lo que sería accesible sin importar qué.
Pero ahora ya casi no tenemos servidores empresariales, ya que muchos módulos de microservicios son aplicaciones java / node / python / go / (inserte su tecnología aquí) independientes. ¿Cómo pueden compartir una sola porción de memoria?
En realidad, es bastante simple: agregue un servidor de sesión central .
La idea aquí es mantener los datos de la sesión de la misma manera que antes: cree un token opaco para usar como clave, y luego puede agregar tantos datos como desee para que esa clave los indexe.
Lo hace en un lugar que es central y accesible para todos los microservicios en su red, por lo que cada vez que alguno de ellos necesite los datos, dichos datos están a solo una llamada de distancia.
La mejor herramienta para este trabajo es Redis . Redis es una base de datos de clave-valor en memoria con una latencia de submilisegundos. Sus microservicios pueden leer los datos de la sesión del usuario como si estuvieran almacenados directamente en su propia memoria (bueno, casi, pero es rápido).
Además, Redis tiene una función que es clave para esta aplicación: puede establecer un tiempo de espera para un par clave-valor, de modo que tan pronto como expire el tiempo, el par se eliminará de la base de datos. El tiempo de espera se puede restablecer emitiendo un comando. Suena exactamente como el tiempo de espera de la sesión, ¿verdad?
Para que esto funcione, necesitarás dos cosas:
Deberá crear un microservicio responsable de la autenticación de sus usuarios. Recibirá la solicitud con el nombre de usuario y la contraseña, verificará si la contraseña es correcta y luego creará los datos de la sesión en Redis.
Tendrá que generar el token opaco, recuperar los datos del usuario de su base de datos y luego almacenar el token y los datos en Redis. Además, una vez hecho esto, deberá devolver el token al cliente que solicitó el inicio de sesión, preferiblemente en un encabezado SetCookie si el cliente es un navegador web.
Prefiero que este módulo se asiente dentro de cada microservicio, pero también puede configurarlo en su API Gateway si lo desea. Su responsabilidad es buscar la solicitud y extraer el token opaco de ella, luego comunicarse con Redis para recuperar los datos de la sesión del usuario y ponerlos a disposición del módulo que procesará esa solicitud.
Como puede ver, la solución es mucho más simple, rápida y segura que usar JWT para el control de la sesión del usuario. Pero ten en cuenta estos:
Espero que esto ayude.
Como beneficio adicional, aquí hay un SessionManager para ayudar con la implementación en Java utilizando el generador de tokens de Jedis y Tomcat, generalmente incluido en Spring Boot.