
https 통신을 위해 로컬에서 자체 서명 인증서를 사용하여 개발하고 있었다.

WebClient로 다른 서버를 호출하니 위와 같이 에러가 발생했다.
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:378)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:316)
at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1351)
at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1226)
at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1169)
at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396)
<28 folded frames>
여기서 핵심적으로 볼 파트는 여기다.
unable to find valid certification path to requested target
요청한 서버의 SSL 인증서 검증 과정에서 서버 인증서를 신뢰할 수 없어서 발생하는 문제이다.
즉, WebClient가 요청을 보내는 대상 서버의 SSL 인증서가 신뢰할 수 있는 CA(Certificate Authority)로 서명되어 있지 않거나, 로컬 JVM의 cacerts(truststore)에 해당 인증서 체인이 등록되어 있지 않아서 생긴 문제이다.
@Configuration
@RequiredArgsConstructor
public class WebClientConfig {
private final String API_KEY_HEADER = "X-API-KEY";
@Bean(name = "formatWebClient")
public WebClient formatWebClient() throws SSLException {
// --- 이부분 추가 ---
SslContext sslContext = SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
HttpClient httpClient = HttpClient.create()
.secure(ssl -> ssl.sslContext(sslContext));
// ---
return WebClient.builder()
.baseUrl("<Other Server's baseURL>")
.defaultHeader(API_KEY_HEADER, apiKeyProvider.getEncryptedApiKey())
// --- 이부분 추가 ---
.clientConnector(new ReactorClientHttpConnector(httpClient))
// ---
.filter((request, next) -> next.exchange(request)
.flatMap(response -> {
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.defaultIfEmpty("Unknown error")
.flatMap(body -> {
log.error("API Error Response: {}", body);
return Mono.error(new RuntimeException("INTERNAL_SERVER_ERROR"));
});
} else {
return Mono.just(response);
}
})
)
.build();
}
}
위와 같이 WebClientConfig를 설정해주었다. 물론 권장되는 방법은 아니다.
SslContext sslContext = SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
HttpClient httpClient = HttpClient.create()
.secure(ssl -> ssl.sslContext(sslContext));
위의 코드는 코드레벨에서 SSL 검증을 무시하는 코드이다. Spring WebClient에 HttpClient를 커스터마이징해서 SSL 검증을 끈 것이다. 즉, 모든 SSL 인증서를 신뢰한다는 것이다.

이런 코드가 보안상 좋을 리가 없다. 그래서 테스트 또는 내부망에서만 사용해야 한다.
호출할 서버가 현재 이 서버만 거쳐서(내부망에서만 이용) 이렇게 작성하고 끝내도 괜찮았다.
하지만 JAVA 인증서 저장소에 인증서를 등록하여 해결하고 싶다면
위의 블로그 글을 참고해봐도 괜찮을 것 같다.
질문과 피드백은 언제나 환영입니다 :)