Emitir tokens seguros que no requieren ningún estado del lado del servidor

2

El problema

Actualmente estoy diseñando el backend para un SPA (aplicación de una sola página), que planeo construir de una manera bastante REST. El backend idealmente será solo una capa delgada entre el cliente y la base de datos. Casi todos los datos de la base de datos estarán relacionados con un usuario específico, por lo que necesito algún tipo de sistema de autenticación.

Dado que vivimos en un universo donde los humanos son bastante predecibles y las máquinas pueden ser realmente realmente , obviamente, tenemos que hacer que el hashing de contraseñas y la verificación de contraseñas sean lentas (normalmente se utilizan esquemas de estiramiento de claves como bcrypt o todo eso) ). Todo esto está bien y es excelente, pero nos complica la vida a las pobres almas que desean diseñar aplicaciones rápidas y rápidas utilizando un backend RESTful, ya que idealmente ese backend no tendría que almacenar ningún dato de sesión, sino simplemente autenticar cada solicitud individualmente ( utilizando, por ejemplo, la autenticación de acceso básico).

Sin embargo, esto significaría cambiar la contraseña de los usuarios para cada solicitud, lo que agregaría una penosa pena para cada solicitud (y posiblemente facilitaría los ataques DDoS). Por supuesto, este problema se puede resolver mediante el almacenamiento en caché de las credenciales de los usuarios en la memoria en el backend, pero esa solución no se siente muy limpia y presenta otros problemas, como manejar la retirada de la caché y obligar al cliente a almacenar las credenciales en texto sin formato.

En resumen, mi problema es que necesito algún tipo de sistema para manejar la autenticación de los usuarios de forma ágil, pero idealmente aún puedo evitar cualquier estado del lado del servidor.

Mi solución propuesta

En primer lugar, el tráfico entre el cliente y el servidor se cifrará utilizando el estándar TSL bog, por lo que no deberíamos ser vulnerables a los intrusos ni a los ataques de intermediarios.

La solución en la que he pensado es emitir un token para el cliente, después de la autenticación inicial exitosa usando las credenciales enviadas en texto sin formato a través de TSL, que contiene la información necesaria para que el servidor autentique a un usuario. Este token se calcularía de la siguiente manera:

key = a random key generated when the server starts, never to be shared
nonce = just some random bytes grabbed out of the air

token = nonce + timestamp + user_id + HMAC(key, nonce + timestamp + user_id)

Esto permitiría al servidor verificar si el token es válido simplemente validando el HMAC para cada solicitud, lo cual es muy barato de hacer (casi tan barato como hacer una búsqueda en una tabla hash, pero evitando por completo la necesidad de una tabla de hash en primer lugar). Si el token es válido y no ha caducado (no se debe permitir que la marca de tiempo sea demasiado antigua) Dejo que la solicitud continúe, y dejo que la base de datos maneje el problema de autorización.

Esto se siente como una manera de autenticar a los usuarios de manera eficiente y segura, además de requerir sorprendentemente pocos LoCs para implementar, así como evitar totalmente cualquier tipo de estado en el servidor (la aplicación del servidor usaría una cantidad constante de memoria durante toda su vida útil) ).

Mi verdadera pregunta

Ahora bien, este esquema puede parecer totalmente inestable a primera vista, pero después de verlo por un momento, veo dos posibles problemas con él que generan preguntas:

  1. No hay forma de retirar un token, excepto esperar a que caduque. ¿Es esto realmente un problema o soy yo paranoico? Al ver que debe almacenarse en el lado del cliente, la seguridad se ve comprometida de inmediato si alguna parte maliciosa obtiene acceso a un cliente antes de que caduque el token. Claro, el peligro está contenido en el intervalo de tiempo durante el cual el token es válido, pero para este SPA ese intervalo de tiempo puede contarse en días.

  2. ¿Cómo escalamos? La clave del lado del servidor secreto tendría que ser compartida entre todos los servidores del clúster, pero ¿cómo lo haríamos de manera segura, y cuándo deberíamos retirar una clave del lado del servidor?

También puede haber otros problemas, pero estos son los dos que se destacaron más que yo. ¿Qué opinas sobre este esquema y estos problemas?

Tenga en cuenta que esta pregunta no es una pregunta general sobre la generación segura de tokens o la administración de sesiones (como se ha respondido muchas veces aquí), sino sobre los problemas periféricos relacionados con este esquema específico, problemas que no he visto discutidos.

    
pregunta Fors 01.06.2015 - 15:04
fuente

2 respuestas

1

La implementación de un cifrado sólido agrega una penalización de rendimiento, pero debería ser despreciable para un usuario humano. Considere un viaje de ida y vuelta desde el navegador web a la base de datos, tomando 600 ms sin cifrado frente a 700 ms. Creo que el incremento de 100 ms es una exageración, pero ilustra el punto que trato de hacer, un humano no es capaz de distinguir la diferencia en 100 ms. También puede implementar técnicas de rendimiento mejoradas, como mostrar una imagen giratoria para mostrar que algo está sucediendo de hecho.

En cuanto a ataques específicos:

  • Fuerza bruta: el retraso realmente te ayuda, un atacante necesitaría un siglo adicional para terminar de procesar la máscara
  • DoS y DDoS: los analizadores de paquetes y el mecanismo de control de inundaciones deben colocarse de todos modos, y estas medidas son mitigantes pero no métodos de evitación; en estos casos, debe aceptar un cierto nivel de riesgo.

No lo has mencionado, pero ¿qué sucede cuando caduca un token? ¿El usuario está obligado a volver a iniciar sesión? Si no es así, entonces la incapacidad de caducar un token plantea un mayor riesgo, ya que cualquier persona que pueda robar ese token puede prevalecer en la "sesión" mientras lo necesite. Sin embargo, esta es la forma en que funciona la mayoría del software; le dan la opción al usuario de cerrar sesión y asumen que permanecen conectados durante un cierto período de inactividad, renovando el tiempo de caducidad con cada solicitud. Entonces, para su caso, ejecutaría un par de casos de uso y determinaría la caducidad ideal de la sesión, y ajustaría el tiempo de espera en consecuencia. (Nota: asegúrese de probar que el token y otras credenciales temporales se destruyen tanto en el cliente como en el servidor).

Otra opción es forzar una renovación del token de vez en cuando, posiblemente transparente para el usuario (no requiere autenticación manual), sino simplemente renovando el token. Esto invalidaría cualquier sesión duplicada con la desventaja de que los usuarios pueden abrir más de una pestaña y puede dificultar la experiencia del usuario.

En cuanto a la escala, puede pegar una sesión a un servidor específico, esto le permitiría usar diferentes claves en su sistema

    
respondido por el Purefan 01.06.2015 - 15:53
fuente
0

Creo que no es una buena idea diseñar tu propio algoritmo de generación de token a menos que realmente sepas lo que estás haciendo. Puede consultar esta biblioteca: enlace

  1. Generalmente, las aplicaciones web funcionan de todos modos, pero aún puede tener un diccionario de tokens invalidados manualmente en la memoria para verificar en un servicio centralizado antes de reenviar las solicitudes a su aplicación.

  2. ¿Por qué no usar sesiones pegajosas para evitar compartir?

respondido por el fips 01.06.2015 - 16:34
fuente

Lea otras preguntas en las etiquetas