Alfonso Marín López

Blog personal de un programador

Spring Security, swing y un servidor de aplicaciones

| 4 Comments

Hacia tiempo que no escribía un post técnico y ya ha llegado el momento. Recientemente, desarrollando una aplicación a medida para un cliente, me he visto en la necesidad de conectar una aplicación de escritorio con una aplicación web. En los años que llevo programando no he tenido necesidad de realizar esta operativa, muchas veces ha sido conectar una aplicación web con otras aplicaciones, tanto web, servidores de diferentes tipos como de escritorio, pero han sido siempre desde la perspectiva de la lógica de negocio en el servidor, consumiendo servicios web, accediendo a base de datos o incluso leyendo archivos. Esto es distinto, tengo que ofrecer acceso a mi aplicación web para aplicaciones de escritorio. Nada complicado si se utilizan webservices. Pero he aprovechado la coyuntura para probar unas nuevas herramientas, spring roo, que genera de forma espectacular una aplicación web y al que ya dedicare un post con mas tranquilidad. Por el momento decir que genera aplicaciones con JPA en Hiberanate (mas bien en cualquier cosa) y spring para el negocio. Ademas de hacer un scaffold de las web con GWT. Para la seguridad puedes incluir spring security, que es lo que yo he introducido. Hasta aquí todo bien, funciona perfectamente y sin ningún problema, puedes controlar el acceso por métodos de negocio con simples tag, muy fácil. Ahora viene la parte en que conectamos la aplicación de escritorio, en mi caso con una aplicación swing (por cierto con netbeans y matisse, muy sencillo también). Spring te ofrece un montón de opciones de interconexión, RMI directamente y de forma sencilla, HTTP-Invoke o webservices, entre muchos. He probado RMI y muy contento, pero al final me he decantado por HTTP-invoke por varios motivos:

  • RMI necesita configurarse ademas un puerto concreto, complicando la instalación en según que sitios. Si el servidor y la red son tuyos no hay muchos problemas, pero si utilizas algo como la PAAS de google puedes tener algunos problemas. En cambio HTTP-Invoke usa el mismo puerto 80.

  • Rendimiento: Tanto RMI como HTTP-Invoke tienen muy buenos rendimientos, ambos usan serialización de objetos.

  • Configuración, al ir por el puerto 80 no requiere configuración extra de seguridad.

El problema lo tuve en la seguridad, como configurar la seguridad y realizar un login remoto, que es lo que voy a explicar aquí.

Servidor

Para configurar el lado del servidor hay que realizar los siguientes pasos:

 

  • Crear una capa de servicios: para ello creamos unas clases en nuestro código que ofrecerá los servicios que nos interesen.

public interface ServicioSaludo {

@Secured(“ROLE_ADMIN”)

public String getSaludo();

}

@Service(“saludador”)

public class ServicioSaludoImpl implements ServicioSaludo {

@Override

public String getSaludo() {

return “Hola”;

}

}

Es en la interfaz donde declaramos la seguridad con los tags @Secured. Luego pondré la configuración básica de seguridad.

El tag @Service le indica a spring que es un bean y que debe nombrarlo como saludador, de esta manera no hará falta ponerlo expresamente en el xml de configuración. Claro que para que funcione debéis configurar vuestro spring con el siguiente código.

<context:component-scan base-package=”paquete”>

Si no, simplemente lo declaráis expresamente como un bean

<bean name=”saludador” class=”paquete.ServicioSaludoImpl”/>

Ya tenemos el servicio declarado, ahora lo exponemos con HTTP-Invoker.

<bean name=”/saludadorHTTP” class=”org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter”>

<property name=”service” ref=”saludador”/>

<property name=”serviceInterface” value=”paquete.ServicioSaludo”/>

</bean>

/saludadorHTTP indica donde estará accesible, después ponemos con que lo exponemos, e indicamos las clases que queremos exponer.

Aquí tenéis el archivo de configuración de seguridad.

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans:beans xmlns=”http://www.springframework.org/schema/security”

xmlns:beans=”http://www.springframework.org/schema/beans”

xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd”>

<http auto-config=”true” use-expressions=”true”>

<intercept-url pattern=”/login.*” access=”permitAll”/>

<intercept-url pattern=”/**” access=”isAuthenticated()” />

<form-login/>

<anonymous />

<http-basic />

</http>

<global-method-security secured-annotations=”enabled”/>

<!– Configure Authentication mechanism –>

<authentication-manager alias=”authenticationManager”>

<authentication-provider>

<user-service>

<user name=”admin” password=”admin” authorities=”ROLE_ADMIN”/>

</user-service>

</authentication-provider>

</authentication-manager>

</beans:beans>

use-expressions sirve para utilizar las nuevas forma de definir las seguridad, bastante mas completas.

Global-method-security sirve para poder utilizar las anotaciones de seguridad en las interfaces que hemos usado antes.

En cuanto a lo demás, es básico en seguridad de spring, así que no haré mas comentarios.

Ahora cargamos otro dispacher de spring en el web.xml, se pueden tener todos los que se quieran, y es recomendable separarlo, si tenéis uno para el MVC de spring, podéis dejarlo intacto y añadir este otro solo para las conexiones remotas.

<servlet>

<servlet-name>remoting</servlet-name>

<servlet-class>

org.springframework.web.servlet.DispatcherServlet

</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/spring/remoting-servlet.xml</param-value>

</init-param>

<load-on-startup>2</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>remoting</servlet-name>

<url-pattern>/remoting/*</url-pattern>

</servlet-mapping>

Varias cosas, para los que no lo sepan, el contextConfigLocation no es necesario, siempre y cuando llaméis al archivo con la nomenclatura servletName-servlet.xml y ubicado en WEB-INF/classes. Si lo queréis en otro sitio y con otro nombre pues se pone.

Aquí nos quedamos con la URI /remoting, que se usara en el cliente. Con esto ya tendremos configurado el servidor.

Cliente

Le toca al cliente, en este caso utilizare una simple aplicación de consola. Necesitaremos varias cosas:

  • Las interfaces del servidor: podemos empaquetar solo las interfaces del servidor y añadirlas nuestro proyecto cliente.
  • Las librerias de spring: tanto el core como security.

Ahora configuramos nuestra aplicación creando un archivo de configuración cliente para spring, por ejemplo cliente.xml, e introducimos lo siguiente:

<bean id=”servicio”>

<property name=”serviceUrl” value=”http://localhost/remoting/saludadorHTTP”/>

<property name=”serviceInterface” value=”paquete.ServicioSaludo”/>

<property name=”httpInvokerRequestExecutor”>

<ref bean=”httpInvokerRequestExecutor”/>

</property>

</bean>

<bean id=”httpInvokerRequestExecutor”     class=”org.springframework.security.remoting.httpinvoker.AuthenticationSimpleHttpInvokerRequestExecutor”/>

La dirección del servicio donde lo tengáis, en mi caso en localhost, seguido de remoting que es donde mapeamos el servlet para servicios remotos y por último la dirección del servicio concreto que configuramos en el archivo xml de spring del servidor. Ademas cargamos el bean para que lo use de autenticado.

Y nos vamos al main de la clase cliente.

//Creamos una autenticación con el usuario y contraseña

Authentication request = new UsernamePasswordAuthenticationToken(“admin”, “admin”);

//Este es el contexto que extiende del servidor, es aqui donde nos logeamos

SecurityContextHolder.getContext().setAuthentication(request);

//Cargamos el archivo de configuración

ClassPathXmlApplicationContext contexto = new ClassPathXmlApplicationContext(“cliente.xml”);

 

ServicioSaludador servicio = (ServicioSaludador) contexto.getBean(“servicio”);

String cadena = servicio.getSaludo();

Y ya esta, facilísimo.

Bueno, quien dice un main dice una aplicación swing, es la mar de sencillo, yo por el momento uso netbeans que funciona muy bien.

  • Mario

    Lo primero muchas gracias por compartir el ejemplo, es muy interesante. Queria preguntarte como seria si en lugar de tener un servicio expuesto con HTTPInvoker es un RMIServiceExporter. Estoy desarrollando un cliente GWT que usa servicios RMI a los cuales he añadido seguridad con spring-security. Creo que el servidor esta bien ya que sin tocar el cliente intento acceder y me devuelve un “org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext”. El caso es que en el cliente hago lo mismo que tu pero usando RmiProxyFactoryBean y ContextPropagatingRemoteInvocationFactory, en lugar de tu HttpInvokerProxyFactoryBean y AuthenticationSimpleHttpInvokerRequestExecutor respectivamente, y sigo con el mismo error. No encuentro forma de pasar los credenciales del cliente al servidor para que los valide. Podrías añadir un ejemplo usando RMI? Muchas gracias por adelantado

  • alfonsomarin

    Hola Mario,
    Tienes que tener muy claro que GWT es cliente en exclusiva, no podrás ejecutar nada de spring en el cliente. La seguridad se aplica, en el caso de gwt, a sus servlets. Si tu quiere conectarte a un servicio que usa los servicios RMI que ofrece spring, debes aplicar la seguridad desde tu servidor hacia esos servicios, no tiene nada que ver que sea GWT. En el ejemplo aplico spring en el cliente, pero porque es java y lo que hago es inyectar las credenciales en el contexto que luego se envía por el servicio httpinvoker.
    Deberías tener, por un lado, configurado el servicio RMI con la seguridad de spring, y el servlet que realice la llamada a ese servicio debe ser el encargado de inyectar las credenciales de seguridad.
    Espero que te sirva.
    Un saludo

  • Mario

    Si, eso esta claro. GWT es solo cliente, lo que pasa es que yo tengo un cliente GWT separado en varios módulos maven, y este módulo hace las llamadas RMI a los servicios que tengo en un servidor java-spring. Y en ese módulo en concreto sí uso spring también por comodidad y no hay problema, de hecho si deshabilito la seguridad todo funciona correctamente. El problema esta en que no se como inyectar esas credenciales ya que a pesar de hacer un setAuthentication al SecurityContext (como tú en el ejemplo) antes de hacer la llamada RMI, el servidor siempre me dice que el “Authentication Object” es nulo en el SecurityContext de la petición. No se si tengo que añadir algún tipo de configuración al web.xml de GWT o como hacerlo para que no se pierdan esos credenciales, ya que entiendo que en el momento de hacer la llamada se crea un SecurityContext diferente al que estoy manejando, el cual esta vacío. La implementación de la llamada RMI la tengo ubicada en el paquete “server” del modulo GWT, en una clase llamada CallServerRMIImpl que extiende de RemoteServiceServlet. Sabes de algún sitio donde documentarme más? la verdad que llevo varios días con esto y aún no veo la solución. Muchas gracias de nuevo.

  • alfonsomarin

    Hola Mario,
    El mecanismo es sencillo y no hay mas. Si estas teniendo problemas con la seguridad quizas el problema lo tengas en el servidor, donde tienes configurado el spring-security, y no en el cliente.
    En el cliente solo debes crear las credenciales y meterlas en el contexto de spring del cliente, del mismo contexto donde luego coges el servicio de llamada (cuidado que con spring puedes tener varios contextos activos). Con eso te llega al servidor rmi una petición formal con usuario y contraseña, si no funciona puede ser porque el error este en la configuración del servidor RMI.
    Prueba a simplificar la seguridad poniendo algún bean de credenciales simples o que se permita todo mientras estés autorizado, y ves probando.
    Suerte