I am invoking HTTPS SOAP web service through java code. I have already imported self-signed certificate in jre cacerts keystore. Now I am getting :
com.sun.xml.internal.ws.com.client.ClientTransportException: HTTP transport error: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names present
The hostname of the service URL is not matching that of the CN provided in cert. I read about a workaround of defining a custom Hostname verifier here. But i cant able to make where I should implement the workaround in my code.
public SOAPMessage invokeWS(WSBean bean) throws Exception {
SOAPMessage response=null;
try{
/** Create a service and add at least one port to it. **/
String targetNameSpace = bean.getTargetNameSpace();
String endpointUrl = bean.getEndpointUrl();
QName serviceName = new QName(targetNameSpace, bean.getServiceName());
QName portName = new QName(targetNameSpace, bean.getPortName());
String SOAPAction = bean.getSOAPAction();
HashMap<String, String> map = bean.getParameters();
Service service = Service.create(serviceName);
service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, endpointUrl);
/** Create a Dispatch instance from a service. **/
Dispatch dispatch = service.createDispatch(portName, SOAPMessage.class,
Service.Mode.MESSAGE);
// The soapActionUri is set here. otherwise we get a error on .net based
// services.
dispatch.getRequestContext().put(Dispatch.SOAPACTION_USE_PROPERTY,
new Boolean(true));
dispatch.getRequestContext().put(Dispatch.SOAPACTION_URI_PROPERTY,
SOAPAction);
/** Create SOAPMessage request. **/
// compose a request message
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage message = messageFactory.createMessage();
// Create objects for the message parts
SOAPPart soapPart = message.getSOAPPart();
SOAPEnvelope envelope = soapPart.getEnvelope();
SOAPBody body = envelope.getBody();
SOAPElement bodyElement = body.addChildElement(bean.getInputMethod(),
bean.getPrefix(), bean.getTargetNameSpace());
...more code to form soap body goes here
// Print request
message.writeTo(System.out);
// Save the message
message.saveChanges();
response = (SOAPMessage)dispatch.invoke(message);
}
catch (Exception e) {
log.error("Error in invokeSiebelWS :"+e);
}
return response;
}
Please ignore WSBean parameter as the namespaces and other wsdl attributes are coming from this bean. And if this exception can solved with some different workarounds, pls do suggest.
Thanks,Bruno for giving me heads up on Common Name and Subject Alternative Name. As we figured out certificate was generated with CN with DNS name of network and asked for regeneration of new certificate with Subject Alternative Name entry i.e. san=ip:10.0.0.1. which is the actual solution.
But, we managed to find out a workaround with which we can able to run on development phase. Just add a static block in the class from which we are making ssl connection.
static {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier()
{
public boolean verify(String hostname, SSLSession session)
{
// ip address of the service URL(like.23.28.244.244)
if (hostname.equals("23.28.244.244"))
return true;
return false;
}
});
}
If you happen to be using Java 8, there is a much slicker way of achieving the same result:
static {
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> hostname.equals("127.0.0.1"));
}
Unlike some browsers, Java follows the HTTPS specification strictly when it comes to the server identity verification (RFC 2818, Section 3.1) and IP addresses.
When using a host name, it's possible to fall back to the Common Name in the Subject DN of the server certificate, instead of using the Subject Alternative Name.
When using an IP address, there must be a Subject Alternative Name entry (of type IP address, not DNS name) in the certificate.
You'll find more details about the specification and how to generate such a certificate in this answer.
Related
I need to consume a SOAP API which requests all the messages to be signed policy "SigOnly".
I've generated the client code from the WSDL using wsimport and I was expecting it to generate the code for signing as well but it's not.
MyWebservice service = new MyWebservice();
MyWebservicePortType port = service.getMyWebserviceSOAPportHttp();
// Build request
ObjectFactory objectFactory = new ObjectFactory();
MyRequest request = objectFactory.createMyRequest();
// Call endpoint
port.myRemoteMethod(request);
Is there an extra switch for the wsimport tool to generate code for signing or am I missing some steps in the client's code?
WS-Security is an extension to soap, so I don't think wsimport could handle it in code generation.
Anyway you can write your handler to add ws-security headers in your soap requests.
You can add a handler to your port in this way :
BindingProvider bp = (BindingProvider) port;
List<Handler> handlerChain = new ArrayList<Handler>();
handlerChain.add( new MyHandler() );
Binding binding = bp.getBinding();
binding.setHandlerChain(handlerChain);
MyHandler class must implement SOAPHandler.
This is an example of an implementation of handleMessage method of MyHandler, adding a header
#Override
public boolean handleMessage(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
SOAPHeader header = envelope.addHeader();
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("MyUser");
}
}
I am trying to consume an authenticated webservice using SAAJ. This is the code I have so far:
import java.io.ByteArrayOutputStream;
import javax.xml.soap.*;
import biz.source_code.base64Coder.*;
public class Client {
private static String endpoint = "https://example.com/xxx.php",
username = "xxx", password = "xxx";
private static SOAPMessage getRequest() throws Exception{
MessageFactory factory = MessageFactory.newInstance();
SOAPMessage message = factory.createMessage();
//set authorization as a HTTP header
String authorization = Base64Coder.encodeString(username + ":" + password);
MimeHeaders hd = message.getMimeHeaders();
hd.addHeader("Authorization", "Basic " + authorization);
//Call getReportList operation
return message;
}
public static void main(String[] args) throws Exception {
SOAPConnectionFactory connFactory = SOAPConnectionFactory.newInstance();
SOAPConnection connection = connFactory.createConnection();
// create request message and give it content
SOAPMessage request = Client.getRequest();
// call the API endpoint with the request
SOAPMessage response = connection.call(request, endpoint);
ByteArrayOutputStream out = new ByteArrayOutputStream();
response.writeTo(out);
String strMsg = new String(out.toByteArray());
System.out.println(strMsg);
}
}
When I run this, it prints strMsg (response from the web service) as follows:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>WSDL</faultcode><faultstring>SOAP-ERROR: Parsing WSDL: Couldn't load from '/www/example.wsdl' : failed to load external entity "/www/example.wsdl"
</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>
I am guessing I have authenticated myself but there was a problem with the web service not mine. But, I am not entirely sure. This error message doesn't seem that common.
Does this mean the authentication I provided was incorrect or insufficient? Or do I have to provide the SSL certificate because the web service uses SSL? If yes, Is there a tutorial on how to use SSL certificates with SAAJ?
The problem was that I was using "https://example.com/xxx.php" as the endpoint not "https://example.com/xxx.php?wsdl". That's why it couldn't load any wsdl file.
I configure HttpsUrlConnection like this:
HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory);
HttpsURLConnection.setDefaultHostnameVerifier(new DummyHostnameVerifier());
DummyHostnameVerifier:
public class DummyHostnameVerifier implements HostnameVerifier {
#Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}
Of course, it's only part of configuration. But the problem is that verify method in DummyHostnameVerifier isn't invoked.
When I test my application on local machine, glassfish 3 server, verify invoked and I'm not recieving any exceptions.
But when I test it on remote environment, verify isn't invoked, and I recieve this:
java.io.IOException: The https URL hostname does not match the Common Name (CN) on the server certificate. To disable this check (NOT recommended for production) set the CXF client TLS configuration property "disableCNCheck" to true.
On remote env app runs on jboss 5.
Maybe this depends on some jboss config? I can't understand, where default hostname verifier changed after setting my verifier.
I think if you want to by pass the certificateValidation you would need to create Trustmanager which will not go for certificate validation
HttpsURLConnection.setDefaultHostnameVerifier(new DummyHostnameVerifier());
// Create a TrustManager which wont validate certificate chains start
javax.net.ssl.TrustManager[] trustAllCertificates = new javax.net.ssl.TrustManager[1];
javax.net.ssl.TrustManager tm = new miTM();
trustAllCertificates[0] = tm;
javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("SSL");
sc.init(null, trustAllCertificates, null);
// Create a TrustManager which wont validate certificate chains end
HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory);
Could you please try with above code and let me know if you get the resolution ?
The problem was in following: somehow there wasn't action name in message to server.
I configured connection like this:
HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory);
HttpsURLConnection.setDefaultHostnameVerifier(new DummyHostnameVerifier());
URL url = null;
try {
url = new URL(endpoint + "/wsdl");
} catch (MalformedURLException e) {
LOG.error(e.getMessage());
}
javax.xml.ws.Service s = MyService.create(url, new QName(MyService.NAMESPACE, MyService.SERVICE));
ServiceSoap port = s.getPort(ServiceSoap.class);
Map<String, Object> reqCtx = ((BindingProvider)port).getRequestContext();
reqCtx.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint);
reqCtx.put(BindingProvider.SOAPACTION_USE_PROPERTY, Boolean.TRUE);
reqCtx.put(BindingProvider.SOAPACTION_URI_PROPERTY, actionName);
Client client = ClientProxy.getClient(port);
HTTPConduit http = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnection(ConnectionType.CLOSE);
http.setClient(httpClientPolicy);
TLSClientParameters tls = new TLSClientParameters();
tls.setSSLSocketFactory(sslFactory);
tls.setDisableCNCheck(true);
http.setTlsClientParameters(tls);
So, port configured and everything began to work.
I'm a bit confused about how to make a request to a webservice via java.
For now the only thing that I've understand is that webservices uses xml structured messages, but still I didn't quite understood how to structure my request.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<getProductDetails xmlns="http://magazzino.example.com/ws">
<productId>827635</productId>
</getProductDetails>
</soap:Body>
</soap:Envelope>
Basically I've to send 2 parameters to the web service and in return I expect two other parameters.
I guess there are some jars that can do most of the job, but I didn't find any online.
Can someone please explain me the basis?
A SOAP request is an XML file consisting of the parameters you are sending to the server.
The SOAP response is equally an XML file, but now with everything the service wants to give you.
Basically the WSDL is a XML file that explains the structure of those two XML.
To implement simple SOAP clients in Java, you can use the SAAJ framework (it is shipped with JSE 1.6 and above):
SOAP with Attachments API for Java (SAAJ) is mainly used for dealing directly with SOAP Request/Response messages which happens behind the scenes in any Web Service API. It allows the developers to directly send and receive soap messages instead of using JAX-WS.
See below a working example (run it!) of a SOAP web service call using SAAJ. It calls this web service.
import javax.xml.soap.*;
public class SOAPClientSAAJ {
// SAAJ - SOAP Client Testing
public static void main(String args[]) {
/*
The example below requests from the Web Service at:
http://www.webservicex.net/uszip.asmx?op=GetInfoByCity
To call other WS, change the parameters below, which are:
- the SOAP Endpoint URL (that is, where the service is responding from)
- the SOAP Action
Also change the contents of the method createSoapEnvelope() in this class. It constructs
the inner part of the SOAP envelope that is actually sent.
*/
String soapEndpointUrl = "http://www.webservicex.net/uszip.asmx";
String soapAction = "http://www.webserviceX.NET/GetInfoByCity";
callSoapWebService(soapEndpointUrl, soapAction);
}
private static void createSoapEnvelope(SOAPMessage soapMessage) throws SOAPException {
SOAPPart soapPart = soapMessage.getSOAPPart();
String myNamespace = "myNamespace";
String myNamespaceURI = "http://www.webserviceX.NET";
// SOAP Envelope
SOAPEnvelope envelope = soapPart.getEnvelope();
envelope.addNamespaceDeclaration(myNamespace, myNamespaceURI);
/*
Constructed SOAP Request Message:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:myNamespace="http://www.webserviceX.NET">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<myNamespace:GetInfoByCity>
<myNamespace:USCity>New York</myNamespace:USCity>
</myNamespace:GetInfoByCity>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
*/
// SOAP Body
SOAPBody soapBody = envelope.getBody();
SOAPElement soapBodyElem = soapBody.addChildElement("GetInfoByCity", myNamespace);
SOAPElement soapBodyElem1 = soapBodyElem.addChildElement("USCity", myNamespace);
soapBodyElem1.addTextNode("New York");
}
private static void callSoapWebService(String soapEndpointUrl, String soapAction) {
try {
// Create SOAP Connection
SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
SOAPConnection soapConnection = soapConnectionFactory.createConnection();
// Send SOAP Message to SOAP Server
SOAPMessage soapResponse = soapConnection.call(createSOAPRequest(soapAction), soapEndpointUrl);
// Print the SOAP Response
System.out.println("Response SOAP Message:");
soapResponse.writeTo(System.out);
System.out.println();
soapConnection.close();
} catch (Exception e) {
System.err.println("\nError occurred while sending SOAP Request to Server!\nMake sure you have the correct endpoint URL and SOAPAction!\n");
e.printStackTrace();
}
}
private static SOAPMessage createSOAPRequest(String soapAction) throws Exception {
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage soapMessage = messageFactory.createMessage();
createSoapEnvelope(soapMessage);
MimeHeaders headers = soapMessage.getMimeHeaders();
headers.addHeader("SOAPAction", soapAction);
soapMessage.saveChanges();
/* Print the request message, just for debugging purposes */
System.out.println("Request SOAP Message:");
soapMessage.writeTo(System.out);
System.out.println("\n");
return soapMessage;
}
}
When the WSDL is available, it is just two steps you need to follow to invoke that web service.
Step 1: Generate the client side source from a WSDL2Java tool
Step 2: Invoke the operation using:
YourService service = new YourServiceLocator();
Stub stub = service.getYourStub();
stub.operation();
If you look further, you will notice that the Stub class is used to invoke the service deployed at the remote location as a web service. When invoking that, your client actually generates the SOAP request and communicates. Similarly the web service sends the response as a SOAP. With the help of a tool like Wireshark, you can view the SOAP messages exchanged.
However since you have requested more explanation on the basics, I recommend you to refer here and write a web service with it's client to learn it further.
I have come across other similar question here. Both of above answers are perfect, but here trying to add additional information for someone looking for SOAP1.1, and not SOAP1.2.
Just change one line code provided by #acdcjunior, use SOAPMessageFactory1_1Impl implementation, it will change namespace to xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/", which is SOAP1.1 implementation.
Change callSoapWebService method first line to following.
SOAPMessage soapMessage = SOAPMessageFactory1_1Impl.newInstance().createMessage();
I hope it will be helpful to others.
if you have WSDL, You can create a new soap request in SoapUI with that WSDL file.
It would automatically generate the Structure/XML for input request.
Here is some simple version of Java code you can use to call Soap service if you have the input request xml from SoapUI:
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class SimpleSoapClient {
public static void main(String args[]) throws IOException {
String address="Hyderabad";
/* place your xml request from soap ui below with necessary changes in parameters*/
String xml="<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ws=\"http://www.YourUrlAsPerWsdl.com/\">\r\n" +
" <soapenv:Header/>\r\n" +
" <soapenv:Body>\r\n" +
" <ws:callRest>\r\n" +
" <name>"+"Hello"+"</name>\r\n" +
" <address>"+address+"</address>\r\n" +
" </ws:callRest>\r\n" +
" </soapenv:Body>\r\n" +
"</soapenv:Envelope>";
String responseF=callSoapService(xml);
System.out.println(responseF);
}
}
static String callSoapService(String soapRequest) {
try {
String url = "https://gogle.com/service/hello"; // replace your URL here
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// change these values as per soapui request on top left of request, click on RAW, you will find all the headers
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type","text/xml; charset=utf-8");
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.writeBytes(soapRequest);
wr.flush();
wr.close();
String responseStatus = con.getResponseMessage();
System.out.println(responseStatus);
BufferedReader in = new BufferedReader(new InputStreamReader(
con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// You can play with response which is available as string now:
String finalvalue= response.toString();
// or you can parse/substring the required tag from response as below based your response code
finalvalue= finalvalue.substring(finalvalue.indexOf("<response>")+10,finalvalue.indexOf("</response>")); */
return finalvalue;
}
catch (Exception e) {
return e.getMessage();
}
}
}
I'm trying to connect to https url - https://rtpubcommission.api.cj.com/wsdl/version2/realtimeCommissionServiceV2.wsdl
But getting the errors (listed only chain of errors, without full stacktrace):
com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl: java.security.PrivilegedActionException: com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl: Message send failed
Caused by: java.security.PrivilegedActionException: com.sun.xml.internal.messaging.saaj.SOAPExceptionImpl: Message send failed
Caused by: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: algorithm check failed: MD2withRSA is disabled
Caused by: java.security.cert.CertPathValidatorException: algorithm check failed: MD2withRSA is disabled
Here is my code:
private void processCommonRequest(String url, HashMap<String, String> params) throws Exception {
URL endpoint = new URL(url);
//MessageDigest md = MessageDigest.getInstance("MD5");
//System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
//Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
// Create SOAP connection
SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
SOAPConnection connection = scf.createConnection();
// Create a message from the message factory.
MessageFactory mf = MessageFactory.newInstance();
SOAPMessage msg = mf.createMessage();
// Get the SOAP Part from the message
SOAPPart soapPart = msg.getSOAPPart();
// Get the SOAP Envelope from the SOAP Part
SOAPEnvelope envelope = soapPart.getEnvelope();
envelope.addNamespaceDeclaration("SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/");
envelope.addNamespaceDeclaration("xsd", "http://www.w3.org/1999/XMLSchema");
envelope.addNamespaceDeclaration("xsi", "http://www.w3.org/1999/XMLSchema-instance-instance");
envelope.addNamespaceDeclaration("tns", "http://api.cj.com");
envelope.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");
// Remove empty header from the Envelope
envelope.getHeader().detachNode();
// Create a soap body from the Envelope.
SOAPBody body = envelope.getBody();
body.addNamespaceDeclaration("soap-env", "http://schemas.xmlsoap.org/soap/encoding/");
// SOAPBodyElement item = body.addBodyElement(envelope.createName("GeScore") );
SOAPBodyElement item = body.addBodyElement(envelope.createName(
"GeScore", "soap-env", "http://schemas.xmlsoap.org/soap/encoding/"));
for (String keyMap : params.keySet()) {
addItem(envelope, keyMap, params.get(keyMap), item);
}
System.out.println("\nContent of the message: \n"); // FIXME
msg.writeTo(System.out);
// Send the SOAP message and get reply
System.err.println("\nSending message to URL: " + endpoint); // XXX
SOAPMessage reply = connection.call(msg, endpoint);
// ... nevermind what later ... .call function throws error ....
connection.close();
}
The MD2 is not secure any more, so Sun disabled its use in certification path validation. In the Release Notes of security update 6u17, http://java.sun.com/javase/6/webnotes/6u17.html, it is said that the vulnerability is addressed, "6861062: Disable MD2 in certificate chain validation".
You could either run your code on older version of JRE (before 6u17) or try latest version instead, as there are some reports that it might work (https://forums.oracle.com/forums/thread.jspa?threadID=1237743).
Alternatively you could try to use custom TrustManager, although it's not recommended if security is important for you.