Jan Vladimir Mostert
Jan Vladimir Mostert

Reputation: 13002

Getting content of a SOAP Header using Spring WS

I'm trying to build an endpoint that will receive SOAP messages from a client. The message I'm receiving contains a username and password inside the soap header ...

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://www.company.com/Application">
    <soapenv:Header  xmlns:wsse="http://__________.xsd">
        <wsse:Security >
            <wsse:UsernameToken>
                <wsse:Username>username</wsse:Username>
                <wsse:Password>password</wsse:Password>
            </wsse:UsernameToken>
        </wsse:Security>

   </soapenv:Header>
   <soapenv:Body>

I'm using Spring WS - the obvious solution is to create a filter inside web.xml that will bypass Spring WS completely, parse the SOAP message, extract the username and password and then continue to Spring WS which will parse the SOAP again.

Is there a way to get the content of the header without circumventing Spring WS?

I've tried adding a bean inside sws:interceptors:

<sws:interceptors>

    <!-- extract Security details from Header -->
    <bean class="com.company.application.service.SecurityInterceptorService" />

    <!-- log full Body of request -->
    <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>

    <!-- validate Request against XSD to make sure it's a valid request -->
    <bean id="CompanyApplication" class="com.company.application.interceptor.ValidatingInterceptor">
        <property name="schema" value="/WEB-INF/_______________.xsd" />
        <property name="validateRequest" value="true" />
        <property name="validateResponse" value="true" />
    </bean>

</sws:interceptors>

and then implementing that class:

public class SecurityInterceptorService implements EndpointInterceptor {


    @Override
    public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {

        System.out.println("---------------");
        System.out.println("handleRequest")                                             ;
        System.out.println("---------------");


        return true;
    }

    @Override
    public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {

        System.out.println("---------------");
        System.out.println("handleResponse");
        System.out.println("---------------");



        return true;
    }

    @Override
    public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {

        System.out.println("---------------");
        System.out.println("handleFault");
        System.out.println("---------------");


        return true;
    }

    @Override
    public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception {

        System.out.println("---------------");
        System.out.println("afterCompletion");
        System.out.println("---------------");

    }



}

endpoint only contains data about the endpoint inside handleRequest and after traversing through many layers and layers inside messageContext while in debug mode, I can't seem to spot the content of the header.

Is the content I'm looking for inside messageContext and if so, how do I access it?

Upvotes: 3

Views: 23486

Answers (3)

Xavier FRANCOIS
Xavier FRANCOIS

Reputation: 712

There is no easy way to unmarshall Soap headers with Spring-ws (it's currently not supported) However, you can access the SoapHeaderElement in your @PayloadRoot annotated method, and do the process of unmarshalling with JAXB.

@Endpoint
public class SubmitEndpoint implements EndpointInterface {

    private static final String NAMESPACE_URI = "http://www.example.com/namespace";

    private Security unmarshallSecurityFromSoapHeader(SoapHeaderElement header) {
        Security security = null;
        try {

            JAXBContext context = JAXBContext.newInstance(Security.class);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            security = (Security) unmarshaller.unmarshal(header.getSource());

        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return security;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "submit")
    @ResponsePayload
    public SubmitResponse submit(@RequestPayload Submit submit, @SoapHeader(
            value = "{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security") SoapHeaderElement wsseSecurityHeader) throws JAXBException {


        Security security = unmarshallSecurityFromSoapHeader(wsseSecurityHeader);

    }
}

Security.java

@Getter
@Setter
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(namespace = Security.SECURITY_NS, name = "Security")
public class Security {

    public static final String SECURITY_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";

    @XmlElement(namespace = SECURITY_NS, name = "UsernameToken")
    private UsernameToken usernameToken;

}

UsernameToken.java

@Getter
@Setter
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(namespace = Security.SECURITY_NS, name = "UsernameToken")
public class UsernameToken {

    @XmlElement(name = "Username", namespace = Security.SECURITY_NS)
    private String username;

    @XmlElement(name = "Password", namespace = Security.SECURITY_NS)
    private String password;

}

Upvotes: 5

Julio Villane
Julio Villane

Reputation: 1034

If you are using spring-boot you can use this kind of configuration:

@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    PayloadValidatingInterceptor validatingInterceptor = new PayloadValidatingInterceptor();
    validatingInterceptor.setValidateRequest(true);
    validatingInterceptor.setValidateResponse(true);
    validatingInterceptor.setXsdSchema(resourceSchema());
    interceptors.add(validatingInterceptor);
  }

  @Bean
  public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
    MessageDispatcherServlet servlet = new MessageDispatcherServlet();
    servlet.setApplicationContext(applicationContext);
    servlet.setTransformWsdlLocations(true);
    return new ServletRegistrationBean(servlet, "/api/*");
  }

  @Bean(name = "registros")
  public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
    DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
    wsdl11Definition.setPortTypeName("ResourcePort");
    wsdl11Definition.setLocationUri("/api");
    wsdl11Definition.setTargetNamespace("http://resource.com/schema");
    wsdl11Definition.setSchema(resourceSchema());
    return wsdl11Definition;
  }

  @Bean
  public XsdSchema resourceSchema() {
    return new SimpleXsdSchema(new ClassPathResource("registro.xsd"));
  }
}

In this example the addInterceptors method is the important one, the others 3 are basic to expose a WSDL API.

Maybe it'll be useful for someone else.

Upvotes: 3

VirtualTroll
VirtualTroll

Reputation: 3091

From the messageContext object, you can retrieve either the request or the response (In your case, I guess you need the request).

The request/response is basically a WebServiceMessage. If you examine the webServiceMessage, you will see that the object can be casted to a SoapMessage. From the soap message, you can now get the soap header.

WebServiceMessage webServiceMessageRequest = messageContext_.getRequest();
SoapMessage soapMessage = (SoapMessage) webServiceMessageRequest;
SoapHeader soapHeader = soapMessage.getSoapHeader()

Afterwards, You might want to get the source object and convert it to a DOMSource object and then get the Node object which make the information retrieval much easier.

Source bodySource = soapHeader .getSource();
DOMSource bodyDomSource = (DOMSource) bodySource;
Node bodyNode = _bodyDomSource.getNode();

Upvotes: 12

Related Questions