Artículo
· 19 mayo, 2024 Lectura de 10 min

Proteger los datos: Se un mejor cerrajero

Buenas a todos,

 

en capítulos anteriores, vimos como "Como controlar el acceso a tus recursos con OAuth2". En este primer artículo explicábamos como preparar un acceso seguro a nuestros recursos utilizando la potente herramienta que nos ofrece Intersytems del servidor de Autenticación. Al finalizar el mismo, comentábamos que si quisiéramos podríamos aplicar un control extra a este acceso y esto nos lleva a este artículo, por lo que seguiremos el siguiente índice:

1.- Introducción

   1.1..- De donde venimos

2.- Problema

   2.1.- ¿Cómo funciona la llave (Token)?

   2.2.- ¿Qué es una hoja de registro (LookUp Table)?

   2.3.- ¿Cómo construyo las cerraduras?

   2.4.- Cómo avisar a la policía de intrusos

 

1.- Introducción

Para empezar, situemos el problema a resolver:

Llevado a una situación más común imaginemos estar en una vivienda compartida, al cual cada inquilino accede al piso con la llave de la puerta de entrada, pero luego dentro de casa, cada habitación no tuviera llave, por lo que cualquier inquilino podría entrar en la habitación de cualquier otro y ¡no creemos que sea para dejar regalos!

Llegados a este punto, tendremos que poner cerraduras en todas las puertas y asegurar que cada inquilino solo pueda acceder a su habitación. ¡Saquemos al cerrajero que llevamos dentro!

Os dejo un pequeño video que de forma conceptual explica nuestro problema y el resultado de nuestra solución:

 

1.1.-De dónde venimos

Partimos de un servidor de autenticación (puerta de entrada) y un servidor de Recursos (la vivienda). En el servidor de autenticación vamos a tener 2 clientes dados de alta, cada uno con su usuario y su contraseña. Por otro lado, nuestro ESB que hace de servidor de recursos tendrá la labor de consultar los recursos determinados (habitaciones) para cada cliente. Por lo tanto, tenemos:

  • Cliente 1 (inquilino 1)
  • Cliente 2 (inquilino 2)
  • Recursos Cliente 1 (habitación de inquilino 1)
  • Recursos Cliente 2 (habitación de inquilino 2)

 

A continuación un diagrama de flujo del problema encontrado:

2.- Problema

Con la solución actual, nos encontramos con el problema de seguridad de datos, pues, si el cliente 2 pudiera conocer los datos de la petición del cliente 1, Con pasar su propia autorización (llave de la puerta de entrada) hacia los recursos, de la puerta hacia dentro, tendría libertad para acceder a los recursos (habitaciones) que quiera. El propietario de la vivienda nos ha pedido que pongamos una cerradura en cada puerta, así que vamos con ello.

A continuación, se indican los pasos a seguir para ser mejores cerrajeros.

2.1.- ¿Cómo funciona la llave (Token)?

            El Servidor de Autenticación, una vez recibe un usuario y una contraseña válidos, nos devolverá un token con el siguiente formato:

{

    "access_token""eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJqdGkiOiJodHRwczovL2F1dGhzcnZyLnNjcy1zYWx1ZC5vcmc6NTc3NzYvb2F1dGgyLmdqQnZqR01JQzJqeVBYZW12dW5EWW1vTnJnMCIsImlzcyI6Imh0dHBzOi8vYXV0aHNydnIuc2NzLXNhbHVkLm9yZzo1Nzc3Ni9vYXV0aDIiLCJzdWIiOiJ5WUZIZElGWDQyZEVEV3FhZHhzWFJxbmZtZW1IVEVZYVFlQ09JbWRKVXpRIiwiZXhwIjoxNzE2MTU0Mjk4LCJhdWQiOiJ5WUZIZElGWDQyZEVEV3FhZHhzWFJxbmZtZW1IVEVZYVFlQ09JbWRKVXpRIn0.",

    "token_type""bearer",

    "expires_in"60,

    "scope""my/scope"
}

Este “Access_token” que viene en base64 se compone de información interesante para nuestro objetivo. Sabiendo que este token se genera por JWT, una vez tratado, vemos que se compone de:

  • Header
  • Payload

Header

Se compone de:

{

  "typ": "JWT",

  "alg": "none"
}

 

Payload

Se compone de:

{

  "jti": "https://authsrvr.org:57775/oauth2.gjBvjGMIC2jyPXemvunDYmoNrg0",

  "iss": "https://authsrvr.org:57775/oauth2",

  "sub": "yYFHdIFX42dEDWqadxsXRqnfmemHTEYaQeCOImdJUzQ",

  "exp": 1716154298,

  "aud": "yYFHdIFX42dEDWqadxsXRqnfmemHTEYaQeCOImdJUzQ"
}

 

Del payload el dato que nos vamos a quedar es el “aud”, ya que este dato es el usuario registrado en el servidor de autenticación que hemos creado previamente.

Para más información sobre este contenido, podéis consultar la documentación oficial de IS: (https://docs.intersystems.com/irisforhealthlatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYS.OAuth2.Validation)

 

2.2.- ¿Qué es una hoja de registro (LookUp Table)?

No profundizaremos en este momento en las LoopUp Tables, lo que, si nos interesa saber en este punto, es que son unas tablas internas de cada namespace, en las cuales podemos guardar datos en el formato “clave/valor”.

Para más información sobre este contenido, podéis consultar la documentación oficial de IS:

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=ECONFIG_other_lookup_tables

 

2.3.- ¿Cómo construyo las cerraduras?

En este punto juntemos los 2 puntos anteriores, es decir, con el escenario actual, tenemos:

                -              Un dato del Token: “aud”

                -              Una tabla interna que almacena datos: “clave/valor”

Como responsables de la seguridad, sabemos que a este cliente solo hay que dejarle acceder a sus propios recursos, por lo que para ello, en la LookUp table, vamos a guardar:

-              Clave: el “aud”

-              Valor: el condicionante de acceso a los recursos. En este ejemplo pongamos “1”.

 

Ahora que ya tenemos preparados los "materiales", nos toca construir la cerradura. Para ello explicamos a continuación los pasos a Seguir:

2.3.1.-  Recepción del Token

2.3.2.-  Extracción y validación del “aud”

 

2.3.1.-  Recepción del Token

Partimos para el ejemplo de un servicio REST que tendrá los siguientes parámetros:

Method MISERVICIO(pInput As %Stream.Object, Output pOutput As %Stream.Object, indicadorRecursos As %String(MAXLEN=""))As %Status

La información que vamos a requerir viene en el pInput y el indicadorRecursos. En el inicio de nuestro servicio observemos las siguientes líneas y lo que hace cada una:

{
    //Necesitamos sacar la cabecera "Authorization" de la petición REST
    set authorization = $tr(pInput.GetAttribute("authorization"),"")

    //Necesitamos sacar Token
    set token = $PIECE(authorization,"Bearer ",2)

    //Necesitamos validar el Token. Este apartado lo vamos a obviar en esta ocasión.
    set respuesta = ..ResServer(token)
    if (respuesta = 1){ Avanzar en el servicio } else{ Devolver error }

    //Necesitamos validar el “aud”
    set audValidado = ..ValidarUsernameOAUTH2(token,indicadorRecursos,.pOutput)

    //Si el “aud” es válido, podemos avanzar, sino salimos con el mensaje de respuesta que configuremos
    if (audValidado = 0)
    {
        quit ERROR            
    }
}

Como podemos ver, en la línea de validación del "aud" nos encontramos con la llamada a un método (ValidarUsernameOAUTH2) el cual os expongo a continuación.

 

2.3.2.-  Extracción del “aud”

En el siguiente método podemos ver paso a paso como se extrae y valida el "aud".

ClassMethod ValidarUsernameOAUTH2(token As %String(MAXLEN=""), indicadorRecursos As %String(MAXLEN=""), Output pOutput As %Stream.Object) As %Boolean
{
 set claseAux = ##class(%ZEN.Auxiliary.jsonProvider).%New()
   
 // Buscamos el cliente de recursos
 Set client = ##class(OAuth2.Client).Open("resserver",.sc)
 If client  = "" Quit      
 // Inicializamos nuestra referencia para acceder a los recursos, pues si no cambia de valor, nunca podremos acceder a los recursos
 set codigoRecursosOAUTH2 = ""
 try{
  // Convertir JWT a objeto 
  set sc = ..JWTToObject(client,token,.securityParameters,.jsonObject)

  // Extraemos el “aud” del JSON que generamos al convertir el Token
  set usuarioOAUTH2 = jsonObject.aud

  // Extraemos de la LookUpTable que creamos en el paso anterior, el Valor que nos da el usuario que hemos extraído del Token. Si este valor es “”, quiere decir que no existe el usuario. Si existe, extraemos el Valor. Este valor es el que tendremos que enfrentar con el indicadorRecursos que tenemos en la entrada. Pues si estos no coinciden, querrá decir que el usuario está intentando acceder a recursos a los cuales no le hemos autorizado acceder.
  set codigoRecursosOAUTH2 = ##class(Util.TablasMaestras).getValorMaestra("SERVIDORAUTH.VALIDACION",usuarioOAUTH2)

 }catch{
  // Si hay errores, devolvemos una respuesta de error de acceso
  set response             = ##class(Mensajes.Response.Autorización.DatosResponse).%New()
  set response.codigo      = "-4"
  set response.descripcion = “Error de acceso"

  set tSC = claseAux.%WriteJSONStreamFromObject(.pOutput,.response,,,,"aeloqtuw")
   
  // Enviamos el JSON con cabeceras
  Do:$$$ISOK(tSC) pOutput.SetAttribute("Content-Type","application/json")
  do pOutput.SetAttribute("Access-Control-Allow-Origin","*")
  do pOutput.SetAttribute("Access-Control-Allow-Credentials","true")
  do pOutput.SetAttribute("Access-Control-Allow-Methods","GET")
  do pOutput.SetAttribute("Access-Control-Allow-Headers","request,Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers")
 }

 // Si hubo error, significa que el token es incorrecto. Por lo tanto devolvemos un 0
 if (codigoRecursosOAUTH2 = ""){
    Quit '$$$OK                           
 }
 // Si la consulta a la LoopUp Table nos devolvió datos, debemos hacer ahora la validación de este valor.
 if (codigoRecursosOAUTH2 '= ""){
    // Si son distintos, significa que el aud NO es de estos recursos
    if (codigoRecursosOAUTH2 '= indicadorRecursos){
     set response             = ##class(Mensajes.Response.Autorización.DatosResponse).%New()
     set response.codigo      = "-3"
     set response.descripcion = “Consulta no Autorizada"
     set tSC = claseAux.%WriteJSONStreamFromObject(.pOutput,.response,,,,"aeloqtuw")
    
     // Enviamos el JSON con cabeceras
     Do:$$$ISOK(tSC) pOutput.SetAttribute("Content-Type","application/json")
     do pOutput.SetAttribute("Access-Control-Allow-Origin","*")
     do pOutput.SetAttribute("Access-Control-Allow-Credentials","true")
     do pOutput.SetAttribute("Access-Control-Allow-Methods","GET")
     do pOutput.SetAttribute("Access-Control-Allow-Headers","request,Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers")

  // Si son distintos, el método responde un 0
     Quit '$$$OK                                                          
    }
 }

 // Si son iguales, el método responde un 1
 quit $$$OK
}

Aclarando este apartado:

Supongamos que el indicadorRecursos de entrada viene a "2". Cuando vayamos a comparar el valor obtenido de la LoopUpTable ("1") con este valor, no coincidirán, por lo que no será posible acceder a los recursos. En cambio, si este valor de entrada es "1", si coincidirá y podrá acceder a los mismos.

Llegados a este punto, ya habríamos aplicado la cerradura (seguridad) a nuestra primera habitación (recursos). Para el resto de habitaciones, bastaría con repetir los mismos pasos para el resto de clientes/recursos.

2.4.- Cómo avisar a la policía de intrusos

Una vez detectado quien puede acceder o no a los recursos, podemos plantearnos monitorizar si alguien se esta pasando de listillo intentando acceder a recursos que no le corresponden. Para ello, podemos hacer uso de las herramientas de Intersystems para el envío de emails, notificaciones push, o incluso alimentar cuadros de mando y tener un mayor control de estos accesos incorrectos. Estas alertas deben configurarse en el momento que vamos a devolver un "0", pues es el momento donde tenemos los datos de acceso así como la respuesta del acceso no permitido.

 

La redacción de este artículo se ha planteado de forma esquematizada para que pueda ser adaptada a cualquier aplicación que puedan plantear. Se ha dejado indicado el código mínimo para que puedan entender y reutilizar de forma lo mas práctica posible. Os prometo que si seguís las indicaciones paso a paso para entender el proceso, ¡empezareis a poner cerraduras por doquier!

Finalmente espero que les sea de utilidad, ¡y que se conviertan en mejores cerrajeros!

Muchas gracias por el tiempo que han dedicado a esta lectura.

PD:Por favor, no duden en plantear cuestiones que ayuden a mejorar la redacción y explicación del mismo por si algo no ha quedado correctamente entendido.

Comentarios (3)3
Inicie sesión o regístrese para continuar

Hola Carlos :=)

Me ha encantado tu artículo. Lo has explicado de una manera muy clara y didáctica, lo cual es esencial para un tema tan técnico y crítico y profundo y "denso", como la seguridad en la gestión de datos. Aquí te comparto mis reflexiones y comentarios sobre algunos puntos que considero especialmente importantes y que merecen ser destacados.

La analogía de la vivienda compartida es excelente para ilustrar la necesidad de controles de acceso más finos. Todos entendemos la incomodidad de no tener privacidad en nuestras habitaciones, y esta comparación hace que el problema de seguridad de datos sea muy sencillita de comprender.

Me pareció particularmente útil tu explicación detallada del token de acceso (JWT). Has desglosado su estructura de una manera que facilita entender cómo se compone y qué información crítica contiene. Es crucial que quienes trabajamos con sistemas de autenticación comprendamos bien estos elementos, ya que son la base para garantizar que solo los usuarios autorizados accedan a los recursos adecuados.


La explicación paso a paso de cómo construir las cerraduras es de gran valor. Detallas los métodos y los parámetros necesarios para la validación del token y del "aud" de una forma que cualquiera con conocimientos básicos de programación podría seguir. Está super práctico este apartado.

Finalmente, tu sugerencia sobre cómo monitorizar y alertar sobre intentos de acceso no autorizados es muy interesante. 

Has desmenuzado un tema complejo en pasos manejables y entendibles, lo cual es una habilidad muy valiosa. Y la analogía de la vivienda compartida es un alivio mental para entenderlo de manera cotidiana.

Te felicito por el gran trabajo y por compartir tus conocimientos de manera tan generosa Carlos. :=)