Opciones de autenticación para sistemas distribuidos

9

Estoy en el proceso de diseñar 3 componentes que funcionarán en sinfonía entre ellos:

  • Un servicio web RESTful que requiere BasicAuthmás de HTTPS en todas las llamadas, y que es lo que realmente hace todo el trabajo pesado para mi sistema (hace el trabajo)
  • Una interfaz de usuario web que traduce las acciones del usuario final en llamadas API al servicio web mencionado anteriormente; por lo tanto, la interfaz de usuario está "respaldada" por el WS
  • Una herramienta de interfaz de línea de comandos (CLI) que los desarrolladores pueden instalar y ejecutar localmente, que también traduce comandos en llamadas API a la WS (por lo tanto, también está "respaldada por" la WS)

Uno de los primeros obstáculos que estoy tratando de cruzar es con respecto a la autenticación y autorización.

Supongamos que WS utiliza un servicio de directorio / LDAP (como AD o quizás Apache DS) como su dominio de autenticación. Es decir, cuando se recibe una llamada API por cable (por ejemplo, un HTTPS GETrecurso), las BasicAuthcredenciales se extraen de la solicitud y se reenvían al servicio LDAP para determinar si se trata de un usuario válido o no. Si están autenticados, digamos que se utiliza un dominio de autorización separado, tal vez una base de datos, para determinar si el usuario identificado puede hacer lo que está intentando en la solicitud HTTPS. Hasta aquí todo bien.

En el caso de la herramienta CLI, el usuario tendrá que autenticarse antes de ejecutar cualquier comando, por lo que este modelo funciona bien, ya que un solo usuario solo operará la misma instancia de CLI en un momento dado.

El problema surge cuando intentamos integrar la aplicación web (UI) con el WS, porque muchas personas pueden iniciar sesión en la aplicación al mismo tiempo, todos con diferentes permisos que dictan qué llamadas API subyacentes se les permite hacer.

Por lo que veo, parece que tengo solo 4 opciones aquí:

  • Credenciales en caché : después de iniciar sesión en la aplicación, las credenciales se almacenan de alguna manera en caché (de modo que la aplicación tenga acceso a ellas), y la aplicación no aplica ningún tipo de política de autorización. Cuando los usuarios intentan hacer cosas que generan llamadas API bajo el capó, sus credenciales se buscan desde la caché y se reenvían con las llamadas API. Si el WS determina que no están autorizados, devuelve un error.
  • Cuentas de nivel de servicio : la aplicación y el WS usan los mismos reinos de autenticación / autorización, excepto que la interfaz de usuario web ahora impone la autorización sobre lo que los usuarios pueden ver y hacer dentro de la aplicación. Si se les permite hacer algo que genera una llamada API subyacente, la aplicación envía las credenciales de la cuenta de servicio (por ejemplo myapp-admin-user) con cada llamada API en nombre del usuario.
  • OAuthv2 : No tengo idea de qué es OAuth o si es aplicable para este escenario, pero siento que puede ser una solución aquí de alguna manera.
  • Servidores de tokens: use un servidor de tokens como CAS o tal vez Kerberos para garantizar a los usuarios, de manera similar a como se comporta la opción Cuenta de nivel de servicio. Aquí, cuando un usuario inicia sesión con éxito en la aplicación, el servidor de tokens envía a la aplicación un UUID de sesión y también registra ese UUID con el WS. Cada vez que la aplicación genera una llamada API, agrega el UUID a la solicitud, que luego se valida en el lado de WS.

La opción " Credenciales almacenadas en caché " se siente como una aberración de todo lo que es bueno y saludable en la tierra de seguridad. Simplemente se siente mal almacenar las credenciales en cualquier lugar, nunca.

El " Token Servidor opción" parece válido para una configuración de tipo SSO, pero no en este caso en particular y se siente incómodo para mí. También creo que no hay una buena manera de usar el concepto UUID de sesión y BasicAuth / HTTPS al mismo tiempo.

Así que esto deja a OAuthv2, de la que no sé nada, y " Cuenta de nivel de servicio (SLA) * " como las únicas opciones restantes. La opción SLA parece estar bien, pero tiene algunos inconvenientes siguientes:

  • Requiere que la cuenta de servicio tenga básicamente "privilegios de Dios" sobre el WS. En otras palabras, una vez que la aplicación considera que un usuario puede hacer clic en un botón o hacer algo en la interfaz de usuario, eso se traduce en una llamada API incondicional por parte de la cuenta de servicio utilizada por la interfaz de usuario. Esto se siente mal, mkay?
  • Se me ocurre que mantener dos conjuntos de permisos (el conjunto de permisos para cada usuario de la aplicación, y luego el conjunto de permisos para la cuenta de servicio utilizada por la aplicación contra el WS) puede dar como resultado que los permisos no estén sincronizados entre sí de algun modo

Parece que realmente no tengo buenas opciones aquí. Seguramente no puedo ser el primer desarrollador en toparse con esto, pero preguntarle a los Dioses de Google no me ha ayudado mucho aquí. ¿Algunas ideas?

smeeb
fuente

Respuestas:

6

Hay muchas razones para no utilizar el esquema de autenticación básico para proteger los servicios de API web.

Para utilizar el servicio, el cliente debe mantener la contraseña en algún lugar en texto claro para enviarla junto con cada solicitud.

La verificación de una contraseña debe ser muy lenta (para contrarrestar los ataques de fuerza bruta), lo que dificultaría la escalabilidad de su servicio. Por otro lado, la validación de token de seguridad puede ser rápida (verificación de firma digital).

OAuth2 ofrece soluciones para cada uno de sus casos de uso. Su aplicación web puede usar la concesión de código , que le da un token de acceso que puede usar para hablar con su API.

Su aplicación web redirigirá el navegador del usuario al servidor de autorización. Solicitará al usuario credenciales (o tarjeta inteligente o código de autenticación de dos factores) y devolverá un código al navegador, que el cliente (su aplicación web) puede usar para obtener un token de acceso del servidor de autorización.

Su aplicación también recuperará un token de actualización con el que puede obtener un nuevo token de acceso si el token actual caduca.

Su aplicación CLI puede usar la concesión de credenciales del propietario del recurso . Solicitará al usuario credenciales y las enviará al servidor de autorización para adquirir un token de acceso y actualización. Una vez que su aplicación CLI tenga el token, puede descartar la contraseña del usuario en la memoria.

Ambos clientes (aplicación web y cliente de línea de comandos) deben registrarse por adelantado con el servidor de autorización.

Su servidor de autorización puede comunicarse con un servicio de directorio / LDAP (proveedor de identidad o IdP) para realizar la autenticación real.

Su servicio de API web solo necesita verificar el token JWT entrante y establecer lo que el usuario puede hacer (autorización).

Si eres víctima de un ataque de hombre en el medio y pierdes tu ficha de acceso, el atacante solo tiene un tiempo limitado (duración de la ficha) para usarla. Una contraseña suele ser válida por mucho más tiempo. Las fichas de actualización se pueden revocar en caso de que se pierdan.

MvdD
fuente
Gracias por la sólida entrada @ user18044 (+1). ¿Qué pasa con otros clientes HTTP para el servicio web? Este es un servicio web RESTful, por lo que cualquiera podría crear clientes para él. ¿Qué opción de OAuthv2 usarían para autenticar cada solicitud? ¿Igual que la aplicación web (concesión de código)? ¡Gracias de nuevo!
smeeb
También @ user18044, ¿puede confirmar que OAuthv2 solo maneja el aspecto de autenticación y no la autorización? Si es así, ¿cómo podría asignar cada solicitud autenticada (exitosa) a un conjunto de roles / permisos disponibles para el cliente?
smeeb
1
Dependiendo de cómo confíe en estos clientes, se autentican de manera diferente. Digamos, por ejemplo, que crea una aplicación móvil para su propia API, podría usar la concesión de credenciales de propietario de recursos. Pero si el cliente móvil es creado por un tercero (alguna aplicación de la tienda de aplicaciones), usaría la concesión implícita.
MvdD
1
@smeeb OAuth define ámbitos para indicar lo que el cliente puede hacer. Existen varios ámbitos predefinidos, pero puede definir los suyos propios (por ejemplo, para llamar a API específicas (consulte tools.ietf.org/html/rfc6749#section-3.3 )
MvdD
1
No te preocupes, de nada. Los ámbitos son realmente un mecanismo para determinar lo que un tercero puede hacer en su API. Por ejemplo, una aplicación de finanzas que solo puede leer el saldo de su cuenta en su sitio bancario. Pero dado que el token JWT contiene el nombre del usuario y, opcionalmente, incluso los roles, puede buscar en una base de datos lo que un usuario o usuario en un rol puede hacer. Así también lo está haciendo mi empresa.
MvdD
2

Estoy trabajando en un sistema algo similar en este momento, en realidad; Mentiría si dijera que conozco la forma "correcta" de hacer que esto funcione, ya que todavía estoy experimentando, pero tal vez repasar lo que he encontrado podría ayudar. La configuración está bastante inspirada en OAuth2 a pesar de sus inconvenientes , algunos de los cuales discutiré.

DESCARGO DE RESPONSABILIDAD: No soy un tipo de seguridad de oficio, y lo que he construido lo he construido con la ayuda de Google y tantos ejemplos como pude encontrar.

Cuando comencé a investigar cómo construiría la API web que respaldaría la (s) aplicación (es) del cliente, decidí que quería intentar hacer que la API fuera lo más sin estado posible. Una parte de mí estaba tentada a buscar la autenticación básica HTTP y hacer que los usuarios se autenticaran en cada solicitud, pero surgieron dos problemas que hicieron que esa solución no pareciera viable:

  1. La búsqueda para validar las credenciales es un gasto de tiempo no trivial, ya que implicaría al menos una llamada a la base de datos con cada solicitud
  2. El sistema es un sistema multiinquilino; identificar a cuáles de los inquilinos a los que pertenecía el usuario requeriría un tercer parámetro y eso no es compatible con la autenticación básica HTTP (1)

Las complejidades involucradas en la autenticación me hicieron optar por un sistema de tokens, donde el usuario haría una solicitud de autenticación a un punto final que emitiría un token de identificación y luego lo almacenaría en algún lugar donde el servidor podría usarlo para validar las solicitudes y vincularlo con algunos datos de usuario necesarios. No es perfectamente apátrida, y he estado mirando los tokens web JSON como un enfoque alternativo, pero la búsqueda de tokens se puede hacer muy rápido. (2)

Los clientes luego se quedan con ese token hasta que el servidor ya no acepta el token. Luego, el cliente intenta volver a autenticarse con el servidor y recuperar un nuevo token para autenticar futuras solicitudes. Esto es lo que su publicación hace referencia como la estrategia de credenciales en caché, y hemos optado por usarla porque nos permite mantener un mayor control sobre el acceso a la aplicación. Siempre que se pueda confiar en el cliente para mantener su propia información de autorización y solo se conecte a través de una conexión segura (forzamos el acceso solo HTTPS por esa razón), esa no es necesariamente una mala forma de manejar las cosas, aunque solo sea desde una perspectiva UX. Para el servicio web, realmente conservamos el token en el almacenamiento local del navegador; dado que es solo una identificación temporal y no una combinación de nombre de usuario / contraseña real del usuario, entonces hemos considerado esto "

Los tokens se envían a la API web como parte de un encabezado de autorización o como un parámetro GET para clientes donde los encabezados HTTP personalizados no están disponibles. Esto es importante porque permite una mayor flexibilidad en la forma en que accedemos a la API desde una amplia gama de posibles aplicaciones de clientes, muy parecidas a cómo necesita admitir una CLI y una aplicación web. Las fichas de portador son algo bastante común, pero no son exactamente perfectas . Sin embargo, las preocupaciones de seguridad de nuestra aplicación no son lo suficientemente importantes como para dedicar tiempo adicional a mejorar esto.

Una vez que se valida el token, entra en juego la autorización. Lo que eso implica puede variar ampliamente, pero en ese punto de la aplicación se conoce la identidad del usuario y, por lo tanto, un servicio de autorización de algún tipo solo debe darse a la identidad de ese usuario y el objeto / acción contra el cual verificar.

Por lo menos, si desea utilizar este tipo de estrategia, hay muchas bibliotecas diseñadas para implementar OAuth y OAuth2; a menos que sea como nosotros y tenga algunos requisitos muy extraños, le recomiendo utilizar una biblioteca de seguridad de terceros de confianza porque es muy probable que no haga las cosas bien la primera vez que lo intente. Todavía busco una alternativa de terceros para reemplazar nuestro sistema de autenticación actual porque sé que está lleno de agujeros y casos extremos que ni siquiera puedo comenzar a imaginar.


Notas al pie

  1. Esto no sería necesario si nuestro sistema se construyera de manera diferente, por ejemplo, usando diferentes puntos de entrada para cada cliente; alternativamente, también consideré ser inteligente y prefijar el nombre de usuario con un identificador de inquilino
  2. He tenido algunas ideas sobre cómo hacer que la cadena de tokens sea fácil de validar puramente computacionalmente en lugar de tener que hacer una búsqueda de E / S como un objetivo a largo plazo para mejorar; como mínimo, los tokens contienen un byte de versión que permitirá actualizaciones en el futuro si / cuando el proceso de decodificación cambia
moberemk
fuente