Servicio web RESTful: ¿cómo autenticar solicitudes de otros servicios?

117

Estoy diseñando un servicio web RESTful al que deben acceder los usuarios, pero también otros servicios y aplicaciones web. Todas las solicitudes entrantes deben estar autenticadas. Toda la comunicación se realiza a través de HTTPS. La autenticación de usuario funcionará en función de un token de autenticación, adquirido mediante la POST del nombre de usuario y la contraseña (a través de una conexión SSL) a un recurso de sesión proporcionado por el servicio.

En el caso de los clientes de servicios web, no hay un usuario final detrás del servicio al cliente. Las solicitudes se inician mediante tareas programadas, eventos o algunas otras operaciones informáticas. La lista de servicios de conexión se conoce de antemano (obviamente, supongo). ¿Cómo debo autenticar estas solicitudes provenientes de otros servicios (web)? Quiero que el proceso de autenticación sea lo más fácil posible de implementar para esos servicios, pero no a costa de la seguridad. ¿Cuáles serían los estándares y las mejores prácticas para un escenario como este?

Opciones en las que puedo pensar (o que me han sugerido):

  1. Haga que los servicios al cliente recurran a tener un nombre de usuario y contraseña "falsos", y autentíquelos de la misma manera que los usuarios. No me gusta esta opción, simplemente no se siente bien.

  2. Asigne una identificación de aplicación permanente para el servicio del cliente, posiblemente también una clave de aplicación. Por lo que he entendido, esto es lo mismo que tener nombre de usuario + contraseña. Con esta identificación y clave, puedo autenticar cada solicitud o crear un token de autenticación para autenticar más solicitudes. De cualquier manera, no me gusta esta opción, porque cualquiera que pueda obtener la identificación y la clave de la aplicación puede hacerse pasar por el cliente.

  3. Podría agregar una verificación de dirección IP a la opción anterior. Esto dificultaría la realización de solicitudes falsas.

  4. Certificados de cliente. Configurar mi propia autoridad de certificación, crear un certificado raíz y crear certificados de cliente para los servicios del cliente. Sin embargo, me vienen a la mente un par de cuestiones: a) ¿cómo puedo permitir que los usuarios se autentiquen sin certificados yb) qué tan complicado es implementar este escenario desde el punto de vista del servicio al cliente?

  5. Algo más, ¿debe haber otras soluciones por ahí?

Mi servicio se ejecutaría en Java, pero omití deliberadamente información sobre el marco específico en el que se construiría, porque estoy más interesado en los principios básicos y no tanto en los detalles de implementación; supongo que la mejor solución para esto será ser posible de implementar independientemente del marco subyacente. Sin embargo, soy un poco inexperto en este tema, por lo que también apreciaré mucho los consejos y ejemplos concretos sobre la implementación real (como bibliotecas útiles de terceros, artículos, etc.).

Tommi
fuente
Si se me permite sugerirlo, familiarícese con los servicios de sitios web de gran tamaño y elija lo que le guste. Sus usuarios también encontrarán similitudes con las mejores prácticas de otros servicios RESTful.
Yzmir Ramirez
Encontré otra pregunta (casi dos años) que toca un tema similar: stackoverflow.com/questions/1138831/…
Tommi
¿En qué sistema operativo están alojados los servicios (tanto la web como los demás)? ¿Se ejecutan en servidores que forman parte de la misma infraestructura?
Anders Abel
El sistema operativo puede variar: Win, * nix, etc. Y los servicios al cliente pueden o no estar dentro de la misma infraestructura que mi servicio.
Tommi

Respuestas:

34

Cualquier solución a este problema se reduce a un secreto compartido. Tampoco me gusta la opción de nombre de usuario y contraseña codificados, pero tiene la ventaja de ser bastante simple. El certificado de cliente también es bueno, pero ¿es realmente muy diferente? Hay un certificado en el servidor y otro en el cliente. Su principal ventaja es que es más difícil utilizar la fuerza bruta. Sin embargo, es de esperar que tenga otras protecciones para protegerse contra eso.

No creo que su punto A para la solución de certificado de cliente sea difícil de resolver. Solo usa una rama. if (client side certificat) { check it } else { http basic auth }No soy un experto en Java y nunca he trabajado con él para hacer certificados del lado del cliente. Sin embargo, un rápido Google nos lleva a este tutorial que busca su callejón.

A pesar de toda esta discusión sobre "lo que es mejor", permítanme señalar que hay otra filosofía que dice, "menos código, menos inteligencia es mejor". (Yo personalmente sostengo esta filosofía). La solución de certificado de cliente parece mucho código.

Sé que expresó preguntas sobre OAuth, pero la propuesta de OAuth2 sí incluye una solución a su problema llamada " tokens de portador " que deben usarse junto con SSL. Creo que, en aras de la simplicidad, elegiría el usuario / pase codificado de forma rígida (uno por aplicación para que se puedan revocar individualmente) o los tokens de portador muy similares.

newz2000
fuente
27
Los certificados de cliente NO son un secreto compartido. Por eso existen. El cliente tiene una clave privada y el servidor tiene una clave pública. El cliente nunca comparte su secreto y la clave pública no es un secreto.
Tim
5
El enlace del tutorial no conduce a un artículo de tutorial, sino a alguna página de índice de Java en el sitio de Oracle ...
Marjan Venema
2
@MarjanVenema Bueno, eso es porque estás probando el enlace 2 años después de la respuesta de newz2000, pero siempre puedes probar WayBack Machine: web.archive.org/web/20110826004236/http://java.sun.com/…
Fábio Duque Silva
1
@MarjanVenema: Lo siento, pero ¿espera que newz2000 venga aquí y actualice el enlace después de que falle? Como dijiste, es link rot, así que tarde o temprano sucede. O intentas acceder al archivo para ver qué estaba viendo el autor en ese momento, o encuentras el nuevo enlace y haces una contribución positiva. No veo cómo su comentario ayudó a nadie. Pero aquí, siga este enlace: oracle.com/technetwork/articles/javase/… (tenga en cuenta que eventualmente también se pudrirá )
Fábio Duque Silva
2
@ FábioSilva: No. No espero que él haga eso. Leí el archivo, pero no tuve tiempo de buscar un nuevo enlace, así que hice la siguiente mejor opción: poner un comentario de que está muerto para que alguien más de la comunidad pueda encontrar la nueva ubicación y actualizar la enviar. Como obviamente tuviste tiempo para encontrar el nuevo enlace, ¿por qué no actualizaste el enlace en la publicación en lugar de ponerlo en un comentario que me molesta?
Marjan Venema
36

Después de leer su pregunta, diría, genere un token especial para realizar la solicitud requerida. Este token vivirá en un tiempo específico (digamos en un día).

Aquí hay un ejemplo de para generar un token de autenticación:

(day * 10) + (month * 100) + (year (last 2 digits) * 1000)

por ejemplo: 3 de junio de 2011

(3 * 10) + (6 * 100) + (11 * 1000) = 
30 + 600 + 11000 = 11630

luego concatenar con la contraseña de usuario, ejemplo "my4wesomeP4ssword!"

11630my4wesomeP4ssword!

Luego haz MD5 de esa cadena:

05a9d022d621b64096160683f3afe804

Cuando llames a una solicitud, usa siempre este token,

https://mywebservice.com/?token=05a9d022d621b64096160683f3afe804&op=getdata

Este token es siempre único todos los días, por lo que creo que este tipo de protección es más que suficiente para proteger siempre nuestro servicio.

La esperanza ayuda

:)

kororo
fuente
1
Realmente me gusta la forma en que agregaste el token de seguridad en cada solicitud, pero ¿qué sucede con esto cuando el programador ya creó cientos de páginas jsp y luego tiene que implementar la seguridad en las 100 páginas creadas previamente, así como las páginas que se van a crear? En ese caso, agregar un token en cada solicitud no es una elección correcta. De todos modos, +1 para su técnica. :)
Ankur Verma
4
¿Qué pasa si los relojes no están sincronizados? ¿No generará el cliente el token incorrecto en esta instancia? Incluso si ambos generan la fecha y hora en UTC, sus relojes aún podrían ser diferentes, lo que da como resultado una ventana de tiempo todos los días, ¿cuando el token no funcionaría?
NickG
@NickG, tuve este problema antes, la única forma de asegurarlo solicitando la hora del servidor. Esto eliminará en un 99% el problema de UTC. Por supuesto, la desventaja es una llamada adicional al servidor.
kororo
pero todavía puedo consumir su servicio web usando este token por un día, ¿verdad? No veo cómo esto ayudará
Mina Gabriel
@MinaGabriel puede agregar más marco de tiempo en la generación de tokens. (minuto * 10) + (hora * 100) + (día * 1000) + (mes * 10000) + (año (últimos 2 dígitos) * 100000)
kororo
11

Hay varios enfoques diferentes que puede tomar.

  1. Los puristas de RESTful querrán que use la autenticación BÁSICA y envíe credenciales en cada solicitud. Su razón fundamental es que nadie almacena ningún estado.

  2. El servicio al cliente podría almacenar una cookie, que mantiene una identificación de sesión. Personalmente, no encuentro esto tan ofensivo como algunos de los puristas de quienes escucho; puede ser costoso autenticarse una y otra vez. Sin embargo, parece que no te gusta mucho esta idea.

  3. A partir de su descripción, realmente parece que podría estar interesado en OAuth2. Mi experiencia hasta ahora, por lo que he visto, es que es un poco confuso y un poco vanguardista. Hay implementaciones por ahí, pero son pocas y distantes entre sí. En Java, entiendo que se ha integrado en los módulos de seguridad de Spring3 . (Su tutorial está muy bien escrito). He estado esperando para ver si habrá una extensión en Restlet , pero hasta ahora, aunque se ha propuesto y puede estar en la incubadora, todavía no se ha incorporado por completo.

jwismar
fuente
No tengo nada en contra de la opción 2, creo que es una buena solución en una aplicación RESTful, pero ¿de dónde obtiene el servicio al cliente el token en primer lugar? ¿Cómo se autentican la primera vez? Tal vez esté pensando mal, pero parece extraño que el servicio al cliente necesite tener su propio nombre de usuario y contraseña para esto.
Tommi
Si el usuario final es un usuario suyo, entonces el servicio intermediario puede pasarle sus credenciales en la primera solicitud, y usted puede devolver una cookie u otro token.
jwismar
De manera similar, en el escenario de OAuth, el usuario final está delegando al servicio intermediario su acceso a su servicio web.
jwismar
Parece haber un malentendido: no hay ningún usuario final detrás del servicio al cliente . He actualizado mi pregunta para explicar mejor la situación.
Tommi
1
Solo agregaría que la opción n. ° 1 enumerada anteriormente solo debe realizarse a través de HTTPS.
mr-sk
3

Creo que el enfoque:

  1. Primera solicitud, el cliente envía identificación / contraseña
  2. Intercambiar id / pase por token único
  3. Validar el token en cada solicitud posterior hasta que expire

es bastante estándar, independientemente de cómo lo implemente y otros detalles técnicos específicos.

Si realmente quiere ir más allá, tal vez podría considerar la clave https del cliente en un estado temporalmente inválido hasta que las credenciales sean validadas, limitar la información si nunca lo están y otorgar acceso cuando se validan, nuevamente en base a la expiración.

Espero que esto ayude

Dynrepsys
fuente
3

En lo que respecta al enfoque del certificado de cliente, no sería muy difícil de implementar y, al mismo tiempo, permitir la entrada a los usuarios sin certificados de cliente.

Si de hecho creara su propia Autoridad de Certificación autofirmada y emitiera certificados de cliente para cada servicio de cliente, tendría una manera fácil de autenticar esos servicios.

Dependiendo del servidor web que esté utilizando, debe haber un método para especificar la autenticación del cliente que aceptará un certificado de cliente, pero no lo requiere. Por ejemplo, en Tomcat al especificar su conector https, puede establecer 'clientAuth = want', en lugar de 'true' o 'false'. Luego, debe asegurarse de agregar su certificado CA autofirmado a su almacén de confianza (de forma predeterminada, el archivo cacerts en el JRE que está utilizando, a menos que haya especificado otro archivo en la configuración de su servidor web), por lo que los únicos certificados confiables serían los emitidos fuera de su CA autofirmado.

En el lado del servidor, solo permitiría el acceso a los servicios que desea proteger si puede recuperar un certificado de cliente de la solicitud (no nulo) y pasa cualquier verificación de DN si prefiere alguna seguridad adicional. Para los usuarios sin certificados de cliente, aún podrían acceder a sus servicios, pero simplemente no tendrán certificados presentes en la solicitud.

En mi opinión, esta es la forma más 'segura', pero ciertamente tiene su curva de aprendizaje y sus gastos generales, por lo que puede que no sea necesariamente la mejor solución para sus necesidades.

bobz32
fuente
3

5. Algo más: ¿debe haber otras soluciones por ahí?

Tienes razón, ¡la hay! Y se llama JWT (JSON Web Tokens).

JSON Web Token (JWT) es un estándar abierto (RFC 7519) que define una forma compacta y autónoma de transmitir información de forma segura entre las partes como un objeto JSON. Esta información puede ser verificada y confiable porque está firmada digitalmente. Los JWT se pueden firmar usando un secreto (con el algoritmo HMAC) o un par de claves pública / privada usando RSA.

Recomiendo encarecidamente buscar JWT. Son una solución mucho más simple al problema en comparación con soluciones alternativas.

https://jwt.io/introduction/

justin.hughey
fuente
1

Puede crear una sesión en el servidor y compartirla sessionIdentre el cliente y el servidor con cada llamada REST.

  1. Primera solicitud REST autenticar: /authenticate. Devuelve la respuesta (según el formato de su cliente) con sessionId: ABCDXXXXXXXXXXXXXX;

  2. Almacenar esta sessionIden Mapla sesión real. Map.put(sessionid, session)o utilizar SessionListenerpara crear y destruir claves para usted;

    public void sessionCreated(HttpSessionEvent arg0) {
      // add session to a static Map 
    }
    
    public void sessionDestroyed(HttpSessionEvent arg0) {
      // Remove session from static map
    }
    
  3. Obtenga sessionid con cada llamada REST, como URL?jsessionid=ABCDXXXXXXXXXXXXXX(o de otra manera);

  4. Recuperar HttpSessiondel mapa usando sessionId;
  5. Validar la solicitud para esa sesión si la sesión está activa;
  6. Enviar respuesta o mensaje de error.
arviarya
fuente
0

Usaría una aplicación para redirigir a un usuario a su sitio con un parámetro de identificación de la aplicación, una vez que el usuario aprueba la solicitud, genera un token único que la otra aplicación usa para la autenticación. De esta manera, las otras aplicaciones no manejan las credenciales de usuario y los usuarios pueden agregar, eliminar y administrar otras aplicaciones. Foursquare y algunos otros sitios se autentican de esta manera y es muy fácil de implementar como la otra aplicación.

Devin M
fuente
Mmm, no estoy seguro de poder seguir la explicación. ¿De qué usuario estamos hablando? Estoy hablando de una aplicación que se comunica con otra aplicación. Supongo que entendiste esto, pero todavía no puedo entenderlo. ¿Qué sucede cuando este "token" caduca, por ejemplo?
Tommi
Bueno, el token que genera y envía de vuelta a la otra aplicación es un token persistente, está vinculado al usuario y a la aplicación. Aquí hay un enlace a los documentos de foursquares developer.foursquare.com/docs/oauth.html y básicamente es oauth2, así que busque una buena solución de autenticación.
Devin M
Parece haber un malentendido: no hay ningún usuario final detrás del servicio al cliente . Sin embargo, la documentación de foursquare a la que vinculó menciona brevemente el acceso sin usuarios, por lo que fue al menos algo útil, ¡gracias! Pero todavía no puedo formarme una imagen completa de cómo funcionaría en la realidad.
Tommi
Simplemente genere claves para las aplicaciones y luego, si todo lo que está haciendo es permitir el acceso a las aplicaciones, entonces un simple application_id y application_key deberían funcionar para la autenticación. Si desea que se autentiquen con un token, mire el uso de las opciones de autenticación de token de devise, ya que solo sería un parámetro que se pasa con la solicitud de URL a su aplicación.
Devin M
¿Pero no es esto exactamente lo mismo que el escenario de nombre de usuario + contraseña = token de autenticación de sesión entonces? ¿No son application_id y application_key solo sinónimos de nombre de usuario y contraseña? :) Está perfectamente bien si esta es realmente la práctica estándar para una situación como esta, como dije, no tengo experiencia en esto, pero pensé que podría haber otras opciones ...
Tommi
-3

Además de la autenticación, le sugiero que piense en el panorama general. Considere hacer su servicio RESTful backend sin ninguna autenticación; luego coloque un servicio de capa intermedia requerido de autenticación muy simple entre el usuario final y el servicio de backend.

Dagang
fuente
¿Entre el usuario final y el servicio de backend? ¿No dejaría eso a los servicios al cliente sin autenticar por completo? No es lo que quiero. Por supuesto, puedo colocar esa capa intermedia entre mi servicio web y los servicios al cliente, pero eso aún deja la pregunta abierta: ¿cuál sería el modelo de autenticación real?
Tommi
La capa intermedia puede ser un servidor web como Nginx, puede realizar la autenticación allí. El modelo de autenticación puede basarse en sesiones.
Dagang
Como he intentado explicar en mi pregunta, no quiero un esquema de autenticación basado en sesiones para estos servicios de cliente. (Por favor, vea mis actualizaciones a la pregunta.)
Tommi
Le sugiero que use una lista de IP blanca o un rango de IP en lugar de nombre y contraseña. Por lo general, la IP del servicio al cliente es estable.
Dagang