Setup Spring RestTemplate to accept Self Signed Cert

This is strictly for testing only but may be useful if you need to perform integration tests. For example, the system you develop needs to access another internal or 3rd party test server via https where the server’s certificate is not signed.

PKIX path building failed

By default, if you try to access a server via https with a self signed certificate, for example with the following codes

RestTemplate template = new TestRestTemplate();
template.getForObject(https://<some server>/, String.class);

you will get the following exception:

org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://<some server>":
sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target;

HttpClient

To fix the above, update the RestTemplate with a custom HttpClient that accepts self-signed certificate:

      SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build());

      HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();

      RestTemplate template = new TestRestTemplate();
      ((HttpComponentsClientHttpRequestFactory) template.getRequestFactory()).setHttpClient(httpClient);

and add the following dependency if needed

      <dependency>
           <groupId>org.apache.httpcomponents</groupId>
           <artifactId>httpclient</artifactId>
           <scope>test</scope>
      </dependency>

Again this should only be used for testing purpose only.

Advertisements

Unit test Spring RestTemplate webservice client

This blog demonstrates how to setup and test webservice client codes written in Spring RestTemplate.

Obtaining the jar

I am using Spring 3.1 here and have to include the jar file from the spring-mvc-test project into the classpath. The project has since been incorporated into Spring 3.2. See the Spring documentation for details. The codes here are tested against Spring 3.1 and spring-mvc-test. The APIs may be somewhat different in Spring 3.2.

Restful webservice client

My webservice client communicates with the server via XML so I setup RestTemplate with JaxB marshaller and unmarshaller as follows:

    <bean id="exampleRestClient" >
        <property name="template" ref="exampleRestTemplate"></property>
        <property name="baseUrl" value="<server url>"></property>
    </bean>

    <!-- RESTful template and related beans -->
    <bean id="exampleRestTemplate">
        <property name="messageConverters">
            <list>
                   <ref bean="marshallingHttpMessageConverter"/>
            </list>
        </property>
    </bean>

    <bean id="marshallingHttpMessageConverter">
      <property name="marshaller" ref="jaxb2Marshaller" />
      <property name="unmarshaller" ref="jaxb2Marshaller" />
    </bean>

    <bean id="jaxb2Marshaller">
        <property name="contextPath">
                <value>com.blog.resttemplatetest.jaxb</value>
        </property>
        <property name="schema" value="classpath:myschema.xsd"/>
    </bean>

The webservice client implements the normal CRUD functions by delegating to its RestTemplate bean. I will focus on the put function here and demonstrate how to write unit test for it in next section.

@Component(“exampleRestClient”)
public class ExampleRestClient {

private RestTemplate template;
private String baseUrl; // base url

public ExampleResponse add(final ExampleData resource, final ExampleResponse response) {
return put(baseUrl, resource, response);
}

private ExampleResponse put(final String url, final ExampleData resource, final ExampleResponse response) {
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
final HttpEntity<ExampleData> requestEntity = new HttpEntity<ExampleData>(resource, headers);
final ResponseEntity<ExampleResponse> reponseEntity = template.exchange(url, HttpMethod.PUT, requestEntity,
response.getClass());
return reponseEntity.getBody();
}

// getters and setters

Note that instead of using the put() method in RestTemplate, I use its exchange() method in order to get the return response from the web service, as the RestTemplate#put method does not return anything. Both input and output classes ExampleData and ExampleResponse are JAXB objects generated via xjc from the example XML schema myschema.xsd.

Writing unit test for webservice client

spring-mvc-test provides a mock server class MockRestServiceServer to support client testing. For example, to test the put add method in our ExampleRestClient:

...
import static org.springframework.test.web.client.RequestMatchers.method;
import static org.springframework.test.web.client.RequestMatchers.requestTo;
import static org.springframework.test.web.client.ResponseCreators.withSuccess;
...

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:example-test.xml" })
public class StorefrontRestClientTest {
    private MockRestServiceServer mockServer;

    @Autowired
    private ExampleRestClient service;

    @Before
    public void setUp() {
        mockServer = MockRestServiceServer.createServer(service.getTemplate()); // (1)
    }

    private String loadXmlFile(final String filename) throws IOException {
        // load xml file into String ...
    }

    @Test
    public void testAddReturnCorrectResponse() throws Exception {

        final ObjectFactory factory = new ObjectFactory();
        final String responseXml = loadXmlFile("/test/expectedResponse.xml");

        mockServer.expect(requestTo(service.getBaseUrl()))
                  .andExpect(method(HttpMethod.PUT))
                  .andRespond(withSuccess(responseXml, MediaType.APPLICATION_XML)); // (2)

        final ExampleData exampleData= factory.createExampleData();
        final ExampleResponse response = (ExampleResponse) service.add(exampleData, new ExampleResponse());
        assertEquals(200, response.getStatus()); // (3)
        assertEquals(12345, response.getID()); // (3)
        mockServer.verify(); // (4)
    }
    ...
// file: expectedResponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<tns:exampleResponse xmlns:tns="http://" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.example.com/example example.xsd ">
  <tns:status>200</tns:status>
  <tns:ID>12345</tns:ID>
</tns:exampleResponse>

Note:

  1. Instantiate mock server with the RestTemplate to be tested
  2. This sets the expectations on the request and mocks the response to be returned, in this case the Xml string stored in file expectedResponse.xml (above).  Note the static imports required at the top of the codes for requestTo, addExpect and andResponse.
  3. Junit assertions as would normally included.
  4. Call verify() method to ensure that expectations are being met.

Final words

Client side unit testing is useful and important. It allows you to verify that correct requests are generated by the client and responses received from server are handled properly, without the need of a running server. It is handy when the server and client are being developed concurrently.

Using Spring RestTemplate to consume restful webservice

Introduction

I recently have to write a client to consume a restful webservice using Spring RestTemplate class. The task turns out to be non trivial as there are a few gotchas needed to be handled because of the way the webservice is implemented. I managed to put together a solution after searching through some useful articles online. I summarise what I have done in this article.

Problem 1: SSL

The webservice I am consuming is implemented in another web application but has to be accessed via https. As a result, calling the getObject() method in RestTemplate with the url will return the following exception:

org.springframework.web.client.ResourceAccessException: I/O error: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:199)
… 32 more

I decide to bypass the certificate validation by adding the following codes:

    public static void trustSelfSignedSSL()
{
try
{
final SSLContext ctx = SSLContext.getInstance(“TLS”);
final X509TrustManager tm = new X509TrustManager()
{

public void checkClientTrusted(final X509Certificate[] xcs, final String string) throws CertificateException
{
// do nothing
}

public void checkServerTrusted(final X509Certificate[] xcs, final String string) throws CertificateException
{
// do nothing
}

public X509Certificate[] getAcceptedIssuers()
{
return null;
}
};
ctx.init(null, new TrustManager[]
{ tm }, null);
SSLContext.setDefault(ctx);
}
catch (final Exception ex)
{
ex.printStackTrace();
}
}

Note the method is static and needs to be called once before calling the webservice. Now calling RestTemplate.getObject will return something about hostname wrong:

org.springframework.web.client.ResourceAccessException: I/O error: HTTPS hostname wrong:  should be <some url>; nested exception is java.io.IOException: HTTPS hostname wrong:  should be <some url>
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:199)

… 32 more

The above error can be removed by providing a custom http client factory by extending Spring class SimpleClientHttpRequestFactory:

public class MySimpleClientHttpRequestFactory extends SimpleClientHttpRequestFactory
{

private final HostnameVerifier verifier;

public MySimpleClientHttpRequestFactory(final HostnameVerifier verifier)
{
this.verifier = verifier;
}

@Override
protected void prepareConnection(final HttpURLConnection connection, final String httpMethod) throws IOException
{
if (connection instanceof HttpsURLConnection)
{
((HttpsURLConnection) connection).setHostnameVerifier(verifier);
}
super.prepareConnection(connection, httpMethod);
}
}

Note we also need to inject a dummy hostname verifier for the above class to bypass the hostname verification:

public class NullHostnameVerifier implements HostnameVerifier
{
@Override
public boolean verify(final String hostname, final SSLSession session)
{
return true;
}

}

All then left to do is the inject the client http request factory to the resttemplate:

RestTemplate template = new RestTemplate();

final MySimpleClientHttpRequestFactory factory = new MySimpleClientHttpRequestFactory(verifier);
template.setRequestFactory(factory);

Problem 2 : List Generic / Type Erasure

My second problem with accessing the webservice is that it returns an array of JSON object in the form

[{“no”:”00028″,”l”:”201″,”t”:0,”i”:10,”a”:20},…,{“no”:”00028″,”l”:”201″,”t”:5,”i”:15,”a”:25}]

Because of Java type erasure, calling RestTemplate.getObject(url, List.class) will return a list of LinkedHashMap objects. To marshall the returned json objects into domain objects, the default json message converter MappingJacksonHttpMessageConverter has to be extended as follows:

public class MyJsonMessageConverter extends MappingJacksonHttpMessageConverter
{

@Override
protected JavaType getJavaType(final Class<?> clazz)
{
if (List.class.isAssignableFrom(clazz))
{
final TypeFactory typeFactory = getObjectMapper().getTypeFactory();
return typeFactory.constructCollectionType(ArrayList.class, StockInventory.class);
}
else
{
return super.getJavaType(clazz);
}
}

Note the use of TypeFactory.constructCollectionType method to map list type into my domain object type StockInventory.

Problem 3: Custom deserialiser

The last problem I need to solve is to implement a custom deserialiser in my message converter to handle the coded json object. I do this by extending the getObjectMapper method:

 @Override
public void setObjectMapper(final ObjectMapper objectMapper)
{
// add custom json serialiser for ItemInventory response
final SimpleModule module = new SimpleModule(“StockInventoryModule”, new Version(1, 1, 1, null));
module.addDeserializer(StockInventory.class, new JsonDeserializer<StockInventory>()
{

@Override
public StockInventory deserialize(final JsonParser jp, final DeserializationContext cxt) throws IOException,
JsonProcessingException
{
final StockInventory stockInventory = new StockInventory();
//    final DeserializerProvider provider = cxt.getDeserializerProvider();
while (jp.nextToken() != JsonToken.END_OBJECT)
{
final String fieldName = jp.getCurrentName();
jp.nextToken();
if (“no”.equals(fieldName))
{
stockInventory.setNo(jp.getText());
}
else if (“l”.equals(fieldName))
{
stockInventory.setLocation(jp.getText());
}
… // handle other fields
else
{
throw new IllegalStateException(“unrecognized field [” + fieldName + “]”);
}
}
return stockInventory;
}
});
objectMapper.registerModule(module);
super.setObjectMapper(objectMapper);
}

Now the only thing left to do is to use the customer converter in the RestTemplate:

  final List<HttpMessageConverter<?>> list = new ArrayList<HttpMessageConverter<?>>();
final MappingJacksonHttpMessageConverter converter = new MyJsonMessageConverter();
converter.setObjectMapper(new ObjectMapper());
list.add(converter);
template.setMessageConverters(list);
List<StockInventory> stocks = template.getForObject(url, List.class);

That’s it!

Resources

Thanks for the contributors of the following links where I got the codes from:

  1. SSL – http://stackoverflow.com/questions/1725863/ssl-handshake-issue-using-spring-resttemplate
  2. Customer client HTTP request factory codes from http://www.jroller.com/jurberg/entry/using_a_hostnameverifier_with_spring
  3. JSON list – http://stackoverflow.com/questions/7040279/jackson-json-java-generics-get-linkedhashmap
  4. JSON list – http://stackoverflow.com/questions/6173182/spring-json-convert-a-typed-collection-like-listmypojo