1. 서론: 웹 서비스 보안과 HTTPS 필수화

웹 서비스에서 HTTPS(HTTP over TLS)는 더 이상 권장 사항이 아닌 절대적인 필수 요건입니다. Google Chrome 등 주요 웹 브라우저는 평문 HTTP 통신을 사용하는 모든 웹사이트에 대해 ‘주의 요함(Not Secure)’ 경고를 띄우며, 검색 엔진(SEO) 평가 시에도 HTTPS 적용 사이트에 명확한 가산점을 부여합니다.

무엇보다 평문 HTTP 통신은 중간자 공격(Man-in-the-Middle, MITM)이나 네트워크 스니핑을 통해 사용자의 비밀번호, 세션 토큰(JSESSIONID), 결제 정보가 고스란히 유출될 수 있는 치명적인 보안 취약점을 내포하고 있습니다.

이번 포스팅에서는 Tomcat WAS 환경에서 SSL/TLS 인증서(PKCS#12, JKS)를 설치하는 최신 표준 설정 방법, HTTP(80) 요청의 HTTPS(443) 강제 리다이렉트, HSTS 보안 헤더 강화, 그리고 실무 트러블슈팅 포인트까지 종합적으로 정리합니다.


2. 인증서 포맷 비교: JKS vs PKCS#12 (.p12)

Java 생태계에서 SSL 인증서를 관리할 때 사용되는 두 가지 핵심 키스토어 포맷입니다.

항목 JKS (Java KeyStore) PKCS#12 (.p12 / .pfx)
표준성 Java 독자 규격 (오라클 종속적) RFC 7292 국제 표준 (범용 규격)
Java 버전 지원 Java 8 이전 기본 포맷 Java 9 이후 공식 표준(Default)
타 도구 호환성 keytool 외 C/Python/OpenSSL 등과 호환 난해 OpenSSL, IIS, Apache 등 모든 플랫폼 호환
보안 강도 독자 암호화 알고리즘 강력한 공개키 기반 암호화 표준

[!TIP] 최신 Tomcat 9, 10, 11 버전에서는 국제 표준이자 산업 표준인 PKCS#12 (.p12) 포맷을 사용할 것을 강력히 권장합니다.

Let’s Encrypt / OpenSSL (PEM) 인증서를 P12로 변환하기

상용 인증 기관이나 Let’s Encrypt에서 발급받은 PEM 파일(cert.pem, privkey.pem)은 openssl 명령어로 P12 키스토어로 묶어 Tomcat에 적용할 수 있습니다.

openssl pkcs12 -export \
  -in fullchain.pem \
  -inkey privkey.pem \
  -out conf/keystore.p12 \
  -name tomcat \
  -CAfile chain.pem \
  -caname root
# 키스토어 접근 비밀번호를 묻는 프롬프트에 안전한 패스워드를 입력합니다.

3. Tomcat server.xml HTTPS 커넥터 설정

Tomcat의 메인 설정 파일인 conf/server.xml을 열어 <Service name="Catalina"> 블록 내에 443 포트 커넥터를 추가합니다. 최신 Tomcat은 <SSLHostConfig>와 <Certificate> 태그를 계층적으로 구성하는 방식을 표준으로 채택하고 있습니다.

server.xml 상세 설정

<!-- HTTPS (SSL/TLS) 커넥터 설정 -->
<Connector port="443" 
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="200"
           SSLEnabled="true"
           scheme="https"
           secure="true">

    <SSLHostConfig protocols="TLSv1.2+TLSv1.3"
                   ciphers="TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
                            TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
                            TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
                            TLS_AES_128_GCM_SHA256,
                            TLS_AES_256_GCM_SHA384"
                   honorCipherOrder="true">
        
        <Certificate certificateKeystoreFile="conf/keystore.p12"
                     certificateKeystorePassword="your_secure_password"
                     certificateKeystoreType="PKCS12"
                     type="RSA" />
    </SSLHostConfig>

</Connector>

핵심 보안 파라미터 해설

  1. protocols="TLSv1.2+TLSv1.3": POODLE, BEAST 등 심각한 취약점이 발견된 레거시 프로토콜(SSLv2, SSLv3, TLSv1.0, TLSv1.1)을 원천 차단하고 보안이 검증된 TLS 1.2 및 TLS 1.3만 활성화합니다.
  2. ciphers 지정: Perfect Forward Secrecy(PFS)를 지원하는 타원곡선 기반 암호화 스위트(ECDHE)를 우선순위에 배치합니다.
  3. honorCipherOrder="true": 클라이언트가 제안한 암호화 스위트 대신 서버 측에서 지정한 안전한 암호화 알고리즘 우선순위를 따르도록 강제합니다.

4. HTTP(80) ➔ HTTPS(443) 자동 리다이렉트 구성

사용자가 브라우저 주소창에 http://myservice.com으로 접근하더라도 안전한 HTTPS 프로토콜로 자동 전환되도록 설정합니다.

1) server.xml의 HTTP 커넥터에 redirectPort 지정

conf/server.xml의 80번 포트 커넥터에서 redirectPort="443"으로 지정되어 있는지 확인합니다.

<Connector port="80" 
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           connectionTimeout="20000"
           redirectPort="443" />

2) web.xml에 보안 제약 조건(Security Constraint) 추가

conf/web.xml (전역 적용) 또는 개별 애플리케이션의 WEB-INF/web.xml 하단에 다음 XML 블록을 추가합니다.

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Force HTTPS Access</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
        <!-- CONFIDENTIAL: 암호화된 전송(HTTPS)만 허용하고 HTTP 요청 시 자동 302 리다이렉트 유도 -->
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

5. HSTS(HTTP Strict Transport Security) 보안 강화

리다이렉트만으로는 최초 접속 시 평문 HTTP 요청이 오가는 취약점 구간이 존재할 수 있습니다. 브라우저에게 “앞으로 1년간 무조건 HTTPS로만 접속하라"고 명령하는 HSTS 응답 헤더를 추가합니다.

conf/web.xml에 HttpHeaderSecurityFilter를 활성화합니다.

<filter>
    <filter-name>httpHeaderSecurity</filter-name>
    <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
    <init-param>
        <param-name>hstsEnabled</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>hstsMaxAgeSeconds</param-name>
        <param-value>31536000</param-value> <!-- 1년 -->
    </init-param>
    <init-param>
        <param-name>hstsIncludeSubDomains</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>httpHeaderSecurity</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

6. 실무 아키텍처 비교: 단독 Tomcat vs Nginx SSL 오프로딩

실무 대규모 서비스에서는 Tomcat 자체에서 SSL 핸드셰이크를 처리하기보다 앞단에 Nginx나 로드 밸런서를 두는 SSL Termination (오프로딩) 패턴을 압도적으로 선호합니다.

flowchart LR subgraph PatternA ["패턴 A: Tomcat 단독 HTTPS (소규모/내부망)"] direction TB C1[Client] == "HTTPS (Port 443)" ==> T1[Tomcat WAS: SSL 복호화 직접 수행] end
subgraph PatternB ["패턴 B: Nginx SSL 오프로딩 (권장 아키텍처)"]
    direction TB
    C2[Client] == "HTTPS (Port 443)" ==> N2[Nginx / ALB: SSL 복호화]
    N2 -- "HTTP (Port 8080 고속 내부망)" --> T2[Tomcat WAS: 순수 비즈니스 로직 전념]
end
  • SSL 오프로딩의 장점: 무거운 비대칭키 암호화 연산을 Nginx(C언어 기반 고속 OpenSSL)에 위임하여 Java JVM의 CPU 연산 부담을 대폭 절감하고, 인증서 갱신 시 Tomcat 재기동 없이 Nginx 리로드(nginx -s reload)만으로 무중단 갱신이 가능합니다.

7. 트러블슈팅 및 검증 체크리스트

  1. 리눅스 1024 이하 포트 바인딩 에러: 리눅스에서는 root 권한이 없는 일반 계정(tomcat)이 443, 80 포트를 직접 열 수 없습니다. authbind 유틸리티를 사용하거나 리버스 프록시(Nginx 443 ➔ Tomcat 8443) 포트 포워딩을 적용하세요.
  2. 비밀번호 불일치 오류: java.io.IOException: Keystore was tampered with, or password was incorrect 오류 발생 시 keytool -list -keystore keystore.p12 명령어로 키스토어 암호가 일치하는지 먼저 검증하세요.
  3. OpenSSL 핸드셰이크 검증:
    openssl s_client -connect localhost:443 -tls1_3
    
    반환값에 Verify return code: 0 (ok)가 나오는지 확인하여 인증서 체인 누락 여부를 점검할 수 있습니다.

8. 결론

Tomcat의 네이티브 SSL/TLS 적용은 PKCS#12 표준 포맷과 최신 SSLHostConfig 문법을 이해하면 손쉽게 구축할 수 있습니다. 단독 WAS 환경이라면 본 가이드의 server.xml과 web.xml 보안 제약 설정을, 분산 프로덕션 환경이라면 앞단 로드 밸런서 기반의 SSL 오프로딩을 고려하여 최적의 보안 파이프라인을 구축해 보시기 바랍니다.