pio_trek248
pio_trek248

Reputation: 21

SOAP security, client authentication problem

I tried to connect to TERYT but I get an error about the lack of authorization. WSDL address - https://uslugaterytws1test.stat.gov.pl/wsdl/terytws1.wsdl

java.lang.IllegalStateException: Failed to execute ApplicationRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:765) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:752) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) [spring-boot-2.7.1.jar:2.7.1]
at com.example.teryt.TerytApplication.main(TerytApplication.java:15) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_332]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_332]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_332]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_332]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.7.1.jar:2.7.1]

Caused by: com.sun.xml.internal.ws.fault.ServerSOAPFaultException: Client received SOAP Fault from server: An error occurred when processing the security tokens in the message. Please see the server log to find more detail regarding exact cause of the failure. at com.sun.xml.internal.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:178) ~[na:1.8.0_332] at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:116) ~[na:1.8.0_332] at com.sun.xml.internal.ws.client.sei.StubHandler.readResponse(StubHandler.java:238) ~[na:1.8.0_332] at com.sun.xml.internal.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:189) ~[na:1.8.0_332] at com.sun.xml.internal.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:276) ~[na:1.8.0_332] at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:104) ~[na:1.8.0_332] at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:77) ~[na:1.8.0_332] at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:147) ~[na:1.8.0_332] at com.sun.proxy.$Proxy83.pobierzListeWojewodztw(Unknown Source) ~[na:na] at com.example.teryt.TerytClient.getResponse(TerytClient.java:31) ~[classes/:na] at com.example.teryt.TerytApplication.lambda$lookup$0(TerytApplication.java:20) [classes/:na] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:762) [spring-boot-2.7.1.jar:2.7.1]

This is what the correct query should look like:

<?xml version="1.0" encoding="UTF-8"?>

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://tempuri.org/"> soapenv:Header <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> <wsse:UsernameToken wsu:Id="UsernameToken-2018-01-11T00:16:02+00:00"> wsse:UsernameTestPubliczny</wsse:Username> wsse:Password1234abcd</wsse:Password> </wsse:UsernameToken> </wsse:Security> <wsa:Action xmlns:wsa="http://www.w3.org/2005/08/addressing">http://tempuri.org/ITerytWs1/PobierzListeWojewodztw</wsa:Action> </soapenv:Header> soapenv:Body ns1:PobierzListeWojewodztw ns1:DataStanu2020-05-18</ns1:DataStanu> </ns1:PobierzListeWojewodztw> </soapenv:Body> </soapenv:Envelope>

TerytApplication.java:

@SpringBootApplication

public class TerytApplication {

public static void main(String[] args) {
    SpringApplication.run(TerytApplication.class, args);
}

@Bean
ApplicationRunner lookup(TerytClient terytClient){
    return args -> System.out.println(terytClient.getResponse());
}

}

Client.java:

@Component

public class TerytClient extends WebServiceGatewaySupport {

public Boolean getResponse() throws DatatypeConfigurationException {
    GregorianCalendar cal = new GregorianCalendar();
    cal.setTime(new Date());
    XMLGregorianCalendar xCal = DatatypeFactory.newInstance()
            .newXMLGregorianCalendar(cal);

    ITerytWs1 instance = new TerytWs1().getCustom(new AddressingFeature(true));
    Binding binding = ((BindingProvider) instance).getBinding();
    List<Handler> handlerList = binding.getHandlerChain();
    if (handlerList == null)
        handlerList = new ArrayList<Handler>();
    handlerList.add(new TerytHeaderHandler("TestPubliczny", "1234abcd"));
    binding.setHandlerChain(handlerList);
    System.out.println(instance.pobierzListeWojewodztw(xCal));

    return instance.czyZalogowany();
}

HeaderHandler.java

   @Override
public boolean handleMessage(SOAPMessageContext smc) {
    Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
    if (outboundProperty.booleanValue()) {
        try {
            SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
            SOAPHeader header = envelope.getHeader();
            SOAPElement security = header.addChildElement("Security", "wsse",
                    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            SOAPElement usernameToken = security.addChildElement("UsernameToken", "wsse");
            SOAPElement username = usernameToken.addChildElement("Username", "wsse");
            username.addTextNode(wsUser);
            SOAPElement password = usernameToken.addChildElement("Password", "wsse");
            password.setAttribute("Type",
                    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
            password.addTextNode(wsPassword);
        } catch (Exception e) {
            e.printStackTrace();
        }
    } else {
        //This handler does nothing with the response from the Web Service
        //even though it should probably check its mustUnderstand headers
        SOAPMessage message = smc.getMessage();
    }
    return outboundProperty;
}

@Override
public boolean handleFault(SOAPMessageContext context) {
    // TODO Auto-generated method stub
    return false;
}

@Override
public void close(MessageContext context) {
    // TODO Auto-generated method stub
}

// Gets the header blocks that can be processed by this Handler instance.
@Override
public Set<QName> getHeaders() {
    QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
            "Security");
    HashSet<QName> headers = new HashSet<QName>();
    headers.add(securityHeader);
    return headers;
}

}

I would be grateful for any help and suggestions.

Upvotes: 2

Views: 1623

Answers (1)

Antonio Musarra
Antonio Musarra

Reputation: 370

When building the WS-Security headers, I recommend you to use Apache WSS4J. I am attaching a code example that I use frequently.

import lombok.extern.slf4j.Slf4j;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.message.WSSecHeader;
import org.apache.wss4j.dom.message.WSSecTimestamp;
import org.apache.wss4j.dom.message.WSSecUsernameToken;
import org.w3c.dom.Element;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

@Slf4j
public class ClientWSSecurityUsernameTokenHandler implements
    SOAPHandler<SOAPMessageContext> {

    public ClientWSSecurityUsernameTokenHandler(
        String userName, String password) {
        _userName = userName;
        _password = password;
    }

    @Override
    public Set<QName> getHeaders() {
        final String NAMESPACE_URI =
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
        final String LOCAL_PART = "Security";
        final String PREFIX = "wsse";

        final QName wsSecurity = new QName(NAMESPACE_URI, LOCAL_PART, PREFIX);
        final Set<QName> headers = new HashSet<QName>();
        headers.add(wsSecurity);

        return headers;
    }

    @Override
    public boolean handleMessage(SOAPMessageContext soapMessageContext) {
        Boolean outboundProperty = (Boolean) soapMessageContext.get(
            MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (outboundProperty) {
            try {

                SOAPMessage soapMessage = soapMessageContext.getMessage();
                SOAPPart soapPart = soapMessage.getSOAPPart();

                SOAPHeader soapHeader = soapMessage.getSOAPHeader();

                if (null == soapHeader) {
                    SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
                    soapHeader = soapEnvelope.addHeader();
                }

                WSSecHeader wsSecHeader = new WSSecHeader(soapPart);
                Element securityElement =
                    wsSecHeader.insertSecurityHeader();

                _addUsernamePassword(soapMessageContext, soapPart, wsSecHeader);

                WSSecTimestamp wsSecTimeStamp = new WSSecTimestamp(wsSecHeader);
                wsSecTimeStamp.build();

                _appendSecurityHeader(soapHeader, securityElement);

                _logSOAPMessage(soapMessageContext.getMessage());
            }
            catch (Exception exception) {
                log.error(exception.getMessage(), exception);
            }
        }
        return true;
    }

    private String getPassword() {
        return _password;
    }

    private String getUserName() {
        return _userName;
    }

    @Override
    public boolean handleFault(SOAPMessageContext soapMessageContext) {
        _logSOAPMessage(soapMessageContext.getMessage());
        return true;
    }

    @Override
    public void close(MessageContext messageContext) {
    }

    private String _userName;
    private String _password;

    private void _appendSecurityHeader(
        SOAPHeader soapHeader, Element securityElement) {
        soapHeader.removeChild(securityElement);
        soapHeader.appendChild(securityElement);
    }

    private void _addUsernamePassword(
        SOAPMessageContext soapMessageContext, SOAPPart soapPart,
        WSSecHeader wsSecHeader) {
        log.debug("Adding Username Token for the user: {}", getUserName());

        WSSecUsernameToken usernameToken = new WSSecUsernameToken(wsSecHeader);
        usernameToken.setUserInfo(getUserName(), getPassword());
        usernameToken.setPasswordType(WSConstants.PASSWORD_TEXT);
        usernameToken.addCreated();
        usernameToken.addNonce();
        usernameToken.build();
    }

    private void _logSOAPMessage(SOAPMessage message) {
        if (log.isDebugEnabled()) {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            try {
                message.writeTo(bout);
                String msg = bout.toString("UTF-8");
                log.debug("Start of SOAP Message");
                log.debug(msg);
                log.debug("End of SOAP Message");
            }
            catch (SOAPException | IOException exception) {
                if (log.isErrorEnabled()) {
                    log.error(exception.getMessage(), exception);
                }
            }
        }
    }
}

Source Code 1 - SOAP Handler ClientWSSecurityUsernameTokenHandler

The following code instead shows how to add the handler to the chain.

ClientWSSecurityUsernameTokenHandler
    clientWSSSecurityUsernameTokenHandler =
        new ClientWSSecurityUsernameTokenHandler(soapClientProperties.getUsername(),
            soapClientProperties.getPassword());

    List<Handler> handlerChain = List.of(clientWSSSecurityUsernameTokenHandler);

    ((BindingProvider) serviceSOAP).getBinding().setHandlerChain(handlerChain);

Source Code 2 - Add the ClientWSSecurityUsernameTokenHandler to SOAP Handler Chain

The following code instead shows how to add the wss4j-ws-security-dom as dependency.

<dependency>
  <groupId>org.apache.wss4j</groupId>
  <artifactId>wss4j-ws-security-dom</artifactId>
  <version>2.4.1</version>
</dependency>

Upvotes: 1

Related Questions