Tomcat 10.1.51 버전 Release Note 요약 및 정리
안녕하세요.
오늘 톰캣 릴리즈 노트 최신 업데이트 내용으로 소개하고자 합니다.
Tomcat 10.1.51 버전 Release 의 내용 정리 및 요약 입니다.

-
ClassLoader.getResource().getContent() 호출 실패 문제 추가 수정
- 개요
Tomcat이 웹 애플리케이션의 로딩 속도를 높이기 위해 내부적으로 JAR 파일을 열어 리소스를 읽고 캐싱한 뒤, 효율성을 위해 해당 JAR 파일의 핸들(파일 연결)을 닫아버리는 경우가 있었습니다.
그러나 이미 닫힌(Closed) 리소스에 접근하려 했기 때문에 URL은 존재하지만 실제 데이터를 긁어와야 하는 기반 스트림(Underlying Stream)이나 JAR 연결이 이미 종료되어 IOException 혹은 NullPointerException이 발생했습니다.
- 개선사항
리소스가 캐시되어 있더라도, 해당 리소스가 참조하는 JAR 파일의 연결 상태를 한 번 더 체크하도록 수정되었습니다. 그리고 여러 쓰레드가 동시에 같은 JAR 내 리소스를 캐싱하고 읽으려 할 때, 한 쓰레드가 사용 중인데 다른 쓰레드가 JAR를 닫아버리지 않도록 동기화 로직이 보강되었습니다. 또한 getContent() 호출이 예상되는 시점까지는 파일 시스템의 핸들을 안전하게 유지하거나, 필요 시 자동으로 재연결(Re-open)하는 메커니즘이 안정화되었습니다.
-
CsrfPreventionFilter에서 CSRF 토큰 중복 추가 문제 해결
- 개요
CsrfPreventionFilter는 보안을 위해 모든 내부 링크(L-R)나 폼 제출 URL에 CSRF 토큰(nonce)을 자동으로 추가합니다. 이 과정에서 HttpServletResponse.encodeURL() 메서드를 래핑(Wrapping)하여 사용합니다.
- 개선사항
URL을 인코딩하기 전에 해당 URL에 이미 유효한 CSRF 토큰 파라미터(기본값 org.apache.catalina.filters.CSRF_NONCE)가 존재하는지 정규식 또는 문자열 비교를 통해 검사합니다. 그리고 한 번의 요청 사이클 안에서 이미 처리가 완료된 응답 객체에 대해서는 중복 작업을 수행하지 않도록 플래그를 관리합니다. 또한 Redirect나 Forward 시 발생하던 토큰 중복 부여 문제를 방지하여 브라우저의 URL 표시줄이 지저분해지거나 서버에서 토큰 검증 시 오류가 날 확률을 줄였습니다.
-
HTTP/2에서 content-length 헤더 없을 때 파라미터 파싱 오류 수정
* HTTP/1.1 vs HTTP/2의 차이
– HTTP/1.1: 본문의 길이를 알기 위해 Content-Length 헤더를 쓰거나, 가변 길이일 경우 Transfer-Encoding: chunked를 사용합니다.
– HTTP/2: 메시지를 ‘프레임(Frame)’ 단위로 쪼개서 보냅니다. 마지막 데이터 프레임에 END_STREAM 플래그를 심어서 보낼 수 있기 때문에, 이론적으로는 Content-Length 헤더가 필수가 아닙니다.
- 개요
Tomcat의 기존 파라미터 파싱 로직 중 일부가 HTTP/1.1 스타일의 사고방식에 머물러 있었던 것이 원인이었습니다.
Tomcat의 파라미터 파서(Parameter Parser)가 요청을 처리하려는데, Content-Length 헤더가 보이지 않자 “읽을 본문 데이터가 없다”고 판단하거나, 본문을 읽기 위한 버퍼를 제대로 준비하지 못했습니다.
결과적으로 request.getParameter()를 호출했을 때, 실제 본문에 데이터가 가득 차 있음에도 불구하고 빈 값을 반환하거나 오류를 내뱉게 되었습니다.
- 개선사항
– END_STREAM 플래그 감지: 이제 Content-Length 헤더가 없더라도 HTTP/2 프로토콜 레벨에서 스트림이 아직 열려 있고 데이터 프레임이 들어오고 있다면, 이를 끝까지 기다려 파라미터를 추출합니다.
– RFC 준수 강화: HTTP/2 사양(RFC 9113)에 따라, 길이가 불분명한 상황에서도 스트림의 끝을 나타내는 플래그를 기준으로 본문 파싱을 완료하도록 수정되었습니다.
– 안정적인 버퍼링: 데이터가 여러 프레임으로 나뉘어 들어올 때, 모든 데이터가 수신될 때까지 파라미터 맵(Parameter Map) 생성을 보류하거나 순차적으로 결합하는 과정이 안정화되었습니다.
-
Rewrite Valve URL 인코딩 오류 시 예외 발생하도록 개선
- 개요
RewriteValve는 Apache의 mod_rewrite와 유사하게 URL을 변경하거나 리다이렉트하는 역할을 합니다. 이때 URL에 포함된 특수문자나 한글 등은 %XX 형태의 퍼센트 인코딩(Percent-encoding)이 되어 있어야 합니다.
예를 들어 이때 %G1(유효하지 않은 16진수)이나 끝이 잘린 %A 같은 잘못된 인코딩이 들어오는 경우, 이전 버전에서는 이런 오류를 발견해도 적당히 문자로 치환하거나 무시하고 프로세스를 진행했습니다.
잘못 해석된 URL이 애플리케이션 내부로 전달되어 경로 탐색(Path Traversal) 공격의 빌미가 되거나, 엉뚱한 리소스가 노출되는 보안 취약점이 발생할 가능성이 있었습니다.
- 개선사항
10.1.51 버전부터는 RewriteValve가 URL을 재작성하는 과정에서 인코딩 오류를 발견하면 즉시 예외를 발생시키고 요청을 중단합니다.
잘못된 인코딩이 감지되면 서버는 내부적으로 예외를 던지고, 클라이언트에게는 보통 400 Bad Request 또는 500 Internal Server Error를 응답하여 잘못된 요청이 처리되지 않도록 차단합니다.
-
Access Log 요청 종료 시간 기록 오류 수정
- 개요
Tomcat의 AccessLogValve에서 로그를 남기는 기본 메커니즘은 응답이 완료된 시점에 발생합니다. 하지만 설정에 따라 ‘요청을 받은 시간’과 ‘응답을 마친 시간’이 혼동되어 기록될 수 있습니다.
로그 패턴 문자열에서 %t(요청 시작 시간)만 사용하고 종료 시간을 계산하는 패턴을 누락한 경우 오류가 발생할 수 있습니다.
- 개선사항
Tomcat의 server.xml 파일 내 AccessLogValve 설정을 수정하여 정확한 종료 시점과 소요 시간을 기록할 수 있습니다.
$vi [TOMCAT_HOME]/conf/server.xml
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
|
패턴 추가 옵션:
- %t: 요청이 시작된 시간 (기본값)
- %D: 요청 처리에 걸린 시간 (밀리초, ms)
- %T: 요청 처리에 걸린 시간 (초, s)
- %F: 응답의 첫 번째 바이트를 보내는 데 걸린 시간
-
Authenticator에 ssoReauthenticationMode 속성 추가
- 개요
기존의 Tomcat SSO(Single Sign-On) 메커니즘에서는 사용자가 한 번 인증되면, 다른 애플리케이션에 접근할 때마다 매번 인증 과정을 거치지 않았습니다. 하지만 보안 요구사항이 강화되면서 다음과 같은 문제들이 제기되었습니다.
– 보안 취약점: SSO 세션이 유효하더라도 개별 애플리케이션 접근 시 최소한의 확인 절차가 필요한 경우가 있음.
– 성능과 보안의 트레이드오프: 매번 사용자 정보를 DB에서 다시 조회할 것인지, 아니면 캐시된 SSO 정보를 믿을 것인지에 대한 선택지가 필요해짐.
- 개선사항
이 속성은 AuthenticatorBase를 상속받는 모든 인증기(Basic, Digest, Form 등)에서 사용할 수 있으며, context.xml이나 server.xml의 <Valve> 설정에 추가합니다.
<Context>
|
이 속성과 관련하여 다음과 같은 오류 수정 및 개선이 이루어졌습니다.
– 인증 흐름 정교화: ssoReauthenticationMode가 full일 때, 캐시된 Principal(사용자 정보)이 실제 Realm의 데이터와 동기화되지 않던 로직을 수정.
– 비동기 처리와의 호환성: 비동기 서블릿 요청 시 재인증 로직이 누락되어 보안 구멍이 생기던 현상을 이 속성 제어를 통해 해결.
– Access Log 연동: 재인증이 발생하는 시점의 로그 기록이 누락되거나 종료 시간이 잘못 계산되던 버그(앞서 언급하신 내용과 연관됨)를 함께 정비함.
-
TLS 커스텀 커넥터 사용 시 NPE 방지
- 개요
Tomcat에서 TLS 통신을 처리할 때, Connector는 내부적으로 SSLHostConfig와 SSLContext를 생성하고 관리합니다. NPE가 발생했던 시나리오는 다음과 같습니다.
클라이언트가 TLS 핸드셰이크를 시작했으나, 서버 측의 커스텀 로직에서 인증서(Certificate)나 프로토콜 정보를 불러오는 과정에서 null을 반환하거나 아직 생성되지 않은 객체에 접근할 때 발생했습니다.
실행 중(Runtime)에 커넥터 설정을 변경할 때, 내부 Endpoint 객체가 재시작되는 과정에서 찰나의 순간에 참조값이 null이 되어 시스템이 뻗어버리는 현상이 있었습니다.
- 개선사항
– 객체 존재 여부 검증: TLS 핸드셰이크를 처리하는 AbstractJsseEndpoint 및 관련 클래스에서 SSLContext나 SSLHostConfig를 참조하기 전, 반드시 null 체크를 수행하도록 로직이 추가되었습니다.
– 기본값 제공: 커스텀 설정이 누락되었거나 로드에 실패하더라도 NPE를 던지는 대신, 안전한 기본 설정으로 폴백(Fallback)하거나 명확한 에러 메시지를 로그에 남기고 연결을 안전하게 종료하도록 변경되었습니다.
– 동기화 강화: 커넥터의 라이프사이클(시작, 중지, 재시작) 동안 설정 객체에 접근하는 스레드 간의 동기화를 개선하여, 초기화 중인 객체를 참조하지 못하도록 막았습니다.
# server.xml 설정 예시
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
|
-
OpenSSL 엔진 종료 시 충돌 방지
- 개요
OpenSSL은 C 언어로 작성된 네이티브 라이브러리이며, Java 환경인 Tomcat에서 이를 사용하려면 JNI(Java Native Interface)를 거쳐야 합니다. 이때 다음과 같은 자원 관리 문제가 발생했습니다.
– VM과 네이티브 자원의 수명 주기 불일치: Java의 가비지 컬렉터(GC)는 Java 객체만 관리할 뿐, OpenSSL이 점유한 네이티브 메모리는 직접 해제하지 못합니다.
– Double Free (이중 해제): Tomcat이 종료되면서 OpenSSL 엔진을 닫으려고 할 때, 이미 해제된 메모리 영역을 다시 참조하거나 해제하려고 시도하면서 시스템 충돌이 발생합니다.
– 종료 순서의 혼선: OpenSSL 엔진이 사용 중인 인증서나 암호화 키 자원이 먼저 삭제되었는데, 엔진 자체는 여전히 살아있어 해당 자원을 참조하려다 발생하는 오류입니다.
- 개선사항
– 상태 체크 강화 (State Checking): OpenSSL 엔진이 이미 종료되었는지 또는 종료 과정에 있는지 확인하는 플래그를 도입하여, 중복 종료 요청을 차단합니다.
– 참조 횟수 관리 (Reference Counting): 엔진을 사용하는 활성 연결(Connection)이 남아있는 동안에는 엔진이 먼저 종료되지 않도록 보장합니다.
– APR/Native 정지 로직 순서 변경: Tomcat의 각 구성 요소가 정지될 때, 네이티브 라이브러리(tcnative)를 가장 마지막에 안전하게 정리하도록 실행 순서를 재조정했습니다.
만약 서버 종료 시 Segmentation Fault나 Invalid Address 같은 메시지가 로그에 남는다면, 아래 사항을 확인해야 합니다.
APR/OpenSSL 설정 확인 (server.xml)
<Listener className="org.apache.catalina.core.AprLifecycleListener"
|
-
TLSv1.3 cipher 설정 변경
# TLSv1.3 과 TLSv1.2 이하 차이
- TLSv1.2 이하: 암호화 알고리즘, 키 교환 방식, 인증 방식 등을 하나로 묶어 복잡하게 정의합니다. (예: AES128-GCM-SHA256)
- TLSv1.3: 인증 및 키 교환 알고리즘이 암호화 제품군과 분리되어 훨씬 단순화되었습니다. (예: TLS_AES_128_GCM_SHA256)
- 개요
TLSv1.2이하 버전과 TLSv1.3 차이로 인해 SSE(Java 기본 SSL)와 OpenSSL(Tomcat Native)이 ciphers 속성을 해석하는 방식에 일관성이 없었고, 사용자가 설정한 값이 의도치 않게 무시되거나 적용되는 혼란이 있었습니다.
- 개선사항
ciphers 속성 같은 경우 TLSv1.2 및 그 이하 버전의 암호화 설정 전용인데, TLSv1.3용 Cipher를 포함시키면, Tomcat은 이를 무시하고 로그에 경고(Warning) 메시지를 출력하게 됩니다.
[변경전]
<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
|
[변경후]
<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
|
*요약 및 주의사항
- 경고 메시지 확인: 서버 구동 시 ciphers 속성에 TLSv1.3 관련 값이 들어있다는 경고가 뜬다면, 해당 값을 ciphersuite 속성으로 옮겨야 합니다.
- OpenSSL과의 일관성: 이전에는 OpenSSL 엔진 사용 시 ciphers에 적힌 TLSv1.3 설정이 그냥 무시되어 보안 취약점이 생길 수 있었으나, 이제는 경고를 통해 명확히 인지할 수 있습니다.
- 철자 주의: 속성 이름이 ciphersuite (단수형/붙여쓰기) 임에 유의하세요. (일부 버전이나 문서에 따라 cipherSuites 등 대소문자 확인이 필요할 수 있습니다.)
-
OCSP 지원 강화
# OCSP란?
– 인증서가 만료되지 않았더라도, 유출 등의 이유로 폐기(Revocation)되었는지 실시간으로 인증기관(CA)에 확인하는 프로토콜
- 개선사항
1) JSSE 기반 TLS에 OCSP 지원 추가
기존엔 OpenSSL 엔진(Tomcat Native)을 사용할 때만 OCSP 설정이 용이했습니다. 이제는 Java 표준 SSL인 JSSE(Java Secure Socket Extension를 사용하는 커넥터(NIO, NIO2)에서도 OCSP 체킹이 가능해져서 별도의 Native 라이브러리 없이도 Java 레벨에서 실시간 인증서 검증이 가능합니다.
2) 커넥터별 OCSP 설정 가능
– 서버 전체 설정이 아닌, 특정 <Connector>나 <SSLHostConfig> 별로 OCSP 사용 여부를 다르게 지정할 수 있습니다.
3) Soft-fail 지원 (기본값 :비활성화)
– Hard-fail: OCSP 서버가 응답하지 않으면 인증 실패로 간주하고 연결을 끊습니다. (보안성 높음, 가용성 낮음)
– Soft-fail: OCSP 서버 장애로 상태 확인이 불가능할 경우, 일단 인증된 것으로 간주하고 통신을 허용합니다. (가용성 높음, 보안성 타협)
4) OpenSSL OCSP 검증 플래그 설정 가능
– Tomcat Native를 통해 OpenSSL을 사용할 때, OpenSSL 내부의 세부 검증 플래그(예: OCSP_NOCHECK, OCSP_NOVERIFY 등)를 설정 파일에서 직접 제어할 수 있게 되었습니다.
그로인해 복잡한 인증서 체인 구조를 가진 환경에서 검증 단계를 세밀하게 조정할 수 있습니다.
* 설정예시
iv server.xml
<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
|
-
태그 처리 시 reuse() / release() 호출 누락 문제 수정
# 태그 풀링(Tag Pooling)이란?
JSP 페이지 내에 커스텀 태그가 많을 경우, 매번 태그 객체를 생성하고 버리는 것은 비용이 큽니다. 이를 방지하기 위해 Tomcat은 사용이 끝난 태그 객체를 보관했다가 재사용하는데, 이때 두 가지 핵심 메서드가 사용됩니다.
– release(): 태그 핸들러가 더 이상 필요하지 않을 때 호출되어, 할당된 자원을 완전히 해제합니다.
– reuse(): (일부 구현에서) 객체를 풀에서 꺼내 재사용하기 전, 상태를 초기화하기 위해 호출됩니다.
- 개요
특정 상황 에서 태그의 생명주기를 정확히 추적하지 못하는 버그가 있었습니다.
– 자원 누수: release()가 호출되지 않으면 태그가 잡고 있던 DB 커넥션, 파일 핸들, 또는 메모리가 JVM으로 반환되지 않고 계속 쌓입니다.
– 데이터 오염: reuse() 또는 초기화 로직이 누락된 채 객체가 재사용되면, 이전에 사용했던 사용자의 데이터나 설정값이 다음 사용자에게 노출되거나 로직 오류를 일으킵니다.
– 성능 저하: 풀링된 객체가 제대로 관리되지 않아 GC(Garbage Collection) 부하가 증가합니다.
- 개선사항
Try-Finally 블록 강화: 태그 처리 중 에러가 발생하더라도 반드시 release()가 호출되도록 finally 블록 내의 자원 정리 로직을 정교화했습니다.
생명주기 상태 머신 수정: 태그가 ‘사용 중’인지 ‘풀에 대기 중’인지 판별하는 내부 로직을 수정하여, 재사용 시점에 반드시 초기화가 일어나도록 강제했습니다.
태그 풀링 비활성화 옵션 안정화: 만약 풀링 자체에 문제가 있다면 org.apache.jasper.runtime.JspFactoryImpl.USE_POOL 옵션을 통해 풀링을 끌 수 있는데, 이 과정에서 발생하던 부작용들도 함께 수정되었습니다.
-
mapSendOptions에 사람이 읽기 쉬운 이름 사용 가능하도록 문서화
- 개요
Tomcat 클러스터링에서 노드 간 메시지를 주고받을 때, 메시지를 어떻게 보낼지(동기/비동기, 복제 방식 등) 결정하는 속성이 sendOptions입니다.
기존방식으론 8, 2, 6과 같은 정수(Integer) 값을 직접 입력해야 했습니다.
예 ) sendOptions=”6″ # (비동기 + 확인 모드의 조합)
숫자로만 되어 있어 문서 없이는 어떤 설정인지 알기 어렵고, 여러 옵션을 조합(Bitwise OR)할 때 계산 실수로 잘못된 설정이 적용될 위험이 컸습니다.
- 개선사항
| 이름 | 숫자 값 | 설명 |
| asynchronous | 8 | 메시지를 비동기로 전송합니다. |
| secure | 4 | 패킷을 암호화하여 전송합니다. |
| use_ack | 2 | 수신 노드로부터 확인 응답(ACK)을 기다립니다. |
| sync_ack | 1 | 동기 방식으로 확인 응답을 처리합니다. |
| multicast | 16 | 멀티캐스트 방식을 사용합니다. |
- 설정예시
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
|
* 업데이트 관련
Tomcat Native
1.x 최소/권장 버전: 1.3.4
2.x 최소/권장 버전: 2.0.12
Tomcat Native 자체도 2.0.12로 업데이트
🔹 내부 라이브러리 업데이트
Commons Pool → 2.13.1
Commons DBCP → 2.14.0
Commons Daemon → 1.5.1
ByteBuddy → 1.18.3
UnboundID → 7.0.4
Checkstyle → 12.3.1
이번 릴리즈 또한 운영중심의 편의성을 위한 업데이트라고 볼 수 있겠네요.
글 읽어 주셔서 감사합니다!
자유롭게 댓글을 달아주세요! 언제나 환영합니다.
기타 문의: info@neoclova.co.kr
네오클로바 기술블로그 홈 바로가기: https://neoclova.net
네오클로바 홈페이지: http://neoclova.co.kr
