SSL handshake failure when RestTemplate calls self‑signed HTTPS endpoint in Grails
0 reputation · 20 Mar 2021, 15:56 UTC
0 reputation · 20 Mar 2021, 15:56 UTC
When a Grails service injects the default RestTemplate to invoke an external HTTPS endpoint that presents a self‑signed certificate, the request fails with an SSL handshake error indicating the certificate chain cannot be validated. The goal is to configure the client to trust that certificate for outbound calls while avoiding unintended side effects on other components that rely on the default RestTemplate (e.g., Actuator endpoints, third‑party plugins). The documentation does not specify where a custom RestTemplate bean should be declared in a typical Grails project layout, nor whether it should be marked as primary or scoped to a specific service.
RestTemplate be declared as the primary bean or qualified with a name?grails-app/conf/spring/resources.groovy, a @Configuration class, or another file) for declaring such a bean in Grails 4+?34775 reputation · 20 Mar 2021, 23:43 UTC
Use a named RestTemplate bean, not @Primary, and inject it into the service that needs the self‑signed endpoint. The default RestTemplate (used by Actuator and other libraries) remains unchanged.
grails-app/conf/spring/resources.groovy – the Grails‑style bean registry.@Configuration class – the Spring Boot‑style approach.Both work; the resources.groovy file is processed after auto‑configuration, so a bean with the same name will not override the default unless marked @Primary.
// src/main/groovy/com/example/Config.groovy
package com.example
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.ClientHttpRequestFactory
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
import org.springframework.web.client.RestTemplate
import org.apache.http.conn.ssl.SSLConnectionSocketFactory
import org.apache.http.conn.ssl.TrustSelfSignedStrategy
import org.apache.http.ssl.SSLContexts
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
@Configuration
class RestTemplateConfig {
@Bean(name = "trustedRestTemplate")
RestTemplate trustedRestTemplate() {
// Load a truststore that contains the self‑signed cert
def trustStore = KeyStore.getInstance(KeyStore.defaultType)
trustStore.load(new FileInputStream("src/main/resources/keystore.jks"), 'changeit'.toCharArray())
def sslContext = SSLContexts.custom()
.loadTrustMaterial(trustStore, null)
.build()
def socketFactory = new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier())
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build()
ClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient)
return new RestTemplate(factory)
}
}
Replace the keystore path, password, and hostname verifier with values that match your environment. If the cert is a PEM file, load it into a KeyStore or use SSLContexts.custom().loadTrustMaterial(new File("cert.pem"), null).
package com.example.service
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
@Service
class ExternalApiService {
@Autowired
@Qualifier("trustedRestTemplate")
RestTemplate restTemplate
String callSelfSignedEndpoint() {
restTemplate.getForObject("https://selfsigned.example.com/api", String)
}
}
Because the bean is named, only components that request trustedRestTemplate receive it. The default RestTemplate stays untouched.
./gradlew test and assert that ExternalApiService uses the named bean./actuator/health and confirm it still uses the default RestTemplate.By default the bean is a singleton, which is usually fine. If you need per‑request isolation, add @Scope("prototype") to the bean definition.
Do you have the self‑signed certificate in a JKS keystore, a PEM file, or another format? The loading code above assumes a JKS file; a different format would change the loadTrustMaterial call.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.