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.
