sábado, 12 de septiembre de 2015

Aplicación de Seguridad en REST con 'SPRING-OAUTH2'

Todos saben que la seguridad a nivel de servicios es muy importante, sobre todo en los escenarios donde es necesaria la comunicación con servicios que están fuera de los límites del 'inventario de dominio u empresa' que se ha definido (un servicio de un proveedor externo), en estos casos existen soluciones a nivel de servicios que se pueden aplicar en arquitecturas SOA como los son:WS-Se curity, Https, SSL, etc, pero para el caso de arquitecturas REST, en realidad no existen muchas en el mercado y las pocas que existen no son muy utilizadas y/o conocidas.

En estas oportunidad hablaré un pocos de 'OAuth2', que en si es un protocolo de autorización que permite a terceros (clientes) acceder a contenidos de un usuario (alojados en aplicaciones de confianza, servidor de recursos). Es decir brinda el acceso a aplicaciones consumidoras, para que pueden acceder al contenido del servicio requerido previa validación y autenticación representada en un token.

La logica en si, tal como lo muestra la imagen, se sigue una lógica que es la siguiente:
  1. El 'usuario' requiere información y la solicita a la 'aplicacion cliente' (consumidora).
  2. La 'aplicacion cliente' solicita una autenticación contra el 'servidor de autenticación' (OAuth2). Se envía los parámetros: (grant_type,client_id,client_secret,username,password).
  3. Si todo está OK, el 'servidor de autenticación' responde los parámetros: (access_token,token_type,refresh_token,expires_in).
  4. Si todo está NOK, el 'servidor de autenticación' responde los parámetros:
  5.  (error,error_description), automáticamante.
  6. La 'aplicación cliente' obtiene el Token de acceso requerido y se comunica con el 'Servidor de Recursos' (el servicio principal), mandandole entre sus parámetros propios del servicio el parámetro: (access_token), previamente recuperado.
  7. El 'Servidor de Recursos' responde la información requerida a la 'aplicacion cliente'.

Actualmente, existe soluciones que brindan 'OAuth2' para su aplicación estas son:
  • Apache Oltu
  • Spring-OAuth2
  • Etc.
En esta oportunidad mostraré una de ellas que es: 'Spring-OAuth2', mediante el desarrollo de un dummy. Asi mismo, dicho dummy preparado fué desarrollado bajo las siguientes tecnologías y herramientas: 
  • Eclipse 3.8 (STS)
  • JDK 6
  • Spring 3.1
  • Spring MVC.
  • JSON.
  • Log4j
  • Tomcar 7.

A.
CONFIGURACIONES PREVIAS:


Dos de las buenas prácticas importante que se deben de considerar para el desarrollo de servicios, para su independencia y facil mantenimiento son:
  • Configurar un archivo de configuración 'logs' genérico y externo al servicio propiamente. 
  • Configurar un archivo de configuración '.properties' propio del servicio y externo a el. 
Para conseguir estos dos puntos, debemos definir variables donde se almacerán las rutas absolutas tanto para Log4j ( -Dlog4j.config="D:\java\xampp\tomcat" ), como para .properties ( -Dproperties.config="D:\JAVA\MIDDLEWARE\propertiesWebLogic" ), en mi caso he definido estás como rutas base.

 

Estos JVM arguments serán usados en el servicio de la siguiente manera, para tener el manejo externo respectivo de los archivos de configuración. Así mismo, es importante considerar que los archivos deberá ser creados en dichas rutas previamente (deben existir):




B. DESARROLLO DEL DUMMY:
El servicio dummy preparado cuenta con la siguiente estructura:


En si la idea es que por medio de este servicio se brinde el rol de: 'servidor de autenticación'  y  con ellos brindar acceso al 'Servidor de Recursos', podemos si deseamos manejarlo con un proxy de acceso.

Toda la seguridad en el servicio brindada por 'Spring-OAuth2', se manejará desde el archivo: 'applicationSecurity.xml', aquí los datos no deberá ser incrustados a modo Hardcore, sinó que serán referenciados por medio de Spring a un archivo .properties externo. 


En dicho archivo se manejará toda la información importante y configurable requerida por el servicio, así mismo, algunos datos propios de 'OAuth2', tal como menciono anteriormente podrían ser cargados de BD, pero para ello se debería modificar algunas partes en el: 'applicationSecurity.xml', que no son propias del dummy en estos momentos:


La parte mencionada como 'servidor de autenticación' está definida por 'OAuth2', mediante la exposición de un servicio con la estructura antes ya explicada. Este servicio en nuestro caso sería este:
http://localhost:8080/DummySpringMvcOauth/oauth/token?grant_type=password&client_id=dummySpringOauth&client_secret=secretDummySpringOauth&username=rguerra&password=@javaman

Al momento de ser consumirdo el servicio, existen dos clases que se encargan tanto de la 'autenticación' como de la validación del 'clientId', estas clases son: (CustomUserAuthenticationProvider.java y ClientDetailsServiceImpl.java)

La clase: CustomUserAuthenticationProvider.java, implementa  (AuthenticationProvider) que se encarga de lla validación del 'client_id' y el 'client_secret'  ingresado como parámetro, en base a ello se agregan a una lista 'autenticados'.


Así mismo, la clase: ClientDetailsServiceImpl
, implementa (ClientDetailsService) que se encarga de agregar una serie de permisos al usuario autorizado previamente.


Vale resaltar que ambas dos lógicas mostradas en esta parte, por mi parte las estoy realizando contra un .properties, pero se pueden tener registradas en una BD tanto el repositorio de usuarios/credenciales válidos como tambien lo relacionado a permisos.

Luego, la parte mencionada como 'servidor de recursos' se puede considerar trabajarlo como un proxy al servicio requerido, ya que este requeriá que se le envíe un parametro adicional (access_token), de los parámetros propios del servicio: 

http://localhost:8080/DummySpringMvcOauth/eai/json/personas/?access_token=df711ba5-0414-4c3c-bf16-4f8d4435358e

Si todo es OK se obtendra la respuesta correcta y propia del servicio requerido.



C. PRUEBA DEL DUMMY:

La prueba del servicio Dummy, se puede realizar de 2 maneras. Considerar que para benefinicios del dummy y cuenta con 2 tipos de 'Servidores de Recursos', uno que funciona sin parámetros propios del servicio y otro que funciona con un parámetro própio del servicio. Así mismo, los servicios REST manejados están implementados vía las funcionalidad brindadas por Spring MVC.


  • DESDE EL NAVEGADOR:
Armando las URL's podemos simular la lógica del flujo que 'OAuth2', propone para sus validaciones de la siguiente manera:

Paso#1: http://localhost:8080/DummySpringMvcOauth/oauth/token?grant_type=password&client_id=dummySpringOauth&client_secret=secretDummySpringOauth&username=rguerra&password=@javaman

Respuesta#1
:
{ "access_token":"a5c7d1f1-6028-4f92-8c35-71ed86272965",
    "token_type":"bearer",
    "refresh_token":"107d7139-484e-4bee-a13a-9059012ddb8a",
    "expires_in":119 }



Paso#2A:  Una vez ya con el Token (access_token) ya obtenido:
http://localhost:8080/DummySpringMvcOauth/eai/json/personas/?access_token=cc953b49-9c57-4aa5-96fd-7d1b0180570f


Respuesta#2A:
[{"id":1,"nombre":"CESAR GUERRA","correo":"cguerra@hotmail.com","telefono":"9877989898"},{"id":2,"nombre":"CATHERINE COTRINA","correo":"ccotrina@hotmail.com","telefono":"9896669898"},{"id":3,"nombre":"JUAN VERA","correo":"jvera@hotmail.com","telefono":"9238989898"},{"id":4,"nombre":"MARTIN CALAGUA","correo":"mcalagua@hotmail.com","telefono":"9898955598"}]


Paso#2B: http://localhost:8080/DummySpringMvcOauth/eai/json/CESAR%20GUERRA?access_token=cc953b49-9c57-4aa5-96fd-7d1b0180570f

Respuesta#2B:
{"id":1,"nombre":"CESAR GUERRA","correo":"cguerra@hotmail.com","telefono":"9877989898"}

Error: Ante un posible error de ya sea de tipo autenticación, parámetrización, etc, 'OAuth2' responderá de la siguiente manera por ejemplo:

{ "error":"invalid_grant","error_description":"Bad credentials" }


  • A NIVEL DE CÓDIGO:
A nivel de código se ha armado una clase tester que simularía la lógica requerida por: 'Aplicación Cliente', con relación al 'Servidor de Autenticación' para obtener el Token requerido y posteriormente ya sin problema consumir el 'Servidor de Recursos':

Ejecutamos la clases: TestMvcOauth.java, que procesará toda la lógica y generá el log respectivo:


Los logs serán generados de manera externa al servicio, por medio de log4j tal como se explicó inicialmente:


Finalemnte, con esto se ha tratato de demostrar una forma de las existes para el manejo de 'OAuth2', específicamente la brindada por Spring. En si se buscado la forma de hacer la explicación lo más detallada ya que en la actualidad veo que no existen, mucha explicación a este nivel sobre 'OAuth2', espero les sea beneficioso.

Para descargar las fuentes del servicio dummy, pulsar aquí:
http://www.mediafire.com/download/lce72exhd8ke2hn/DummySpringMvcOauth.zip

2 comentarios:

Bernardo Lopez dijo...

Hola, estoy intentado implementar la solución pero me aparece un error que es el siguiente:


18:07:33,467 WARN [org.jboss.as.ee] (MSC service thread 1-3) JBAS011006: Not installing optional component org.springframework.web.context.request.async.StandardServletAsyncWebRequest due to exception: org.jboss.as.server.deployment.DeploymentUnitProcessingException: JBAS011054: Could not find default constructor for class org.springframework.web.context.request.async.StandardServletAsyncWebRequest
at org.jboss.as.ee.component.ComponentDescription$DefaultComponentConfigurator.configure(ComponentDescription.java:606)
at org.jboss.as.ee.component.deployers.EEModuleConfigurationProcessor.deploy(EEModuleConfigurationProcessor.java:81)
at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:113) [jboss-as-server-7.1.1.Final.jar:7.1.1.Final]
at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1811) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]


Y después de hacer el inventario de servicios rest en el despliegue aparece esto:

18:07:38,155 WARN [org.springframework.web.context.support.XmlWebApplicationContext] (MSC service thread 1-7) Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChains': Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0' while setting bean property 'sourceList' with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot create inner bean '(inner bean)#675cf7a3' of type [org.springframework.security.web.authentication.www.BasicAuthenticationFilter] while setting constructor argument with key [4]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#675cf7a3': Cannot resolve reference to bean 'clientAuthenticationEntryPort' while setting constructor argument; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'clientAuthenticationEntryPort' is defined
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:336) [spring-beans-4.1.1.RELEASE.jar:4.1.1.RELEASE]

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot create inner bean '(inner bean)#675cf7a3' of type [org.springframework.security.web.authentication.www.BasicAuthenticationFilter] while setting constructor argument with key [4]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#675cf7a3': Cannot resolve reference to bean 'clientAuthenticationEntryPort' while setting constructor argument; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'clientAuthenticationEntryPort' is defined



Que pdoria ser que me este generando el error??

JAVAMAN dijo...

Te recomieno que nates que nada intentes replicar el ejemplo en un entorno igual al que se ha trabajado, esto para que entiendas como trabaja. Ahora veo que tu no estas usando Tomcat7 sino un Servidor de Aplicaciones JBOss por ahi es un posible problema que se puede dar debido a las librerías ya que JBOSS puede necesitar otras librerías para su funcionamiento o por el contrario la version que internamente este server tiene puede causar conflicto con las de la aplicación. Aspí mismo veo que hay un problema parace que estás usando Spring4 y creo que este no soporta OAUTH2. Maneja la misma versión que se recomienda en el ejemplo.