Skip to content

Commit cfdc173

Browse files
committed
Add RestClient SSL support
Add `RestClientSsl` support class to help apply an `SslBundle` to a `RestClient.Builder`. See gh-36213
1 parent 7c1b168 commit cfdc173

File tree

10 files changed

+364
-0
lines changed

10 files changed

+364
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.web.client;
18+
19+
import java.util.function.Consumer;
20+
21+
import org.springframework.boot.ssl.SslBundle;
22+
import org.springframework.boot.ssl.SslBundles;
23+
import org.springframework.boot.web.client.ClientHttpRequestFactories;
24+
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
25+
import org.springframework.http.client.ClientHttpRequestFactory;
26+
import org.springframework.web.client.RestClient;
27+
28+
/**
29+
* An auto-configured {@link RestClientSsl} implementation.
30+
*
31+
* @author Phillip Webb
32+
*/
33+
class AutoConfiguredRestClientSsl implements RestClientSsl {
34+
35+
private final SslBundles sslBundles;
36+
37+
AutoConfiguredRestClientSsl(SslBundles sslBundles) {
38+
this.sslBundles = sslBundles;
39+
}
40+
41+
@Override
42+
public Consumer<RestClient.Builder> fromBundle(String bundleName) {
43+
return fromBundle(this.sslBundles.getBundle(bundleName));
44+
}
45+
46+
@Override
47+
public Consumer<RestClient.Builder> fromBundle(SslBundle bundle) {
48+
return (builder) -> {
49+
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS.withSslBundle(bundle);
50+
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings);
51+
builder.requestFactory(requestFactory);
52+
};
53+
}
54+
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.web.client;
18+
19+
import java.util.function.Consumer;
20+
21+
import org.springframework.boot.ssl.NoSuchSslBundleException;
22+
import org.springframework.boot.ssl.SslBundle;
23+
import org.springframework.boot.web.client.ClientHttpRequestFactories;
24+
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
25+
import org.springframework.http.client.ClientHttpRequestFactory;
26+
import org.springframework.web.client.RestClient;
27+
28+
/**
29+
* Interface that can be used to {@link RestClient.Builder#apply apply} SSL configuration
30+
* to a {@link org.springframework.web.client.RestClient.Builder RestClient.Builder}.
31+
* <p>
32+
* Typically used as follows: <pre class="code">
33+
* &#064;Bean
34+
* public MyBean myBean(RestClient.Builder restClientBuilder, RestClientSsl ssl) {
35+
* RestClient restClientrestClient= restClientBuilder.apply(ssl.forBundle("mybundle")).build();
36+
* return new MyBean(webClient);
37+
* }
38+
* </pre> NOTE: Apply SSL configuration will replace any previously
39+
* {@link RestClient.Builder#requestFactory configured} {@link ClientHttpRequestFactory}.
40+
* If you need to configure {@link ClientHttpRequestFactory} with more than just SSL
41+
* consider using a {@link ClientHttpRequestFactorySettings} with
42+
* {@link ClientHttpRequestFactories}.
43+
*
44+
* @author Phillip Webb
45+
* @since 3.2.0
46+
*/
47+
public interface RestClientSsl {
48+
49+
/**
50+
* Return a {@link Consumer} that will apply SSL configuration for the named
51+
* {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder
52+
* RestClient.Builder}.
53+
* @param bundleName the name of the SSL bundle to apply
54+
* @return a {@link Consumer} to apply the configuration
55+
* @throws NoSuchSslBundleException if a bundle with the provided name does not exist
56+
*/
57+
Consumer<RestClient.Builder> fromBundle(String bundleName) throws NoSuchSslBundleException;
58+
59+
/**
60+
* Return a {@link Consumer} that will apply SSL configuration for the
61+
* {@link SslBundle} to a {@link org.springframework.web.client.RestClient.Builder
62+
* RestClient.Builder}.
63+
* @param bundle the SSL bundle to apply
64+
* @return a {@link Consumer} to apply the configuration
65+
*/
66+
Consumer<RestClient.Builder> fromBundle(SslBundle bundle);
67+
68+
}

spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/rest-client.adoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,22 @@ In that case, no auto-configuration or `RestClientCustomizer` is applied.
107107

108108

109109

110+
[[io.rest-client.restclient.ssl]]
111+
==== RestClient SSL Support
112+
If you need custom SSL configuration on the `ClientHttpRequestFactory` used by the `RestClient`, you can inject a `RestClientSsl` instance that can be used with the builder's `apply` method.
113+
114+
The `RestClientSsl` interface provides access to any <<features#features.ssl.bundles,SSL bundles>> that you have defined in your `application.properties` or `application.yaml` file.
115+
116+
The following code shows a typical example:
117+
118+
include::code:MyService[]
119+
120+
If you need to apply other customization in addition to an SSL bundle, you can use the `ClientHttpRequestFactorySettings` class with `ClientHttpRequestFactories`:
121+
122+
include::code:settings/MyService[]
123+
124+
125+
110126
[[io.rest-client.resttemplate]]
111127
=== RestTemplate
112128
Spring Framework's {spring-framework-api}/web/client/RestTemplate.html[`RestTemplate`] class predates `RestClient` and is the classic way that many applications use to call remote REST services.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.io.restclient.restclient.ssl;
18+
19+
import org.springframework.boot.autoconfigure.web.client.RestClientSsl;
20+
import org.springframework.stereotype.Service;
21+
import org.springframework.web.client.RestClient;
22+
23+
@Service
24+
public class MyService {
25+
26+
private final RestClient restClient;
27+
28+
public MyService(RestClient.Builder restClientBuilder, RestClientSsl ssl) {
29+
this.restClient = restClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();
30+
}
31+
32+
public Details someRestCall(String name) {
33+
return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);
34+
}
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.io.restclient.restclient.ssl.settings;
18+
19+
public class Details {
20+
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.io.restclient.restclient.ssl.settings;
18+
19+
import java.time.Duration;
20+
21+
import org.springframework.boot.ssl.SslBundles;
22+
import org.springframework.boot.web.client.ClientHttpRequestFactories;
23+
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
24+
import org.springframework.http.client.ClientHttpRequestFactory;
25+
import org.springframework.stereotype.Service;
26+
import org.springframework.web.client.RestClient;
27+
28+
@Service
29+
public class MyService {
30+
31+
private final RestClient restClient;
32+
33+
public MyService(RestClient.Builder restClientBuilder, SslBundles sslBundles) {
34+
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
35+
.withReadTimeout(Duration.ofMinutes(2))
36+
.withSslBundle(sslBundles.getBundle("mybundle"));
37+
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings);
38+
this.restClient = restClientBuilder.baseUrl("https://example.org").requestFactory(requestFactory).build();
39+
}
40+
41+
public Details someRestCall(String name) {
42+
return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);
43+
}
44+
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.io.restclient.restclient.ssl
18+
19+
class Details
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.io.restclient.restclient.ssl
18+
19+
import org.springframework.boot.autoconfigure.web.client.RestClientSsl
20+
import org.springframework.boot.docs.io.restclient.restclient.ssl.settings.Details
21+
import org.springframework.stereotype.Service
22+
import org.springframework.web.client.RestClient
23+
24+
@Service
25+
class MyService(restClientBuilder: RestClient.Builder, ssl: RestClientSsl) {
26+
27+
private val restClient: RestClient
28+
29+
init {
30+
restClient = restClientBuilder.baseUrl("https://example.org")
31+
.apply(ssl.fromBundle("mybundle")).build()
32+
}
33+
34+
fun someRestCall(name: String?): Details {
35+
return restClient.get().uri("/{name}/details", name)
36+
.retrieve().body(Details::class.java)!!
37+
}
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.io.restclient.restclient.ssl.settings
18+
19+
class Details
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.io.restclient.restclient.ssl.settings
18+
19+
import org.springframework.boot.ssl.SslBundles
20+
import org.springframework.boot.web.client.ClientHttpRequestFactories
21+
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings
22+
import org.springframework.stereotype.Service
23+
import org.springframework.web.client.RestClient
24+
import java.time.Duration
25+
26+
@Service
27+
class MyService(restClientBuilder: RestClient.Builder, sslBundles: SslBundles) {
28+
29+
private val restClient: RestClient
30+
31+
init {
32+
val settings = ClientHttpRequestFactorySettings.DEFAULTS
33+
.withReadTimeout(Duration.ofMinutes(2))
34+
.withSslBundle(sslBundles.getBundle("mybundle"))
35+
val requestFactory = ClientHttpRequestFactories.get(settings)
36+
restClient = restClientBuilder
37+
.baseUrl("https://example.org")
38+
.requestFactory(requestFactory).build()
39+
}
40+
41+
fun someRestCall(name: String?): Details {
42+
return restClient.get().uri("/{name}/details", name).retrieve().body(Details::class.java)!!
43+
}
44+
45+
}
46+

0 commit comments

Comments
 (0)