Pass data from a SOAP handler to a webservice server Class
Asked Answered
H

2

5

I have a Spring boot SOAP services with cxf, and my Consumers are passing me SSO token in http header.. I am able to retrieve the SSO token using JAX-WS handler. I am saving that SSO token into handler class level variable, and after control going through various classes it reaches to a point where I have to make a request to another service and have to pass the same SSO token, but in my Connection class the SSO token value is NULL.

@Component
public class EndPointHandler implements SOAPHandler<SOAPMessageContext> {
    private List<String> ssoToken;
    private Map<String, List<String>> headers;

    @Override
    public boolean handleMessage(SOAPMessageContext context) {
        Boolean isResponse = (Boolean) context.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (!isResponse) {
            headers = ((Map<String, List<String>>) context.get(MessageContext.HTTP_REQUEST_HEADERS));
            if (headers != null) {
                if (!headers.get("SSOToken").get(0).isEmpty()) {
                    List<String> ssoToken = headers.get("SSOToken");
                    LOGGER.info(ssoToken.get(0));
                    this.ssoToken = ssoToken;
                } else {
                    LOGGER.error("SSO Token value cannot be empty");
                    return false;
                }
            }
        }
        return true;
    }

    public void setSSOToken() {
        headers.put("SSOToken", this.ssoToken);
    }
}

In my Connection class I have to set this SSO token as a header and make a call to another service but SSO token value is NULL.

Connection Class:

@Component
public class ConnectionManager {
    private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class);

    @Autowired
    private EndPointHandler handler;

    private void establishConnection(String uri) throws FileNetIntegrationException {
        handler.ssoToken; //  --> I need SSO token here but the value is NULL;
    }
}

This is how I set the handler chain in my WebServiceConfig class:

@Bean
public Endpoint endpoint(Bus bus) {
    EndpointImpl endpoint = new EndpointImpl(bus, changeServiceEndpoint);
    WebService ws = AnnotationUtils.findAnnotation(endpoint.getImplementorClass(), WebService.class);
    endpoint.setAddress("/" + ws.serviceName());
    endpoint.publish();
    SOAPBinding binding = (SOAPBinding) endpoint.getBinding();
    ArrayList<Handler> handlerChain = new ArrayList<>();
    handlerChain.add(new EndPointHandler());
    binding.setHandlerChain(handlerChain);
    return endpoint;
}
Honorable answered 21/8, 2020 at 22:28 Comment(0)
H
4

I think, I got a solution right after posting the last piece of code here when I noticed that I used new EndPointHandler() while adding it into handler chain.. I tried using Autowired it and it worked for me.

Honorable answered 24/8, 2020 at 22:21 Comment(3)
One remark here without knowing too much of your code: EndPointHandler is a singleton right? That means it is shared within your application context. Storing request specific information can lead to unwanted side effects. When multiple users use your app in parallel, they will override each others ssoToken. As I assume ssoToken is security relevant, this can result in a major security flaw.Ballyhoo
You are absolutely right.. I am in process to remove sso token value from handler class variable and trying to use either ThreadLocal Or something else.. still figuring it outHonorable
@Ballyhoo what should I do to save ssoToken into their respective requests so that they don't override each other?Honorable
H
2

As far as I know, jax-ws context and spring-ws context don't intersect with each other. So this is not a solution but a workaround. As another workaround, you can use some singleton synchronizedMap, or use a jax-ws Handler to redirect requests to another endpoint with extended api.


Access HTTP headers of SOAP messages using JAX-WS handler

If you have configured SOAPHandler for your JAX-WS WebService, then you can access the HTTP headers and pass them on inside the SOAPBody as fields of the SOAPElement type, and vice versa. To do this you have to extend the messages POJOs with these fields annotated as XmlElement. Then you can access them from your WebService.

Note: If you have a dynamically generated WSDL schema based on Java classes, then it changes too. But anyway, the old requests and responses are accepted, because these fields are not required by default.


Example: UserService - the incoming message has one field of String type and outgoing message has one field of int type. Let's extend each of them with one ssoToken field of String type. We'll read this token from HTTP headers of incoming message and send it back in HTTP headers of outgoing message.

GetUserRequest.java:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType
@XmlRootElement(name = "getUserRequest")
public class GetUserRequest {
    @XmlElement
    protected String ssoToken;
    @XmlElement(required = true)
    protected String name;

    // getters + setters + constructor
}

GetUserResponse.java:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType
@XmlRootElement(name = "getUserResponse")
public class GetUserResponse {
    @XmlElement
    protected String ssoToken;
    @XmlElement(required = true)
    protected int age;

    // getters + setters + constructor
}

UserServiceHandler.java

public class UserServiceHandler implements SOAPHandler<SOAPMessageContext> {
    @Override
    public boolean handleMessage(SOAPMessageContext soapMessageContext) {
        Boolean isResponse =
                (Boolean) soapMessageContext
                        .get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (!isResponse) {
            // Request message
            return processIncomingMessage(soapMessageContext);
        } else {
            // Response message
            return processOutgoingMessage(soapMessageContext);
        }
    }
}

processIncomingMessage:

@SuppressWarnings("unchecked")
private boolean processIncomingMessage(SOAPMessageContext soapMessageContext) {
    Map<String, List<String>> headers =
            ((Map<String, List<String>>) soapMessageContext
                    .get(MessageContext.HTTP_REQUEST_HEADERS));
    if (headers == null || headers.isEmpty()) {
        return false;
    }
    List<String> ssoTokens = headers.get("SSOToken");
    if (ssoTokens == null || ssoTokens.size() != 1) {
        return false;
    }
    String ssoToken = ssoTokens.get(0);
    if (ssoToken == null || ssoToken.isEmpty()) {
        return false;
    }
    try {
        Iterator<Node> iterator =
                soapMessageContext.getMessage().getSOAPBody().getChildElements();
        while (iterator.hasNext()) {
            Node element = iterator.next();
            if (element.getNodeName().contains("getUserRequest")) {
                ((SOAPElement) element)
                        .addChildElement("ssoToken", element.getPrefix())
                        .setTextContent(ssoToken);
            }
        }
    } catch (SOAPException e) {
        e.printStackTrace();
        return false;
    }
    return true;
}

processOutgoingMessage:

@SuppressWarnings("unchecked")
private boolean processOutgoingMessage(SOAPMessageContext soapMessageContext) {
    Map<String, List<String>> headers =
            ((Map<String, List<String>>) soapMessageContext
                    .get(MessageContext.HTTP_RESPONSE_HEADERS));
    if (headers == null || headers.isEmpty()) {
        soapMessageContext
                .put(MessageContext.HTTP_RESPONSE_HEADERS, new HashMap<>());
        headers = ((Map<String, List<String>>) soapMessageContext
                .get(MessageContext.HTTP_RESPONSE_HEADERS));
    }
    try {
        Iterator<Node> iterator = soapMessageContext
                .getMessage().getSOAPBody().getChildElements();
        while (iterator.hasNext()) {
            Node element = iterator.next();
            if (element.getNodeName().contains("getUserResponse")) {
                Iterator<Node> iteratorResponse =
                        ((SOAPElement) element).getChildElements();
                while (iteratorResponse.hasNext()) {
                    Node childElement = iteratorResponse.next();
                    if (childElement.getNodeName().contains("ssoToken")) {
                        String ssoToken = childElement.getTextContent();
                        headers.put("SSOToken",
                                Collections.singletonList(ssoToken));
                        element.removeChild(childElement);
                    }
                }
            }
        }
    } catch (SOAPException e) {
        e.printStackTrace();
        return false;
    }
    return true;
}

UserService.java

@WebService(endpointInterface = "com.example.UserPort",
        serviceName = "UserService")
@HandlerChain(file="handler-chain.xml")
public class UserService implements UserPort {
    public GetUserResponse getUser(GetUserRequest request) {
        GetUserResponse response = new GetUserResponse();
        response.setAge(23);
        response.setSsoToken(request.getSsoToken());
        return response;
    }
}

Testing with SoapUI:

Request headers:

POST http://localhost:8080/ws/userService HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: ""
SSOToken: 6cd506ac-738a-43ca-aee8-d13b78180605
Content-Length: 296
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

Request message:

<soapenv:Envelope
        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:spr="http://example.com/jax-ws-sample">
    <soapenv:Header/>
    <soapenv:Body>
        <spr:getUserRequest>
            <spr:name>John</spr:name>
        </spr:getUserRequest>
    </soapenv:Body>
</soapenv:Envelope>

Response headers:

HTTP/1.1 200 
SSOToken: 6cd506ac-738a-43ca-aee8-d13b78180605
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Thu, 27 Aug 2020 15:54:33 GMT
Keep-Alive: timeout=20
Connection: keep-alive

Response message:

<S:Envelope 
        xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <S:Body>
        <getUserResponse xmlns="http://example.com/jax-ws-sample">
            <age>23</age>
        </getUserResponse>
    </S:Body>
</S:Envelope>
Hydromel answered 27/8, 2020 at 16:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.