# Tomcat SSL/TLS 설정 가이드 (Apache/Nginx 연동)

## 1. 개요

이 가이드는 **Tomcat**으로 실행되는 웹 애플리케이션에 **SSL/TLS (HTTPS)를 적용**하는 다양한 방법을 설명합니다. 특히, 사내 인프라(`192.168.0.61`과 같은 중앙 프록시)와 유사한 환경 및 클라우드 배포 환경 모두에서 적용할 수 있는 표준적인 방법을 다룹니다.

다양한 조합(Docker/Bare-metal, Apache/Nginx, Let's Encrypt/자체 서명 인증서)에 따른 설정 방법을 체계적으로 안내합니다.

이 가이드의 핵심 전략은 **리버스 프록시(Reverse Proxy)를 이용한 SSL 오프로딩(SSL Offloading)**입니다.

## 2. 핵심 전략: SSL 오프로딩 (TLS Termination)

SSL 오프로딩은 클라이언트와의 암호화된 통신(HTTPS)을 백엔드 애플리케이션(Tomcat) 앞단의 리버스 프록시(Nginx, Apache)가 전담하여 처리하는 구조입니다.

**장점:**

-   **중앙 집중식 SSL 관리**: 인증서 관리를 리버스 프록시 한 곳에서 처리하므로 관리가 쉽고, 여러 Tomcat 인스턴스가 있어도 개별적으로 SSL을 설정할 필요가 없습니다.
-   **성능 향상**: SSL 암호화/복호화 연산은 CPU를 많이 사용합니다. 웹 서버(Nginx/Apache)가 이를 전담함으로써 Tomcat은 비즈니스 로직 처리에 더 많은 리소스를 사용할 수 있습니다.
-   **유연성 및 보안**: 리버스 프록시가 로드 밸런싱, 캐싱, 요청 필터링, 로깅 등 다양한 기능을 수행할 수 있으며, Tomcat 서버가 외부에 직접 노출되지 않아 보안이 강화됩니다.

이 구조에서 Tomcat은 SSL 설정을 할 필요 없이 일반 HTTP 또는 AJP 프로토콜로 실행됩니다.

## 3. 1단계: SSL 인증서 준비

리버스 프록시에 적용할 SSL 인증서를 준비합니다. 환경에 따라 두 가지 방법 중 하나를 선택합니다.

### 3.1. 개발 환경: 자체 서명 인증서 (Self-Signed Certificate)

로컬이나 개발 서버에서 테스트 용도로 사용합니다. `openssl` 명령어로 생성합니다.

```bash
# 인증서를 저장할 디렉토리 생성
mkdir -p certs

# 2048비트 RSA 키와 365일 유효한 자체 서명 인증서 생성
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout certs/self-signed.key \
    -out certs/self-signed.crt \
    -subj "/C=KR/ST=Seoul/L=Gangnam/O=MyOrg/OU=MyDept/CN=localhost"
```

-   `CN` (Common Name)에는 접속할 도메인(예: `dev.mydomain.com` 또는 `localhost`)을 입력합니다.
-   생성된 `self-signed.key`(개인키)와 `self-signed.crt`(인증서) 파일을 리버스 프록시 서버의 특정 경로에 저장합니다.

### 3.2. 운영 환경: Let's Encrypt 인증서 (Certbot 사용)

신뢰할 수 있는 무료 인증 기관인 Let's Encrypt의 인증서를 사용합니다. `certbot` 클라이언트를 통해 발급 및 자동 갱신을 설정하는 것이 일반적입니다.

**사전 준비:**

-   서버를 가리키는 공인 도메인 (예: `www.your-domain.com`)
-   서버의 80번 포트(HTTP)가 외부에 개방되어 있어야 합니다. (Certbot이 도메인 소유권을 확인하기 위해 사용)

**Certbot 설치 (Ubuntu/Debian 기준):**

```bash
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
```

**인증서 발급 (Nginx/Apache 중단 방식):**

리버스 프록시를 잠시 중단하고 독립 실행형(standalone) 모드로 인증서를 발급받는 것이 가장 간단합니다.

```bash
# Nginx 사용 시
sudo systemctl stop nginx

# Apache 사용 시
sudo systemctl stop apache2

# Certbot 실행
sudo certbot certonly --standalone -d your-domain.com --email your-email@example.com --agree-tos

# 프록시 재시작
sudo systemctl start nginx # 또는 apache2
```

-   인증서는 `/etc/letsencrypt/live/your-domain.com/` 디렉토리 내에 `fullchain.pem`(인증서)과 `privkey.pem`(개인키)로 생성됩니다.
-   Certbot은 자동으로 갱신을 시도하도록 cron job 또는 systemd timer를 설정합니다. `sudo certbot renew --dry-run`으로 갱신을 테스트할 수 있습니다.

**Docker 환경에서의 Certbot:**
이전 `docker_ssl_setup_guide.md`의 `certbot` 서비스 부분을 참고하여 구성할 수 있습니다. `certbot` 컨테이너가 Nginx와 볼륨을 공유하여 인증서를 발급하고 갱신하도록 설정합니다.

## 4. 2단계: 리버스 프록시 설정 (SSL 적용)

준비된 인증서를 사용하여 Nginx 또는 Apache에 SSL을 설정하고, Tomcat으로 요청을 전달합니다.

### 4.1. Nginx로 설정하기

#### Bare-metal 환경

`/etc/nginx/sites-available/`에 다음과 같은 설정 파일을 작성합니다. (`your-domain.com.conf`)

```nginx
# /etc/nginx/sites-available/your-domain.com.conf

# HTTP (80) -> HTTPS (443) 리디렉션
server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-domain.com;

    # 1. SSL 인증서 설정
    # Let's Encrypt 사용 시
    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    # 자체 서명 인증서 사용 시
    # ssl_certificate /path/to/certs/self-signed.crt;
    # ssl_certificate_key /path/to/certs/self-signed.key;

    # SSL 보안 강화 옵션
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    location / {
        # 2. Tomcat으로 요청 전달 (HTTP)
        # Tomcat 서버가 10.0.0.5:8080에서 실행 중이라고 가정
        proxy_pass http://10.0.0.5:8080;

        # 3. 프록시 관련 헤더 설정 (Tomcat에서 클라이언트 IP 등을 인식하기 위함)
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

**설정 적용:**

```bash
# 심볼릭 링크 생성
sudo ln -s /etc/nginx/sites-available/your-domain.com.conf /etc/nginx/sites-enabled/
# 설정 테스트
sudo nginx -t
# Nginx 재시작
sudo systemctl restart nginx
```

#### Docker 환경 (Nginx + Tomcat 연동)

`docker-compose.yml` 파일과 `nginx.conf` 파일을 프로젝트 루트에 구성합니다.

**`docker-compose.yml`**
```yaml
version: '3.8'

services:
  tomcat:
    image: tomcat:9.0-jdk11-temurin
    # server.xml 등을 마운트 할 수 있음
    # volumes:
    #   - ./tomcat/conf/server.xml:/usr/local/tomcat/webapps/conf/server.xml
    expose:
      - "8080" # 컨테이너 내부 8080 포트
    networks:
      - my-network

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      # nginx.conf 마운트
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      # 인증서 마운트 (letsencrypt 또는 self-signed)
      - /etc/letsencrypt:/etc/letsencrypt:ro
      # 또는 - ./certs:/etc/nginx/certs:ro
    depends_on:
      - tomcat
    networks:
      - my-network

networks:
  my-network:
    driver: bridge
```

**`nginx.conf`**
- Bare-metal 설정과 거의 동일하지만, `proxy_pass` 부분이 다릅니다.
- Docker의 내부 DNS를 사용하여 서비스 이름(`tomcat`)으로 요청을 전달합니다.

```nginx
# ... (server 블록 및 ssl 설정은 위와 동일) ...
    location / {
        # 서비스 이름 'tomcat'과 노출된 포트 '8080'으로 전달
        proxy_pass http://tomcat:8080;

        proxy_set_header Host $host;
        # ... (나머지 헤더 설정은 동일) ...
    }
# ...
```

### 4.2. Apache로 설정하기

#### Bare-metal 환경

`mod_ssl`, `mod_proxy`, `mod_proxy_http` (또는 `mod_proxy_ajp`) 모듈이 활성화되어 있어야 합니다.

```bash
# 모듈 활성화 (Debian/Ubuntu)
sudo a2enmod ssl proxy proxy_http

# AJP 연동을 원할 경우
# sudo a2enmod proxy_ajp
```

`/etc/apache2/sites-available/`에 다음과 같은 가상 호스트 설정 파일을 작성합니다. (`your-domain-ssl.conf`)

```apache
# /etc/apache2/sites-available/your-domain-ssl.conf

# HTTP -> HTTPS 리디렉션
<VirtualHost *:80>
    ServerName your-domain.com
    Redirect permanent / https://your-domain.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName your-domain.com

    # 1. SSL 설정
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/your-domain.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/your-domain.com/privkey.pem

    # 2. Tomcat으로 요청 전달 (선택 1: HTTP 프록시)
    ProxyPreserveHost On
    ProxyPass / http://10.0.0.5:8080/
    ProxyPassReverse / http://10.0.0.5:8080/

    # 2. Tomcat으로 요청 전달 (선택 2: AJP 프록시)
    # Tomcat의 server.xml에 AJP Connector가 활성화(보통 8009 포트)되어 있어야 함
    # ProxyPass / ajp://10.0.0.5:8009/
    # ProxyPassReverse / ajp://10.0.0.5:8009/

    # 3. 프록시 관련 헤더 설정 (HTTP 프록시 사용 시)
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Port "443"
</VirtualHost>
```

**설정 적용:**

```bash
# 사이트 활성화
sudo a2ensite your-domain-ssl.conf
# 설정 테스트
sudo apache2ctl configtest
# Apache 재시작
sudo systemctl restart apache2
```

#### Docker 환경 (Apache + Tomcat 연동)

공식 `httpd` 이미지를 사용하여 구성할 수 있습니다. `apache.conf`와 `docker-compose.yml`이 필요합니다.

**`docker-compose.yml`**
```yaml
version: '3.8'

services:
  tomcat:
    # ... (Nginx 예시와 동일) ...

  apache:
    image: httpd:2.4
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./apache.conf:/usr/local/apache2/conf/extra/httpd-ssl.conf
      - /etc/letsencrypt:/etc/letsencrypt:ro
      # httpd.conf를 수정하여 ssl.conf를 include 하거나 직접 마운트
      # ... 복잡하므로 Bare-metal을 더 권장 ...
    depends_on:
      - tomcat
    # ...
```
*참고: Docker에서 Apache SSL을 설정하는 것은 Nginx보다 다소 복잡합니다. `httpd.conf`를 직접 수정하여 `mod_ssl` 로드, `Listen 443`, `Include conf/extra/httpd-ssl.conf` 등의 지시어를 관리해야 하기 때문입니다.*

## 5. 3단계: Tomcat 설정 (`server.xml`)

리버스 프록시 뒤에서 Tomcat이 올바르게 동작하도록 `server.xml` 파일을 설정합니다. 핵심은 Tomcat이 클라이언트의 원래 요청(HTTPS, 도메인 등)을 인지하도록 만드는 것입니다. **`RemoteIpValve`를 사용하는 것이 가장 표준적이고 권장되는 방법입니다.**

**`conf/server.xml`**

```xml
<Host name="localhost"  appBase="webapps"
      unpackWARs="true" autoDeploy="true">

    <!--
      RemoteIpValve:
      Nginx/Apache와 같은 프록시 서버로부터 받은 헤더를 기반으로
      request.getRemoteAddr(), request.getScheme() 등의 값을 실제 클라이언트 값으로 대체해줍니다.
    -->
    <Valve className="org.apache.catalina.valves.RemoteIpValve"
           internalProxies="192\.168\.0\.61|172\.\d{1,3}\.\d{1,3}\.\d{1,3}|..."  <!-- 사내 프록시 IP, Docker 네트워크 대역 등 프록시 서버 IP 대역을 정규식으로 추가 -->
           remoteIpHeader="x-forwarded-for"
           protocolHeader="x-forwarded-proto"
           />

</Host>
```

-   `internalProxies`: 신뢰할 수 있는 프록시 서버의 IP 주소 목록입니다. 여기에 등록된 IP가 보낸 `x-forwarded-*` 헤더만 신뢰합니다. **중앙 프록시(`192.168.0.61`), Docker의 게이트웨이 IP 등을 등록해야 합니다.**
-   `remoteIpHeader`: 클라이언트의 실제 IP가 담긴 헤더 이름입니다. (`X-Real-IP` 또는 `X-Forwarded-For`)
-   `protocolHeader`: 원래 프로토콜(http/https)이 담긴 헤더 이름입니다. (`X-Forwarded-Proto`)

`RemoteIpValve`를 사용하면, `ProxyPass` 설정 시 `proxyName`, `proxyPort`, `scheme` 같은 속성을 Connector에 일일이 설정할 필요가 없어 구성이 매우 깔끔해집니다.

Tomcat의 Connector는 SSL 설정 없이 일반 HTTP/AJP로 유지합니다.

**HTTP Connector:**
```xml
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />
```

**AJP Connector (AJP 연동 시):**
```xml
<Connector protocol="AJP/1.3"
           address="::1"
           port="8009"
           redirectPort="8443" />
```

## 6. 결론

Tomca.t 애플리케이션에 SSL을 적용하는 가장 현대적이고 효율적인 방법은 **리버스 프록시를 사용한 SSL 오프로딩**입니다

1.  `openssl` 또는 `certbot`으로 **인증서를 준비**합니다.
2.  **Nginx/Apache 프록시 서버에 SSL을 설정**하고 Tomcat으로 요청을 전달합니다. (Docker/Bare-metal 환경에 맞게)
3.  **Tomcat의 `server.xml`에는 `RemoteIpValve`를 설정**하여 프록시 환경에 대응합니다.

이 구조를 통해 SSL 인증서 관리와 트래픽 제어를 프록시 서버에 위임하고, Tomcat은 비즈니스 로직에 집중하여 안정적이고 확장성 있는 서비스 아키텍처를 구축할 수 있습니다.

## 7. 고급 구성: End-to-End 암호화 (Re-Encryption)

지금까지 설명한 **SSL 오프로딩**이 가장 일반적이고 효율적인 방법이지만, 내부망 보안 규정과 같은 특수한 요구사항으로 인해 리버스 프록시와 Tomcat 서버 간의 통신까지 암호화해야 하는 경우가 있습니다. 이 구성을 "End-to-End 암호화" 또는 "Re-Encryption"이라고 부릅니다.

**구조:** `Client --(HTTPS)--> Nginx/Apache --(HTTPS)--> Tomcat`

이 방식은 보안을 극대화하지만, 다음과 같은 추가 작업이 필요하며 약간의 성능 저하가 발생할 수 있습니다.

1.  Tomcat 자체에 SSL/TLS를 설정해야 합니다.
2.  리버스 프록시가 Tomcat의 HTTPS 포트(예: 8443)로 요청을 전달하도록 구성해야 합니다.

### 7.1. 1단계: Tomcat에 SSL 인증서 적용

Tomcat이 HTTPS 요청을 처리할 수 있도록 `conf/server.xml` 파일의 `<Connector>`를 설정합니다. Tomcat 8.5 이상 버전은 Let's Encrypt에서 발급받은 PEM 형식의 인증서를 직접 사용할 수 있어 편리합니다.

**`conf/server.xml` 예시 (PEM 인증서 사용):**

```xml
<!-- HTTP Connector는 비활성화하거나 다른 포트로 유지할 수 있습니다. -->

<!-- HTTPS/TLS Connector 설정 -->
<Connector
        protocol="org.apache.coyote.http11.Http11NioProtocol"
        port="8443" maxThreads="150" SSLEnabled="true">
    <SSLHostConfig>
        <!--
          Let's Encrypt 인증서 경로를 지정합니다.
          Docker 환경에서는 이 파일들을 컨테이너 내부로 볼륨 마운트해야 합니다.
        -->
        <Certificate certificateKeyFile="conf/certs/privkey.pem"
                     certificateFile="conf/certs/fullchain.pem"
                     type="RSA" />
    </SSLHostConfig>
</Connector>
```

-   `certificateKeyFile`: 개인키 파일 (`privkey.pem`)의 경로입니다.
-   `certificateFile`: 인증서와 체인 인증서가 포함된 파일 (`fullchain.pem`)의 경로입니다.
-   Docker 환경에서는 `docker-compose.yml` 등을 통해 인증서 파일들을 Tomcat 컨테이너의 `conf/certs/`와 같은 경로에 마운트해야 합니다.

### 7.2. 2단계: 리버스 프록시가 Tomcat(HTTPS)으로 요청을 전달하도록 수정

프록시 서버가 Tomcat의 HTTPS 포트(예: 8443)로 요청을 전달하도록 `proxy_pass` (Nginx) 또는 `ProxyPass` (Apache) 설정을 변경합니다.

#### Nginx 설정 변경

`proxy_pass` 지시어의 주소를 `https://...`로 변경합니다.

```nginx
# /etc/nginx/sites-available/your-domain.com.conf
# ... (server 블록 및 ssl 설정은 동일) ...

location / {
    # Tomcat HTTPS 백엔드로 프록시
    # Docker 환경: proxy_pass https://tomcat:8443;
    proxy_pass https://10.0.0.5:8443;

    # ... (프록시 헤더 설정은 동일) ...
}
```

-   **백엔드 인증서 검증**: Nginx는 기본적으로 백엔드 서버(Tomcat)의 인증서를 검증합니다.
    -   **공인 인증서**: Tomcat에 Let's Encrypt와 같은 공인 인증서를 사용했다면 추가 설정이 필요 없습니다.
    -   **자체 서명 인증서**: Tomcat에 자체 서명 인증서를 사용한 경우, Nginx가 해당 인증서를 신뢰하도록 `proxy_ssl_trusted_certificate`를 설정해야 502 오류를 방지할 수 있습니다.
    ```nginx
    # Tomcat의 자체 서명 인증서(또는 CA 인증서)를 Nginx가 신뢰하도록 설정
    proxy_ssl_trusted_certificate /etc/nginx/certs/tomcat-self-signed.crt;
    
    # 개발 환경에서만 검증을 끌 수 있습니다 (보안상 비권장).
    # proxy_ssl_verify off;
    ```

#### Apache 설정 변경

`mod_ssl`의 `SSLProxyEngine`을 활성화하고 `ProxyPass` 지시어를 `https://...`로 변경합니다.

```apache
# /etc/apache2/sites-available/your-domain-ssl.conf
<VirtualHost *:443>
    ServerName your-domain.com
    # ... (SSLEngine on, SSLCertificateFile 등은 동일) ...

    # 백엔드 SSL 프록시 기능 활성화
    SSLProxyEngine on

    # Tomcat HTTPS 백엔드로 요청 전달
    ProxyPass / https://10.0.0.5:8443/
    ProxyPassReverse / https://10.0.0.5:8443/

    # 자체 서명 인증서 사용 시 신뢰 설정
    # SSLProxyCACertificateFile /path/to/tomcat-self-signed.crt

    # 개발 환경에서만 검증 비활성화
    # SSLProxyVerify none
</VirtualHost>
```

이처럼 End-to-End 암호화는 각 컴포넌트(Tomcat, Nginx/Apache)에 모두 SSL 설정을 적용해야 하므로 전체적인 아키텍처 이해가 중요합니다.

- [Configure SSL for Docker (Kendis Help Center)](http://help.kendis.io/en/articles/3382550-configure-ssl-for-docker)
- [SSL for docker apps (Docker Community Forums)](https://forums.docker.com/t/ssl-for-docker-apps/102903)
- [Let's Encrypt](https://letsencrypt.org/)
- [Certbot](https://certbot.eff.org/) 
- [Apache Module mod_proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html)
- [Tomcat Remote IP Valve](https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#Remote_IP_Valve) 