Search code examples
javaspring-bootssltomcatcxf

CXF Failover Conduit modified on the fly - guarantee same SSL session and client thread safety?


I would like to implement CXF's Failover feature for both JAX-WS/RS clients in our application which have to call remote webservices via HTTPS using client certificates. 2 remote servers are in place: Primary + Alternate.

I am a bit lost how to guarantee the same functionality(with the right TLS params/SSL session) after the failover occurs.

JAX-WS client

There are 6 ClientServices which extend AbstractClientServiceImpl and use the same PortType wsClient bean and same basePath of the remote server, however they set their to be invoked service's last uri part with their own String getEndpointUrl(){ return "X";} method.

BasePath: https://remote1.server.com:443/api

Alternate addresses: https://remote2.server.com:443/api

Please take a look at the code - ClientEndpointAddressInterceptor. With this interceptor I am able to combine the basePath + lastUriPart and call the proper target endpoint for a particular ClientService - even when failover occurs. E.g:

target endpoint for ClientService1.class https://remote1.server.com:443/api/service1

target endpoint for ClientService2.class: https://remote1.server.com:443/api/service2

I have been struggling with the right settings/configuration for 2weeks. If I don't add tlsClientParameters or HttpClientPolicy to the extensor like so, then after having taken place the failover I would not be able to see any TLS settings for the newly created conduit!

// ssl settings
endpointInfo.addExtensor(tlsClientParameters);

I don't know if this is the right way to do it, but with this hacky workaround, I could have managed to "provide" the same tlsClientParams for the 2 remote calls(primary + alternate remote server address) - client certificate's SAN has both server's DNS names.

Technology: SpringBoot v2.1 + CXF 3.3.0 + Tomcat8.5

@Bean
public PortType wsClient(Properties properties,
                                        TLSClientParameters tlsClientParameters,
                                        LoggingFeature loggingFeature,
                                        ClientEndpointAddressFeature clientEndpointAddressFeature) {
        return createClient(properties, huTlsClientParameters, loggingFeature, 
    createFailoverFeature(properties.getFailover().getAddresses(), properties.getFailover().getRetryDelay()), 
                clientEndpointAddressFeature);
}

private FailoverFeature createFailoverFeature(String[] alternateAddresses, long failOverRetryDelay) {

    final FailoverFeature failOverFeature = new FailoverFeature();
    final SequentialStrategy strategy = new SequentialStrategy();
    strategy.setAlternateAddresses(Arrays.asList(alternateAddresses));
    strategy.setDelayBetweenRetries(failOverRetryDelay);
    failOverFeature.setStrategy(strategy);
    return failOverFeature;
}

private PortType createClient(Properties properties, TLSClientParameters tlsClientParameters, WebServiceFeature... features) {
    final Service service = new Service();
    final PortType client = service.getPortType(features);
    final Client clientProxy = ClientProxy.getClient(client);
    final EndpointInfo endpointInfo = clientProxy.getEndpoint().getEndpointInfo();

    final HTTPClientPolicy httpClientPolicy = Optional.ofNullable(endpointInfo.getExtensor(HTTPClientPolicy.class))
            .orElseGet(() -> {
                // if there is no XYFeature, policy has to be initialized at this point
                final HTTPClientPolicy policy = new HTTPClientPolicy();
                policy.setAccept(HuHttpHeaders.HEADER_ACCEPT_VALUE);
                endpointInfo.addExtensor(policy);
                return policy;
            });
    // timeout settings
    httpClientPolicy.setConnectionTimeout(properties.getConnectionTimeout());
    httpClientPolicy.setReceiveTimeout(properties.getReadTimeout());
    // set content-length by default
    httpClientPolicy.setAllowChunking(false);
    // ssl settings
    endpointInfo.addExtensor(tlsClientParameters);
    // set global requestContext
    setRequestContext((BindingProvider) client, properties.getUrl());
    return client;
}


private void setRequestContext(BindingProvider bp, String server) {
    bp.getRequestContext().put(Message.ENDPOINT_ADDRESS, server);
    bp.getRequestContext().put(ClientImpl.THREAD_LOCAL_REQUEST_CONTEXT, true);
    bp.getRequestContext().put(Message.SCHEMA_VALIDATION_ENABLED, true);
    bp.getRequestContext().put(BindingProvider.SOAPACTION_USE_PROPERTY, true);
}


public class ClientEndpointAddressOutInterceptor extends AbstractPhaseInterceptor<Message> {
    public ClientEndpointAddressOutInterceptor() {
        super(Phase.PREPARE_SEND);
        addBefore(MessageSenderInterceptor.class.getName());
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        final String previousEndpointAddress = (String) message.get(Message.ENDPOINT_ADDRESS);
        final String lastUriPath = (String) message.get("lastUriPath");
        message.put(Message.ENDPOINT_ADDRESS, previousEndpointAddress + lastUriPath);
   }
}
public abstract class AbstractClientServiceImpl implements ClientService {
        public AbstractClientServiceImpl(PortType PortType) {
            this.portType = portType;
        }
        @Override
        public HttpStatus sendRequest(String xmlData) {
            ...
            final BindingProvider bindingProvider = (BindingProvider) this.portType;
            try {
            // set http header for this particular request          
            // also store bindingProvider.getRequestContext().put("lastUriPath", getEndpointUrl()); 
            HttpHeaderUtil.setHttpHeader(getSoapActionUrl(), bindingProvider, getEndpointUrl());
                execute(xmlData, createSoapHeader());
            } catch (Exception ex) {
                ...
            }
            ...
        }
        // last uri part 
        protected abstract String getEndpointUrl();
        // execute is responsible for calling a particular service. e.g: in ClientService1.class portType.callService1(xmlData);
        protected abstract void execute(String xmlData, TransactionHeader transactionHeader);
}

Questions

JAX-WS client

  • what happens with the previously set (thread-local-requests,true) setting after the failover occurs? will the following service calls being invoked by the 6 ClientServices class remain thread-safe afterwards?
  • I have a requirement that I am supposed to use the SSL session reuse mechanism. IF CXF's failover feature removes the conduit and creates a new one, then how can I apply this again after the failover? Or does Tomcat somehow handles this and no need to bother with this? I am not expert in CXF and I haven't found much info related to SSL session in CXF's site/mailing list.

JAX-RS client

Actually the same 2 concerns/questions which are addressed above for JAX-WS client.

The only difference is that RS has 3 client call methods using the same client instance declared as

private WebClient webClient(){
   final JAXRSClientFactoryBean clientFactoryBean = new JAXRSClientFactoryBean();
   clientFactoryBean.setThreadSafe(true);
   final WebClient webClient = clientFactoryBean.createWebClient();
        final ClientConfiguration config = WebClient.getConfig(webClient);
        config.getRequestContext().put(HTTPConduit.NO_IO_EXCEPTIONS, Boolean.TRUE);
        // ssl settings
    config.getEndpoint().getEndpointInfo().addExtensor(tlsClientParameters);
    return webClient;
}

Thank you for the help in advance.


Solution

  • The solutions is to use CXF's HTTPConduitConfigurator as detailed here: https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=49941#ClientHTTPTransport(includingSSLsupport)-HowtouseHTTPConduitConfigurer?

    HTTPConduitConfigurer httpConduitConfigurer = new HTTPConduitConfigurer() {
        public void configure(String name, String address, HTTPConduit c) {
            c.setTlsClientParameters(_tlsParams);
        }
    }
    bus.setExtension(httpConduitConfigurer, HTTPConduitConfigurer.class);
    

    This will set the the TLS Client Parameters on all conduits that are created, e.g. when using failover.