✅ 문제 배경
Spring 서버가 자체 서명된(Self-signed) SSL 인증서를 사용하는 서버와 HTTPS 통신을 시도할 때 다음과 같은 SSL 오류가 발생합니다.
javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException:
PKIX path building failed:
unable to find valid certification path to requested target
✅ 직접 생성한 인증서라면
🚩 1단계: OpenSSL과 CNF 파일로 인증서 생성
CNF 파일이란?
OpenSSL 인증서 생성 시 사용하는 설정파일로, 인증서에 포함될 정보를 정의합니다. 특히 subjectAltName 필드는 IP 주소와 도메인을 인증서에 추가해줍니다. 최신 브라우저 및 JVM은 이 필드를 반드시 요구합니다.
openssl.cnf 파일 예시
[ req ]
default_bits = 2048
default_md = sha256
prompt = no
distinguished_name = dn
req_extensions = v3_req
[ dn ]
C = KR
ST = Seoul
L = Seoul
O = MyCompany
OU = IT Department
CN = 192.168.0.100
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 192.168.0.100
IP.2 = 127.0.0.1
DNS.1 = localhost
DNS.2 = my-internal-domain.local
- [ req ]: 인증서 요청 기본 설정
- default_bits: RSA 키 길이 (2048 권장)
- default_md: 인증서 서명 알고리즘 (sha256 권장)
- prompt: 정보 입력 없이 즉시 생성 (no)
- distinguished_name: 인증서 소유자 정보 설정 (dn)
- req_extensions: 추가 확장 필드(subjectAltName) 사용 지정
- [ dn ]: 인증서의 주체 정보 설정
- C: 국가 코드 (KR)
- ST: 시/도
- L: 도시
- O: 조직명
- OU: 부서명
- CN: 주체 이름 (IP 주소나 도메인)
- [ v3_req ]: 인증서에 추가되는 확장 필드 설정
- subjectAltName: 인증서가 지원하는 IP 주소 및 도메인
- [ alt_names ]: 실제 사용할 IP 및 DNS 목록 정의
CNF 파일을 사용한 인증서 생성 명령어
openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout key.pem -out cert.pem \
-config openssl.cnf -extensions v3_req
- key.pem: 개인키 파일
- cert.pem: 자체 서명된 SSL 인증서 파일
🚩 2단계: keytool을 이용하여 JVM truststore에 인증서 등록
keytool이란?
Java 인증서 관리 도구로, truststore(cacerts)에 인증서를 추가 및 관리합니다.
인증서 등록 명령어
keytool -importcert \
-alias internal-keycloak \
-keystore custom-cacerts \
-file cert.pem \
-storepass changeit \
-noprompt
- -importcert: 인증서를 truststore에 추가
- -alias: 인증서 별칭
- -keystore: 인증서 저장 위치
- -file: 인증서 파일
- -storepass: truststore 비밀번호 (changeit 기본값)
- -noprompt: 확인 프롬프트 생략
✅ 외부 서버에서 자체 서명된 SSL 인증서를 사용하고 있다면
🚩 1단계: 원격 서버 인증서 추출
openssl s_client -showcerts -connect 192.168.0.100:443 </dev/null 2>/dev/null | openssl x509 -outform PEM > external-server-cert.pem
- 192.168.0.100:443: 대상 서버의 IP 주소와 HTTPS 포트(일반적으로 443)
- 생성된 external-server-cert.pem 파일이 원격 서버의 인증서입니다.
추출한 인증서를 검증하고자 할 경우 아래 명령으로 정보를 확인할 수 있습니다.
openssl x509 -in external-server-cert.pem -text -noout
인증서의 세부 정보를 통해 subjectAltName 필드, 유효기간, 주체 정보 등을 확인할 수 있습니다.
🚩 2단계: JVM Truststore에 등록하기
추출된 인증서를 Java의 Truststore(cacerts)에 추가합니다.
keytool -importcert \
-alias external-server \
-keystore custom-cacerts \
-file external-server-cert.pem \
-storepass changeit \
-noprompt
- 인증서 별칭(alias)은 식별 가능한 이름으로 설정하세요.
🚩 3단계: Docker 컨테이너에 truststore 볼륨 마운트
미리 준비한 custom-cacerts 파일을 컨테이너 JVM의 truststore 경로에 마운트합니다.
services:
spring-app:
image: your-spring-image
volumes:
- ./custom-cacerts:/usr/lib/jvm/java-17-openjdk/lib/security/cacerts:ro
- JVM truststore 경로를 정확히 확인하고 일치시켜야 합니다.
🚩 4단계: JVM 옵션으로 truststore 경로 명시적 설정
JVM이 정확한 truststore 파일을 사용하도록 환경 변수를 설정합니다.
services:
spring-app:
image: your-spring-image
volumes:
- ./custom-cacerts:/usr/lib/jvm/java-17-openjdk/lib/security/cacerts:ro
environment:
- JAVA_OPTS=-Djavax.net.ssl.trustStore=/usr/lib/jvm/java-17-openjdk/lib/security/cacerts -Djavax.net.ssl.trustStorePassword=changeit
- javax.net.ssl.trustStore: truststore 경로 설정
- javax.net.ssl.trustStorePassword: truststore 파일 비밀번호 설정
'JVM' 카테고리의 다른 글
[Java] 메서드와 필드 접근: 런타임과 컴파일 타임의 차이 (1) | 2025.06.05 |
---|---|
[Java] 제네릭과 오버로딩, 왜 예상과 다를까? (1) | 2025.06.05 |
[Java] 자바는 Call by Value (1) | 2025.05.17 |
[JVM] JDBC 쿼리 및 메서드 실행 로깅 (0) | 2024.08.17 |
[JVM] Jackson의 ObjectMapper: 객체 생성 방식과 필드 바인딩 (0) | 2024.08.10 |