Wednesday, August 22, 2007

Using SSL with XFire/CXF - Battling "Illegal Protocol https for HTTP URLConnection Factory"

Today I had trouble getting CXF (the new xfire) to access a WSDL file located on a SSL-based URL. In addition, it was behind basic authentication. Let us jump right in. I got this error:
org.apache.cxf.interceptor.Fault: Could not send Message.
at org.apache.cxf.interceptor.MessageSenderInterceptor.handleMessage(MessageSenderInterceptor.java:48)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:206)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:253)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:204)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:179)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:199)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:185)
at com.blogspot.techpolesen.ws.DynamicClient.atSystemHentBilagsnavneForSag(DynamicClient.java:66)
at com.blogspot.techpolesen.ws.DynamicClient.main(DynamicClient.java:25)
Caused by: java.io.IOException: Illegal Protocol https for HTTP URLConnection Factory.
at org.apache.cxf.transport.http.HttpURLConnectionFactoryImpl.createConnection(HttpURLConnectionFactoryImpl.java:44)
at org.apache.cxf.transport.http.HTTPConduit.prepare(HTTPConduit.java:468)
at org.apache.cxf.interceptor.MessageSenderInterceptor.handleMessage(MessageSenderInterceptor.java:46)
... 8 more
Exception in thread "main" org.apache.cxf.interceptor.Fault: Could not send Message.
at org.apache.cxf.interceptor.MessageSenderInterceptor.handleMessage(MessageSenderInterceptor.java:48)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:206)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:253)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:204)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:179)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:199)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:185)
at com.blogspot.techpolesen.ws.DynamicClient.atSystemHentBilagsnavneForSag(DynamicClient.java:66)
at com.blogspot.techpolesen.ws.DynamicClient.main(DynamicClient.java:25)
Caused by: java.io.IOException: Illegal Protocol https for HTTP URLConnection Factory.
at org.apache.cxf.transport.http.HttpURLConnectionFactoryImpl.createConnection(HttpURLConnectionFactoryImpl.java:44)
at org.apache.cxf.transport.http.HTTPConduit.prepare(HTTPConduit.java:468)
at org.apache.cxf.interceptor.MessageSenderInterceptor.handleMessage(MessageSenderInterceptor.java:46)
... 8 more
And googling it eventually led me to the page on configuring SSL support but it does not tell the complete story. What solved my problem was to add a cxf.xml to the root of my classpath, with this content:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://cxf.apache.org/configuration/security"
xmlns:http="http://cxf.apache.org/transports/http/configuration"
xsi:schemaLocation="
http://cxf.apache.org/configuration/security
http://cxf.apache.org/schemas/configuration/security.xsd
http://cxf.apache.org/transports/http/configuration
http://cxf.apache.org/schemas/configuration/http-conf.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<http:conduit name="{https://myhost.com/this/is/namespace}MyPortName.http-conduit">
<http:authorization>
<sec:UserName>usernamehere</sec:UserName>
<sec:Password>passwordhere</sec:Password>
</http:authorization>
<http:tlsClientParameters secureSocketProtocol="SSL">

</http:tlsClientParameters>
</http:conduit>
</beans>
It was the secureSocketProtocol="SSL" attribute that solved the problem. Of course, you need to change "{https://myhost.com/this/is/namespace}MyPortName" into a namespace and port name that matches your WSDL. Also note, that I have added static basic authentication credentials to get access to my wsdl.

13 comments:

douglas said...

I am having the same problem.


I have a simple web service and client. When I host it on Tomcat at http://localhost:8080/service/services, the client connects just fine, but it does not work when using https://localhost:8443/service/services.

I am using spring.

For connection on http://localhost:8080/service/services

here is the relevant portion of the spring config file on the server:

<jaxws:endpoint
id="connectionAdmin"
implementor="#connectionAdminService"
address="/connectionAdminService" />


here is the relevant portion of the spring file for the client:

<bean id="adminClient" class="gov.nwcg.isuite.service.webservice.admin.ConnectionAdminService" factory-bean="adminClientFactory" factory-method="create"/>

<bean id="adminClientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass" value="gov.nwcg.isuite.service.webservice.admin.ConnectionAdminService"/>
<property name="address" value="http://localhost:8080/service/services/connectionAdminService"/>
</bean>


The server page at http://localhost:8080/service/services/ returns:
{http://gov.nwcg.isuite.webservice}ConnectionAdmin

When I run the client, it connects to the web service and everything is fine

now I want to connect on https

I change the spring config on the client to have an http:conduit with SSL and change the address of the client factory. It now reads:

<http:conduit name="{https://gov.nwcg.isuite.webservice}ConnectionAdmin.http-conduit">
<http:tlsClientParameters secureSocketProtocol="SSL">
</http:tlsClientParameters>
</http:conduit>

<bean id="adminClient" class="gov.nwcg.isuite.service.webservice.admin.ConnectionAdminService" factory-bean="adminClientFactory" factory-method="create"/>

<bean id="adminClientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass" value="gov.nwcg.isuite.service.webservice.admin.ConnectionAdminService"/>
<property name="address" value="https://localhost:8443/service/services/connectionAdminService"/>
</bean>


The server page at https://localhost:8443/service/services/ returns:
{http://gov.nwcg.isuite.webservice}ConnectionAdmin

When the client runs, I get the same error you were getting:

......
Caused by: java.io.IOException: Illegal Protocol https for HTTP URLConnection Factory.
at org.apache.cxf.transport.http.HttpURLConnectionFactoryImpl.createConnection(HttpURLConnectionFactoryImpl.java:44)
at org.apache.cxf.transport.http.HTTPConduit.prepare(HTTPConduit.java:468)
at org.apache.cxf.interceptor.MessageSenderInterceptor.handleMessage(MessageSenderInterceptor.java:46)
... 7 more

Any idea what I am missing?

Per Olesen said...

Hi Douglas,

I have not tried that exact config, but I might have a suggestion. You write that you are configuring a conduit on the client with this name:

"{https://gov.nwcg.isuite.webservice}ConnectionAdmin.http-conduit"

But, you also write that the server, after being deployed on ssl, still have this conduit name:

"{http://gov.nwcg.isuite.webservice}ConnectionAdmin"

Which I think is correct. As far as I understand, the conduit name is put together like this:

"{namespace-of-service}ServiceName"

And the namespace should not change, just because the access protocol changes. It just happens to be, that the namespace defaults to being a URL, which can be confusing.

What happens, when you change the conduit name in you spring config to:

"{http://gov.nwcg.isuite.webservice}ConnectionAdmin"

and access the SSL service?

douglas said...

Thanks for your quick response

I have tried various combinations of the http-conduit but all have failed.

<http:conduit name="{http://gov.nwcg.isuite.webservice}ConnectionAdmin.http-conduit">
</http:conduit>

<http:conduit name="{http://gov.nwcg.isuite.webservice}ConnectionAdmin.http-conduit">
<http:tlsClientParameters>
</http:tlsClientParameters>
</http:conduit>


<http:conduit name="{http://gov.nwcg.isuite.webservice}ConnectionAdmin.http-conduit">
<http:tlsClientParameters>secureSocketProtocol="SSL"
</http:tlsClientParameters>
</http:conduit>


<http:conduit name="{http://gov.nwcg.isuite.webservice}ConnectionAdmin.http-conduit">
<http:tlsClientParameters>secureSocketProtocol="ssl"
</http:tlsClientParameters>
</http:conduit>


I know that the file is being read because if I make some big error (such as an invalid xml ) I get a different error

I am not sure what to try next.


any ideas?

Anonymous said...

Hi
I have the same problem and still don't know what I'm doing wrong but
as temporary solution you can try (it works for me):

YourService ss = new YourService(wsdlURL, SERVICE_NAME);
YourPort port = ss.getYourPort();

Client c = ClientProxy.getClient(port);

HTTPConduit conduit = (HTTPConduit) c.getConduit();


TLSClientParameters tlsParams = new TLSClientParameters();

conduit.setTlsClientParameters(tlsParams);

Filippo said...

Thanks for this post, we are not alone!


Here the workaround I used:
69 Client client = ClientProxy.getClient(port);
70 HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
71 HTTPClientPolicy httpClientPolicy = httpConduit.getClient();

[...]

86 if("https".equalsIgnoreCase(SERVICE_URL.getProtocol())) {
87 TLSClientParameters tlsParams = new TLSClientParameters();
88 tlsParams.setSecureSocketProtocol("SSL"); // TODO do we need it?
89 httpConduit.setTlsClientParameters(tlsParams);
90 }


Filippo

Anonymous said...

I also was unable to get http:conduit to work.
I'm using a ClientProxyFactoryBean with the simple service. Here's the code that worked:


ClientProxyFactoryBean factory = new ClientProxyFactoryBean();
factory.setServiceClass(WidgetServiceRead.class);
factory.setAddress(url);
m_svc = (WidgetServiceRead) factory.create();
if (url.toLowerCase().startsWith("https:")) {
Client client = ClientProxy.getClient(m_svc);
HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
TLSClientParameters tlsParams = new TLSClientParameters();
tlsParams.setSecureSocketProtocol("SSL");
httpConduit.setTlsClientParameters(tlsParams);
}

Patrick Crocker said...

I too have struggled to get the Spring configured client (using JaxWsProxyFactoryBean) working with the http:conduit SSL configuration.

While this is not ideal, it seems to work:

<http:conduit name="*.http-conduit">
...
</http>

Sammy said...

I love these posts when people help each other out with code, on many an evening I wish I had help with some of the programs I have written. keep up the good work.

Anonymous said...

I've been struggling for many hours with this http:conduit problem and I've found a solution.

For me CXF expected something which event wasn't in wsdl file. I had to debug CXF code to find it and I'd recommend doing the same. Just put the breakpoint in ConfigurerImpl.configureWithWildCard() method and check value of first param. This is the value you should enter as http:conduit id.

Hope that helps,
Pawel

Anonymous said...

Pawel your a star,
Your approach solved my problem !

Cheers,
Paul

Anonymous said...

Thanks guys, this is a great post.

My scenario is to be able to connect to the https service regardless of the actual server certificate. Basically I want to "trust" the server, no matter what. So I choose to set a dummy TrustManager into the HTTPConduit. Here's the code to do it:

private void configHttpConduit(Object service) {
Client clientProxy = ClientProxy.getClient(service);

HTTPConduit conduit = (HTTPConduit) clientProxy.getConduit();
String targetAddr = conduit.getTarget().getAddress().getValue();
if (targetAddr.toLowerCase().startsWith("https:")) {
TrustManager[] simpleTrustManager = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
} };
TLSClientParameters tlsParams = new TLSClientParameters();
tlsParams.setTrustManagers(simpleTrustManager);
tlsParams.setDisableCNCheck(true);
conduit.setTlsClientParameters(tlsParams);
}
}

Cheers,

GZ

Anonymous said...

Thanks guys, this is a great post.

My scenario is to be able to connect to the https service regardless of the actual server certificate. Basically I want to "trust" the server, no matter what. So I choose to set a dummy TrustManager into the HTTPConduit. Here's the code to do it:

private void configHttpConduit(Object service) {
Client clientProxy = ClientProxy.getClient(service);

HTTPConduit conduit = (HTTPConduit) clientProxy.getConduit();
String targetAddr = conduit.getTarget().getAddress().getValue();
if (targetAddr.toLowerCase().startsWith("https:")) {
TrustManager[] simpleTrustManager = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
} };
TLSClientParameters tlsParams = new TLSClientParameters();
tlsParams.setTrustManagers(simpleTrustManager);
tlsParams.setDisableCNCheck(true);
conduit.setTlsClientParameters(tlsParams);
}
}

Cheers,

GZ

Anonymous said...

Hello,
This is really a good post.
My Scenario is: I am a client to consume web service (PERL webservice) via HTTPS. I just wanted to trust the server certificate and post the request and get the response. Tried the Spring - CXF configuration and it didn't worked. So I went thru the API way.

Here is the code, that solved my problem. Also, pls note the additional code to overcome the "Server returned HTTP response code: 411 for URL issue" for the request length issue:

Client clientProxy = ClientProxy.getClient(service);

HTTPConduit conduit = (HTTPConduit) clientProxy.getConduit();
HTTPClientPolicy httpClientPolicy = conduit.getClient();
httpClientPolicy.setAllowChunking(false);

String targetAddr = conduit.getTarget().getAddress().getValue();
if (targetAddr.toLowerCase().startsWith("https:")) {
TrustManager[] simpleTrustManager = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
} };
TLSClientParameters tlsParams = new TLSClientParameters();
tlsParams.setTrustManagers(simpleTrustManager);
// tlsParams.setDisableCNCheck(true);
conduit.setTlsClientParameters(tlsParams);
}


adoptCsrSyncResponseType = adoptVitPortType
.adoptCsrSync((AdoptCsrSyncRequestType) request);