Como utilizar la seguridad de Spring Security para autenticar un usuario/contraseña de un servicio web implementado con CXF mediante WSS4J (Java WS-Security de Apache):
En el aplication context de spring, primero definimos los namespaces, importamos los xml de CXF y definimos el "Interceptor" que gestionará la seguridad:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:cxf="http://cxf.apache.org/core" xmlns:util="http://www.springframework.org/schema/util" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd" > <!-- Load CXF modules from cxf.jar --> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <bean id="WSS4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"> <property name="properties"> <map> <entry key="action" value="UsernameToken Timestamp"/> <entry key="passwordType" value="PasswordDigest"/> <entry key="passwordCallbackRef"> <bean class="my.company.SecurityInPasswordHandler"/> </entry> </map> </property> </bean> </beans>
Y luego agregamos el interceptor a los webservices que necesiten seguridad:
<jaxws:endpoint id="salaWebService" implementor="#salaService" address="/salas"> <jaxws:inInterceptors> <ref bean="WSS4JInInterceptor"/> </jaxws:inInterceptors> </jaxws:endpoint>
Asumiendo que tenemos el siguiente servicio:
@WebService @SOAPBinding public interface SalaService { @WebResult(name = "sala") public abstract Sala get(@WebParam(name = "id") Long id); } @Service("salaService") @WebService(serviceName = "SalaService", portName = "SalaPort", endpointInterface = "my.company.service.SalaService") public class SalaServiceImpl implements SalaService { public Sala get(Long id) { return (Sala) executor.execute(SalaBusinessOperations.GET, new Long(id)); } }
Y que CXF está configurado en el web.xml de la siguiente forma:
<servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping>
La seguridad de spring debería estar configurada, por ejemplo, con lo siguiente:
<http > <intercept-url pattern="/services/**" access="ROLE_ANONYMOUS" /> <intercept-url pattern="/**" access="ROLE_USER" /> <logout/> <anonymous/> </http>
Finalmente podemos dedicarnos a la clase que gestionará la seguridad:
public class SecurityInPasswordHandler implements CallbackHandler { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userService; public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException, AuthenticationException { WSPasswordCallback pwdCallback = (WSPasswordCallback) callbacks[0]; int usage = pwdCallback.getUsage(); if ((usage == WSPasswordCallback.USERNAME_TOKEN) || (usage == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN)) { String password = pwdCallback.getPassword(); if (usage == WSPasswordCallback.USERNAME_TOKEN) { UserDetails userDetails = userService.loadUserByUsername(pwdCallback.getIdentifier()); password = userDetails.getPassword(); } Authentication authentication = new UsernamePasswordAuthenticationToken(pwdCallback.getIdentifier(), password); authentication = authenticationManager.authenticate(authentication); //throws AuthenticationException SecurityContextHolder.getContext().setAuthentication(authentication); // Return the password to the caller pwdCallback.setPassword(password); } } }
Con esto logramos gestionar passwords enviadas en plano (PasswordText) y encriptadas (PasswordDigest). En ambos casos creando un SecurityContext para que la petición tenga los roles del usuario y puedan utilizarse las anotaciones "@Secured" de Spring Security.
1 comentario:
Hola Niko, encontré tu blog buscando cosas sobre CXF+Spring en la web. Es un post tuyo del año pasado, pero aún así comento, con la esperanza de recibir respuesta!!.
Estaba buscando hacer justamente lo que describís acá pero usando certificados X509. Partiendo de lo tuyo, pensaba aplicar la misma estrategia, pero sentite libre de corregirme si me equivoco en cuanto a la aproximación.
Entonces, la idea sería generar un X509AuthenticationToken en lugar de tu UNAuth Token. El código quedaría algo así:
if ((usage == WSPasswordCallback.SIGNATURE) )
{
UserDetails userDetails = userService.loadUserByUsername(pc.getIdentifer());
String password = userDetails.getPassword();
Authentication authentication = new X509AuthenticationToken(...);
authentication = authenticationManager.authenticate(authentication); //throws AuthenticationException
SecurityContextHolder.getContext().setAuthentication(authentication);
// Return the password to the caller
pc.setPassword(password);
}
El tema es que para generar el new X509AuthenticationToken necesito un X509Certificate que no se de donde obtener..
Alguna idea al respecto?, se te ocurre alguna aproximación alternativa?.
Muchas gracias desde ya!!
Publicar un comentario