I am trying to connect to a secure external rest service using Jersey 1.X version.
I used the following code
public class MyRestClient
{
private static final String API_USER_NAME = "some value";
private static final String API_PASSWORD = "some value";
private static final String REST_URL = "https://<somevalue>";
public static void main(String[] args)
{
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
client.addFilter(new HTTPBasicAuthFilter(API_USER_NAME, API_PASSWORD));
WebResource webResource =
client.resource(UriBuilder.fromUri(REST_URL).build());
ClientResponse response = webResource.post(ClientResponse.class);
System.out.println(response);
}
}
But I keep hitting this exception..
com.sun.jersey.api.client.ClientHandlerException: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching 'somevalue' found
I checked out the API of this external rest service and it says it support Basic HTTP authentication but I dont know why I keep hitting this error.
Any thoughts?
Because the lack of security in Basic Auth by itself, it's usually done over SSL, as you can see in the https
schema in the URL. With SSL, there are certificates used. The SSL handshake consists of the server sending its certificate and the client checking its truststore to see if the certificate is trusted. The platform should have a list of Certificate authorities that it trusts. For instance if we try and access
WebTarget target = client.target("https://wikipedia.org");
this will work as the cert sent by wikipedia is signed by a trusted authority in the system. On the other hand, if the certificate from the server is not signed by one of those trused authorities, then the SSL handshake will fail.
If this is the case, then Client
needs to be configured to handle the SSL handshake, that's why you are getting the exception. You can see some good answers here on how to configure the Client
for working with https
UPDATE
The link you have provided is dead so I dont know what 'myTrustManager' and 'hostnameVerifier' is...can you share some info on how can I supply that?
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.client.urlconnection.HTTPSProperties;
import java.io.FileInputStream;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.junit.Test;
public class JUnitTest {
private static final String TRUSTSTORE_FILE = "<location-of-truststore";
private static final String TRUSTSTORE_PASSWORD = "trustStorePassword";
@Test
public void test() throws Exception {
KeyStore truststore = KeyStore.getInstance("JKS");
truststore.load(new FileInputStream(TRUSTSTORE_FILE),
TRUSTSTORE_PASSWORD.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(truststore);
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tmf.getTrustManagers(), null);
ClientConfig config = new DefaultClientConfig();
config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES,
new HTTPSProperties(null, sslContext));
Client client = Client.create(config);
final String httpsUrl = "https://...";
ClientResponse response = client.resource(httpsUrl).get(ClientResponse.class);
System.out.println(response.getStatus());
System.out.println(response.getEntity(String.class));
}
}
Basically you need to get the X.509 Certificate from the people who own the API (see link below for programmatic way). Then you import it into your trust store. This is how your client knows to trust the connection. If you trust that the server is who they say they are, then the connection can be encrypted.
Once you have the cert you can import it with the Java keytool. By doing this
keytool -import -alias serverCert -file <cert.file> -keystore <client_trust_file>
You will be asked for a password. Then asked if you trust the cert. Type 'yes', then you're done. This is the file (client_trust_file
) and password you typed, should be used in the above code.
- Programmaticly add a remote certificate to your truststore
- See more about Java Secure Socket Framework
Update 2
Instructions on creating a simple app that is over a secured SSL connection with Tomcat. The use the client code above to access it. I am going to be using Netbeans 8, but will try to include instructions for doing this in a general way also. I will also be using Tomcat 8 (the configuration may be a bit different fro Tomcat 7. You should consult the documentation for any differences). I will be using Maven, so hopefully you are comfortable using it.
Step 1:
Crate a new app. I will create a simple Jersey app from a Maven archetype. In Netbeans
File → New Project → Maven → Project from Archetype → Search "jersey-quickstart-webapp"; choose the one with groupId "org.glassfish.jersey.archetypes" → Next → Name the project "secured-rest-app" → Hopefully you can complete the rest. You should end up with a Maven app.
In any other IDE that supports Maven, just look for an archetype with the coordinates:
- groupId: org.glassfish.jersey.archetypes
- artifactId: jersey-quickstart-webapp
- version: 2.13
From command line: do
mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=secured-rest-app -Dpackage=secured.rest.app \
-DarchetypeVersion=2.13
Step 2:
We need to set up Basic Authentication in the app. This can be done in the web.xm l. Open up the web.xml of the project and add this below the </servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Secured Rest App</web-resource-name>
<url-pattern>/webapi/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>secured-rest-app.com</realm-name>
</login-config>
<security-role>
<role-name>user</role-name>
</security-role>
Step 3:
Now we just need to set up the realm in Tomcat. By default Tomcat use a realm names UserDatabaseRealm
. It basically just reads from an xml file. This is probably not the most desired way in production, but this is the easiest to work with for an example. For more information on realms, see Realm Configuration HOW-TO. For this particular realm, the file is already set up. We just need to add our user. Open the <tomcat-home>/conf/tomcat-users.xml
and enter the following user inside <tomcat-users>
<user password="secret" roles="user" username="peeskillet"/>
Step 4:
Now we can test it. If you already have Tomcat setup on Netbeans, all we need to do is Run
and select the server. This should automatically open up the browser to our index.jsp
. The file is not secured, as it doesn't fit within the <url-pattern>/webapi/*</url-pattern>
of out security constraint. Click the Jersey Resource link and you will see the Basic Auth login. Type in peeskillet
and secret
for the username and password, respectively. Now you can access the resource.
Step 5:
All of the above just set us up for Basic Authentication, but because all Basic Auth is, is just base64 encoding our username and password, it can be easily decoded, so we need to authenticate over a secure connection.
First thing we need to do is create a keystore for our server. What we will be going here is creating a self signed certificate, which should only be done in development. In production, you will want to get a cert from a trusted CA authority
cd
to <tomcat-home>/conf
and type the following (all on one line)
keytool -genkey -alias localhost -keyalg RSA -keysize 1024
-dname "CN=localhost"
-keypass supersecret
-keystore tomcat-keystore.jks
-storepass supersecret
You should now see a file tomcat-keystore.jks
in the conf
dir. Now we can export the certificate.
keytool -export -alias localhost -rfc -keystore ./tomcat-keystore.jks > ./tomcat.cert
You will be prompted for the password, type supersecret
. You should now see a tomcat.cert
file created in the conf
dir. Copy that file into the project root of your application you created above.
From the command line cd
to the project root, the location of the tomcat.cert
and type the following
keytool -import -alias tomcatCert -file ./tomcat.cert -keystore ./client-truststore.jks
You will be prompted for a password for the truststore. Use trustpass
. You will need to enter it twice. When done, it will prompt to trust the certificate, type yes
and enter. You should now see a client-truststore.jks
file in the project root. This is what we will use for our client app.
Now we just need to configure Tomcat to use our keystore for the connection. In the <tomcat-home>/conf/server.xml
, inside the the <Service>
element
. (Note Tomcat 7 may be a little different)
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150"
SSLEnabled="true"
scheme="https"
secure="true"
keystoreFile="absolute/path/to/tomcat-keystore.jks"
keystorePass="supersecret"
clientAuth="false"
sslProtocol="TLS" />
And finally, in our webapp, we should add the secure connection support by altering the <security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Secured Rest App</web-resource-name>
<url-pattern>/webapi/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
Now we are set to use our client code.
Step 6:
The code above, uses Jersey 1 client. We are using Jersey 2, so the code will be slightly different. Anywhere in the app, just create a class with a main
method to run our client. Here's what I use:
import java.io.FileInputStream;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
public class SimpleClientApp {
private static final String TRUSTSTORE_FILE = "client-truststore.jks";
private static final String TRUSTSTORE_PASSWORD = "trustpass";
private static final String APP_URL
= "https://localhost:8443/secured-rest-app/webapi/myresource";
public static void main(String[] args) throws Exception {
KeyStore truststore = KeyStore.getInstance("JKS");
truststore.load(new FileInputStream(TRUSTSTORE_FILE),
TRUSTSTORE_PASSWORD.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(truststore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
Client client = ClientBuilder.newBuilder()
.sslContext(sslContext).build();
client.register(HttpAuthenticationFeature.basic("peeskillet", "secret"));
Response response = client.target(APP_URL).request().get();
System.out.println(response.readEntity(String.class));
}
}
You should be able to run this and it should print out Got it!
. We're done!
Since this particular question was about Jersey 1, I'll just mention that you can easily create a Jersey 1 app. From the first step, just use the coordinates for the Maven archetype
- groupId: com.sun.jersey.archetypes
- artifactId: jersey-quickstart-webapp
- version: 1.18.1
In Netbeans, just follow the steps above, but select the com.sun.jersey.archetypes
version of the jersey-quickstart-webapp
.
With Jersey 1, you can use the code in the original answer, just add the Basic Auth filter, as the OP has done in the original post, setting the username and password and changing the url of course.
Please let me know if there are any mistakes in the post. I haven't gotten a chance to proof-read this yet :-)
Some Resource
- Tomcat 8 SSL/TLS Configuration HOW-TO
- Tomcat 8 Realm Configuration HOW-TO
- Tomcat 7 Realm Configuration HOW-TO
- Tomcat 7 SSL/TLS Configuration HOW-TO