본문 바로가기
  • 개발하는 곰돌이
Development/Errors

[WebClient] Spring WebClient로 HTTPS 통신하기(sun.security.provider.certpath.SunCertPathBuilderException 해결)

by 개발하는 곰돌이 2023. 7. 6.

목차

    시작하기 전에

    이전에 Spring WebClient로 HTTPS를 사용하는 웹사이트에 연결을 시도할 때 SunCertPathBuilderException이 발생하는 것을 해결하기 위해 자바에서 신뢰하는 인증서 목록에 해당 서버의 인증서를 추가하는 것으로 해결하는 글을 작성한 적이 있다.

     

    그 당시에는 WebClient를 사용하는 방법에 익숙치 않다보니 서버마다 인증서를 추출해서 신뢰하는 인증서 목록에 추가하는 비효율적인 방법을 사용했는데, 모든 환경에서 이런 복잡한 절차 없이 WebClient를 사용한 HTTPS 통신이 가능하다는 것을 알게 되어 정리하려고 한다.

    주의사항 : 이 방법은 검증 없이 모든 인증서를 신뢰하게 만들기 때문에 실제 제품에서 함부로 사용하면 안된다.

    아무 설정을 하지 않은 경우

    기본적으로 자바에서 신뢰하는 인증서 목록에 등록된 인증서를 사용하는 서버가 아니라면 아래와 같은 코드를 사용하여 호출했을 때 이런 에러가 발생하는 것을 볼 수 있다.

    fun callApi() {
        WebClient.create(url)
            .get()
            .exchangeToMono {
                if (it.statusCode().is2xxSuccessful) println("API 호출 성공")
                it.bodyToMono(Response::class.java)
            }
            .block()
    }

    신뢰하는 인증서에 추가하지 않고 간단히 해결하는 방법

    이 문제는 API를 호출하는 코드를 약간 수정하는 것으로 모든 인증서를 신뢰하게 할 수 있다. 위의 callApi()를 다음과 같이 수정해주면 된다.

     

    HttpClient는 reactor.netty.http.client.HttpClient를 사용해야 한다.

     

    Kotlin

    fun callApi() {
        val httpClient = HttpClient.create()
            .secure { it.sslContext(
                SslContextBuilder.forClient()
                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
                    .build()) 
                }
                
        WebClient.builder()
            .baseUrl(url)
            .clientConnector(ReactorClientHttpConnector(httpClient))
            .get()
            .exchangeToMono {
                if (it.statusCode().is2xxSuccessful) println("API 호출 성공")
                it.bodyToMono(Response::class.java)
            }
            .block()
    }

     

    Java

    void callApi() throws SSLException {
        SslContext context = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        HttpClient httpClient = HttpClient.create().secure(provider -> provider.sslContext(context));
        
        WebClient.builder()
            .baseUrl(url)
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .get()
            .exchangeToMono(response -> {
                    if (response.statusCode().is2xxSuccessful) System.out.println("API 호출 성공")
                    return response.bodyToMono(Response.class)
                }
            )
            .block();
    }

    이렇게 코드를 수정하고 실행해보면 정상적으로 실행되는 것을 확인할 수 있다.

     

    여담

    앞서 이야기한 것처럼 이 방법은 번거로운 설정 없이 모든 HTTPS 연결이 가능하다는 점에서 분명 매력적이지만, 인증서를 검증하지 않고 신뢰한다는 점으로 인해 이 방법을 함부로 사용하면 보안 상 문제가 발생할 수 있기 때문에 함부로 사용하면 안된다.

    참조 링크

     

    Disable SSL verification in Spring WebClient

    We can use an insecure TrustManagerFactory that trusts all X.509 certificates without any verification. This will allow any https certificate (self-signed, expired, etc) with WebClient communication.

    www.javacodemonk.com

     

    댓글