La mejor solución para autorizar que un usuario solo pueda modificar / actuar con sus propios recursos en una API REST


8

Antecedentes:

Actualmente en el proceso de construir una API REST, usando el nodo w / express y es consumida por una aplicación móvil y eventualmente un sitio web (basado en un navegador moderno).

Estoy tratando de identificar la mejor manera de autorizar la solicitud de actualización / acción de un usuario para que solo se les permita modificar sus propios recursos. Las acciones ocurrirán a una velocidad semi alta, de ahí la preocupación.

Nota: La propiedad de entidades no se puede transferir en este caso de uso.

Posibles soluciones:

Solución : almacene y mantenga una lista de los recursos de cada usuario en una sesión de servidor respaldada por algo como reddis.

Preocupación (es) : persistir en una sesión del lado del servidor tiene su propio conjunto de complejidades específicamente escalables con múltiples servidores. Esto también viola el REST. do-sessions-realmente-violate-restfulness para más información.

Solución: haga una consulta de lectura antes de la consulta de actualización / acción del usuario. IE me da los elementos de este usuario, entonces si está en la lista proceda con la actualización.

Preocupación (es): Sobrecarga de una lectura adicional cada vez que un usuario actúa en nombre de un recurso.

Solución: Pase la identificación de usuario a la capa db y hágalo parte de la actualización condicional o, si desea obtener más lujo, use algo como la seguridad de nivel de fila de Postgres para ese recurso, dependiendo del backend de datos.

Preocupación (es): esto parece un poco tarde en el ciclo de vida de la solicitud para verificar si el recurso es el usuario solicitante. El error tendría que ser lanzado desde el backend de datos. En la misma nota, también estaría un poco fuera de lugar teniendo en cuenta que la autenticación y la autorización basada en roles a menudo se realizan al comienzo del ciclo de vida de la solicitud. La implementación también depende del backend de datos. También empuja la lógica de negocios en el backend de datos.

Solución: tipo de sesión firmada por el cliente. Ya sea con un JWT o una cookie cifrada / firmada. Esencialmente, mantener una sesión confiable que contenga una lista de los identificadores de recursos del usuario.

Preocupación (es): el tamaño de la sesión del lado del cliente. El hecho de que se enviaría con cada solicitud, incluso cuando no sea necesario. El mantenimiento se vuelve extremadamente complejo cuando presenta la posibilidad de múltiples sesiones / clientes activos. ¿Cómo actualizaría el estado del lado del cliente cuando se agrega un recurso en otro cliente?

Solución: Pase un token de actualización firmado (JWT) o una url al cliente con el recurso cuando se recupera el recurso. Espere que cuando un recurso se actualice / accione. El token firmado contendría la identificación del usuario y la identificación del recurso y usted podría verificarlo fácilmente contra ellas.

Preocupación (es): se vuelve complejo si la propiedad del recurso es transferible, pero en mi caso eso no es una preocupación. Introduce la complejidad sobre una lectura antes de la actualización. ¿Es un poco extraño?

Pensamientos finales:

Me estoy inclinando hacia la última solución, pero debido a que no veo que suceda muy a menudo, me pregunto si me estoy perdiendo algo. o tal vez es parte de un patrón de diseño que no conozco.


1
Suponiendo que es posible transferir la propiedad, el último enfoque implicaría tener que verificar si ese usuario sigue siendo el propietario de dicho recurso. Entonces terminas teniendo el token más uno de los otros enfoques.
Miguel van de Laar

@MiguelvandeLaar ¡Gracias por la respuesta! tiene razón, sin embargo, en mi caso de uso, la propiedad no puede transferirse, bajo ninguna circunstancia.
Ashtonian

2
"una sesión de servidor" A menos que me falte algo sustancial, REST excluye las sesiones del lado del servidor.
Carreras de ligereza en órbita el

@BarryTheHatchet - Parece que eso es debatible, hay una discusión interesante aquí sobre eso: stackoverflow.com/questions/6068113/… . Esa solución también es duradera, ya que hace que la presentación de nuevos servidores de carga equilibrada sea divertida ...
Ashtonian

@Ashtonian: La respuesta aceptada de 131 puntos a esa pregunta está de acuerdo conmigo;) (al igual que el seguimiento de 223 puntos)
Lightness Races in Orbit

Respuestas:


1

Creo que hay dos soluciones en su lista que se ajustan mejor a su caso de uso; la consulta de lectura antes de la actualización (solución 2) y el envío de un token de actualización con la solicitud de lectura (solución 5).

Si le preocupa el rendimiento, decidiría una u otra dependiendo de cuántas lecturas vs actualizaciones de un recurso que espera. Si espera muchas más actualizaciones que lecturas, entonces, obviamente, la solución 5 es mejor, porque no necesita hacer una búsqueda adicional de la base de datos para averiguar el propietario del recurso.

Sin embargo, no debe olvidar las implicaciones de seguridad de entregar un token de actualización. Con la solución 2, suponiendo que la autenticación es segura, la actualización de un recurso probablemente también sea segura porque usted determina el propietario de un recurso en el servidor.

Con la solución 5, no verifica dos veces el reclamo que hace el cliente, excepto que verifica una firma válida. Si está permitiendo que la actualización se realice a través de un enlace de texto sin formato (p. Ej., Sin SSL), solo codificar el recurso y la identificación del usuario en el token no es seguro. Por un lado, te estás abriendo para repetir ataques, por lo que debes incluir un nonce que crece con cada solicitud. Además, si no codifica una marca de tiempo / fecha de vencimiento en el token de actualización, básicamente le da acceso de actualización indefinido a cualquiera que tenga ese token. Finalmente, si no desea el nonce, debe incluir al menos un hmac del recurso recuperado, de modo que el token de actualización solo sea válido para el estado del recurso tal como fue recuperado. Esto hace que los ataques de repetición sean más difíciles y limita aún más el daño que puede hacer el token de actualización,

Creo que incluso si se está comunicando a través de un enlace seguro, agregar un nonce (o al menos un hmac del estado del recurso) y una fecha de vencimiento para el token de actualización sería algo inteligente. No conozco el dominio de la aplicación, pero puede tratar con usuarios malintencionados y pueden causar todo tipo de estragos con el poder del acceso ilimitado a las actualizaciones de sus propios recursos.

Agregar un nonce significará una columna adicional por recurso en su base de datos donde almacenará el valor del nonce que envió con la última solicitud de recuperación. Puede calcular el hmac sobre la representación json serializada de su recurso. Luego puede enviar su token no como parte del cuerpo del mensaje sino como un encabezado HTTP adicional.

Ah, y usaría un token, no una URL; en REST, la URL de actualización para un recurso dado debe ser la misma URL desde la que se obtuvo el recurso, ¿verdad? Movería todas las cosas relacionadas con la autenticación y la autorización a los encabezados HTTP, por ejemplo, podría proporcionar el token de actualización en el encabezado de autorización de la solicitud PUT.

Tenga en cuenta que agregar un nonce que crece con cada recuperación de recursos también requerirá una actualización de la base de datos (el nuevo valor para el nonce) para cada solicitud de recuperación de recursos (aunque puede salirse con la actualización solo de nonce cuando el estado del recurso realmente cambie , por lo que estaría libre desde el punto de vista del rendimiento), a menos que desee mantener esa información en la memoria transitoria y simplemente hacer que los clientes vuelvan a intentarlo cuando su servidor se reinicie entre una búsqueda y una solicitud de actualización.

Una nota al margen de su solución 4 (sesiones del lado del cliente): dependiendo del número de recursos que sus usuarios puedan crear, el tamaño de la sesión podría no ser un problema. El problema de actualización de la sesión del lado del cliente también puede ser bastante fácil de resolver: si una solicitud de actualización de un recurso falla porque el recurso no figura en los datos de la sesión que recibió de su cliente, pero el usuario está en lo correcto, entonces usted hace un compruebe en el backend si los datos de la sesión de ese cliente están desactualizados. Si es así, permite la actualización y envía una cookie actualizada con la respuesta a la solicitud de actualización. Esto solo provocará una búsqueda en la base de datos cuando un usuario intente actualizar un recurso de un cliente con datos de sesión local desactualizados (o cuando sea malicioso e intente deshacerse de usted, ¡pero limite la tasa de rescate!). Sin embargo, Estoy de acuerdo en que la solución 4 es más complicada que las otras y que estás mejor con una de tus otras ideas. La solución 4 también tiene varias consideraciones de seguridad que debe tener en cuenta; El almacenamiento de este estado de autorización en el cliente realmente necesita que su seguridad sea hermética.


¿Qué sucede si todos los recursos que son propiedad del usuario se modelan como recursos secundarios? (Por ejemplo, / usuarios / 1 / entidad / 2). Combine eso con un JWT firmado que tenga la identificación de usuario. Si hace esto, la autorización puede incluso delegarse en un servidor separado (API Gateway) porque no necesita acceso a la base de datos.
Chamindu

0

Otra solución que puede considerar:

Si un recurso realmente no se puede transferir a otro usuario una vez creado, podría pensar en codificar la identificación del usuario en la identificación del recurso.

Por ejemplo, todos los recursos que pertenecen al usuario 'alice' se denominan 'alice-1', 'alice-2', etc.

Entonces es trivial verificar si 'eve' tiene acceso a un recurso llamado 'alice-1'.

Si no desea que la propiedad del recurso sea de conocimiento público, puede usar una función hash criptográfica como esta:

  1. Dada una contraseña que solo su servidor conoce
  2. Y un nombre de usuario u (por ejemplo, 'alicia')
  3. Y un nombre de recurso r (por ejemplo, '1')

Generar hmac(password, u | '-' | r ), donde | es concatenación Use el resultado, junto con el nombre del recurso r, como la identificación del recurso. Opcionalmente, agregue un valor de sal (como para almacenar contraseñas).

Cuando una solicitud de actualización proviene del usuario 'alice' para una identificación de recurso dada, extrae r de la identificación del recurso, calcula hmac (contraseña, 'alice' | '-' | r) y la compara con la parte restante de la identificación del recurso . Si coincide, Alice puede actualizar el recurso, si no coincide, no está autorizada. Y sin la contraseña, nadie puede averiguar quién posee qué recurso.

Creo que es seguro, pero es posible que desee repasar las cualidades de hmacs.


0

Parece que hay dos problemas separados que debe gestionar. Son (1) ¿Cómo está autenticando al usuario que genera la solicitud? y (2) cómo sabe qué objetos de fondo están asociados con ese usuario. También tengo una observación que puede ser adecuada para su caso de uso (3)

(1) La autenticación de usuario de una manera RESTful nunca es bonita. Tiendo a preferir el uso de los encabezados de Autenticación / Autorización HTTP y las respuestas HTTP 401 para activar la autenticación, lo que significa que potencialmente está autenticando al usuario en cada solicitud. Como es una aplicación móvil, tiene la opción de crear su propio esquema de autenticación personalizado, lo que podría permitirle proporcionar un token de sesión en lugar de detalles de autenticación completos en futuras solicitudes.

(2) El usuario asociado con un conjunto de objetos seguramente son datos clave que necesita almacenar junto con los objetos reales en su almacén de datos. Como parte de su lógica de negocios, debe verificar quién es el propietario del objeto y compararlo con la identidad que accede al objeto. Haría esto explícitamente en código, como parte de la capa empresarial que valida la solicitud. Si los accesos a la base de datos son un problema lo suficientemente grande, entonces tendría sentido almacenar en caché esta información en un almacén de valores clave, como memcache o (para volúmenes muy altos, sitios de alta disponibilidad, riak) y solo golpear la base de datos si el caché está frío para esta combinación de usuario / objeto.

(3) Si tiene un número suficientemente pequeño de usuarios y objetos, puede combinar la identificación de usuario y objeto en el mismo campo, y usar este campo como la clave principal de la base de datos para la tabla de objetos. por ejemplo, el número de 64 bits proporciona 24 bits de espacio para la ID de usuario y 40 bits de espacio para los identificadores de objeto. De esa manera, cuando consulta el objeto, puede estar 100% seguro de que es para su usuario.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.