diff --git a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc index 087d19afe2..8e80571029 100644 --- a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc +++ b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc @@ -94,6 +94,49 @@ include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.j The use `@ClassRule` means that the server will shut down after all the methods in this class. +== Relaxed SSL Validation for Rest Template + +WireMock allows you to stub a "secure" server with an "https" URL protocol. If your application wants to +contact that stub server in an integration test, then it will find that the SSL certificates are not +valid (it's the usual problem with self-installed certificates). The best option is often to just +re-configure the client to use "http", but if that's not open to you then you can ask Spring to configure +an HTTP client that ignores SSL validation errors (just for tests). + +To make this work with minimum fuss you need to be using the Spring Boot `RestTemplateBuilder` in your app, +e.g. + +[source,java,indent=0] +---- + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } +---- + +This is because the builder is passed through callbacks to initalize it, so the SSL validation can be set up +in the client at that point. This will happen automatically in your test if you are using the +`@AutoConfigureWireMock` annotation (or the stub runner). If you are using the JUnit `@Rule` approach you need +to add the `@AutoConfigureHttpClient` annotation as well: + +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest("app.baseUrl=https://localhost:6443") +@AutoConfigureHttpClient +public class WiremockHttpsServerApplicationTests { + + @ClassRule + public static WireMockClassRule wiremock = new WireMockClassRule( + WireMockSpring.options().httpsPort(6443)); +... +} +---- + +If you are using `spring-boot-starter-test` then you will have the Apache HTTP client on the classpath and it will +be selected by the `RestTemplateBuilder` and configured to ignore SSL errors. If you are using the default `java.net` +client you don't need the annotation (but it won't do any harm). There is no support currently for other clients, but +it may be added in future releases. + == WireMock and Spring MVC Mocks Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a diff --git a/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java b/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java index dbf42f24cc..0608f2f338 100644 --- a/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java +++ b/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java @@ -3,6 +3,7 @@ package com.example; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,8 +14,8 @@ import org.springframework.web.client.RestTemplate; public class WiremockTestsApplication { @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); } public static void main(String[] args) { diff --git a/samples/wiremock-jetty/src/test/java/com/example/WiremockHttpsServerApplicationTests.java b/samples/wiremock-jetty/src/test/java/com/example/WiremockHttpsServerApplicationTests.java index e700ce0211..93b83c12d5 100644 --- a/samples/wiremock-jetty/src/test/java/com/example/WiremockHttpsServerApplicationTests.java +++ b/samples/wiremock-jetty/src/test/java/com/example/WiremockHttpsServerApplicationTests.java @@ -1,26 +1,28 @@ package com.example; +import com.github.tomakehurst.wiremock.junit.WireMockClassRule; + +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureHttpClient; +import org.springframework.cloud.contract.wiremock.WireMockSpring; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.assertj.core.api.Assertions.assertThat; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.contract.wiremock.WireMockSpring; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringRunner; - -import com.github.tomakehurst.wiremock.junit.WireMockClassRule; - - @RunWith(SpringRunner.class) @SpringBootTest("app.baseUrl=https://localhost:8443") @DirtiesContext +@AutoConfigureHttpClient public class WiremockHttpsServerApplicationTests { @ClassRule @@ -32,8 +34,8 @@ public class WiremockHttpsServerApplicationTests { @Test public void contextLoads() throws Exception { - stubFor(get(urlEqualTo("/resource")) - .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); + stubFor(get(urlEqualTo("/resource")).willReturn(aResponse() + .withHeader("Content-Type", "text/plain").withBody("Hello World!"))); assertThat(this.service.go()).isEqualTo("Hello World!"); } diff --git a/samples/wiremock-tomcat/src/main/java/com/example/WiremockTestsApplication.java b/samples/wiremock-tomcat/src/main/java/com/example/WiremockTestsApplication.java index fa59698f17..926417a038 100644 --- a/samples/wiremock-tomcat/src/main/java/com/example/WiremockTestsApplication.java +++ b/samples/wiremock-tomcat/src/main/java/com/example/WiremockTestsApplication.java @@ -3,6 +3,7 @@ package com.example; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,8 +14,8 @@ import org.springframework.web.client.RestTemplate; public class WiremockTestsApplication { @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); } public static void main(String[] args) { diff --git a/samples/wiremock-tomcat/src/test/java/com/example/WiremockHttpsServerApplicationTests.java b/samples/wiremock-tomcat/src/test/java/com/example/WiremockHttpsServerApplicationTests.java index add1d311fe..9e30002fdf 100644 --- a/samples/wiremock-tomcat/src/test/java/com/example/WiremockHttpsServerApplicationTests.java +++ b/samples/wiremock-tomcat/src/test/java/com/example/WiremockHttpsServerApplicationTests.java @@ -11,6 +11,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureHttpClient; import org.springframework.cloud.contract.wiremock.WireMockSpring; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; @@ -21,6 +22,7 @@ import com.github.tomakehurst.wiremock.junit.WireMockClassRule; @RunWith(SpringRunner.class) @SpringBootTest("app.baseUrl=https://localhost:6443") @DirtiesContext +@AutoConfigureHttpClient public class WiremockHttpsServerApplicationTests { @ClassRule diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureHttpClient.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureHttpClient.java new file mode 100644 index 0000000000..48f3e9d60b --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureHttpClient.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.wiremock; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; +import org.springframework.context.annotation.Import; + +/** + * Annotation for test classes that want to install a RestTemplateCustomizer that sets up + * a Spring Boot app to ignore SSL errors. Use only in tests! + * + * @author Dave Syer + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(WireMockRestTemplateConfiguration.class) +@PropertyMapping("wiremock.server") +public @interface AutoConfigureHttpClient { +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java index fb8db93eb1..b1337ba0c3 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMock.java @@ -39,6 +39,7 @@ import org.springframework.context.annotation.Import; @Documented @Import(WireMockConfiguration.class) @PropertyMapping("wiremock.server") +@AutoConfigureHttpClient public @interface AutoConfigureWireMock { int port() default 8080; diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestTemplateConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestTemplateConfiguration.java new file mode 100644 index 0000000000..c543b1a556 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestTemplateConfiguration.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.wiremock; + +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.web.client.RestTemplateCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * @author Dave Syer + * + */ +@Configuration +public class WireMockRestTemplateConfiguration { + + @Bean + @ConditionalOnClass(SSLContextBuilder.class) + public RestTemplateCustomizer restTemplateCustomizer() { + return new RestTemplateCustomizer() { + @Override + public void customize(RestTemplate restTemplate) { + HttpComponentsClientHttpRequestFactory factory = (HttpComponentsClientHttpRequestFactory) restTemplate + .getRequestFactory(); + factory.setHttpClient(createSslHttpClient()); + } + + private HttpClient createSslHttpClient() { + try { + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( + new SSLContextBuilder().loadTrustMaterial(null, + TrustSelfSignedStrategy.INSTANCE).build(), + NoopHostnameVerifier.INSTANCE); + return HttpClients.custom().setSSLSocketFactory(socketFactory) + .build(); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to create SSL HttpClient", + ex); + } + } + }; + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSpring.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSpring.java index e23dc40879..632ccc7b3d 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSpring.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockSpring.java @@ -18,13 +18,14 @@ package org.springframework.cloud.contract.wiremock; import javax.net.ssl.HttpsURLConnection; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; + import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.ssl.SSLContexts; import org.junit.Assert; -import org.springframework.util.ClassUtils; -import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import org.springframework.util.ClassUtils; /** * Convenience factory class for a {@link WireMockConfiguration} that knows how to use diff --git a/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories b/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories index 6c450975aa..9ac5aa6677 100644 --- a/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories @@ -4,4 +4,4 @@ org.springframework.cloud.contract.wiremock.WireMockApplicationListener # RestDocs Auto Configuration org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs=\ -org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocsConfiguration \ No newline at end of file +org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocsConfiguration