Monday, February 11, 2008

HTTPS Connection in Java


Every time I had to create a java client to access a page using a https connection, I come across some issue and I keep googling for the answer. So this time I decided to document this so it will be useful for me the next time. There are few steps that we need to follow to make a https connection in java.

1) Create a trust store
To create a https client you need to provide the trust store with the certificate of the host you are connecting. The trust store is a file where a list of trusted certificates are stored. Thie file need to be created or the certificate of your host need to be manually added to the trust store. I believe there is a way to automatically extract this from the url and dynamically add a handler. (I say this because JMeter does not require you to do this step) This is an isssue when you are connecting to servers in your development environment or servers, whoose certificate is not signed by a valid signing authority.

So, to create the truststore, you need a certificate. You can create a certificate by pointing your browser to the host and use your browser to extract the certificate file. Here are the steps to extract the certificate using IE. Point your browser to the site. When the browser shows the padlock, doubleclick the padlock and bringup the popup window. Go to the details tab and select the options to copy the certificate to a file.
Now, use the keytool that is part of the jdk to create a store file.

<%JAVA_HOME%>\bin\keytool -import -trustcacerts -keystore testkeys -storepass passphrase -noprompt -file mycert.cer

In the above command, testkeys is the name of the truststore file to be created/edited, mycert.cer is the name of the certificate file created by the browser.

If you prefer to install the certificate using a java program you can follow the instructions
from Andreas Sterbenz's Blog.

2) Make truststore available to jvm
Once you have created the truststore file, your code to make normal URL connection should work even for the ssl connection. The only change you need to do is add a JVM argument to the command line. java -Djavax.net.ssl.trustStore="<%PATH_TO_TRUST_STORE%>\testkeys"

If you dont want to add the extra parameter you can update the default trust store found under /lib/security/cacerts file using the above procedure. I suggest that you backup the file before you do anything with the file.

3) Make URL connection as usual

Here is a sample snippet for making a url request.



URL url = new URL("https://someurl");
URLConnection con = url.openConnection();
con.setDoOutput(true);
con.connect();
OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream());
wr.write("Request data....");
wr.flush();
// Get the response
BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line;
while ((line = rd.readLine()) != null){
System.out.println(line);
}
wr.close();
rd.close();



4) Create host verifier if your host name does not match with certificate

If your certificate issuing name does not match with the hostname you are connecting to, you may run in to another error. Thie can be solved by adding your own hostverifier to your code. Adding the following snippet will accept any host. Use this with caution in a production environment and allow only known mappings.

HostnameVerifier hostnameVerifier = new HostnameVerifier()
{ public boolean verify(String urlHostName, SSLSession session) { System.out.println("Warning: URL Host: " + urlHostName + " v/s " + session.getPeerHost()); return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

5) Create authorization handler, if page is password protected

The next challenge you may run in to is if the site you are visiting is protected by username/password. You need to add an authenticator. The easiest way to do this is to add a default authenticator.
The following is an example of a simple authenticator:

static class MyAuthenticator extends Authenticator {
String username , password ;
MyAuthenticator(String user, String pwd ) {

super();
username = user ;
password = pwd;
}
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication (username, password.toCharArray());
}
}

Now to activate this authenticator, you need to add the following code to your source.

MyAuthenticator auth = new MyAuthenticator(user, passwd); Authenticator.setDefault( auth);

Hope fully these steps will get you going with making a url connection to a url that is ssl enabled and password protected.

6 comments:

jean said...

When you are running your app, and getting the following error, it is most likely you have not specified the -DtrustStore option to your java command line or the file specified has a problem

"trustAnchors parameter must be non-empty"

Unknown said...

Hi,

I'm having an issue with HttpsUrlConnection. I'm writing my request to the connection outputstream but I don't get anything in inputstream. It's empty.

So I tried with all the steps above but still i have the same issue.

Do you have any suggestions?

Thanks,
Darshana

jean said...

Darshana, Please post your code with the certificates you are using to connect to the url. I will be happy to take a look at it.

Regards
Jean

Benedict Devlin said...

Hi Jean
I want to create a HTTPS connection to
https://www.mycarphonewarehouse.com/portal/servlet/gben-portal-home-Home

and enter my email and password.
I've followed what you've said, but information I get back doesn't correspond with the login page I should obtain.
Any ideas?
Many thanks
Ben

jean said...

Ben, The approach will work for web pages that uses basic authentication. Typically you can identify those pages when you visit the page and you get the browser's popup window prompt for authentication.

The site you mentioned appears to be using forms based authentication. If you are trying to screen scrape such web pages, you will have to pass in the credentials to the page using the same parameter names used by the login form.

Benedict Devlin said...

Hi Jean.
Cheers for the initial post.
I pass the email and password with the same parameter names as the form. However it recognises bad credentials (returns HTML code with error) but for good credentials it accepts them but just returns information the same HTML code for the login page.

Any ideas? HTTP Request?

Ben