web.xml과 컨텍스트 설정
web.xml은 Java 웹 애플리케이션의 배포 설명자(Deployment Descriptor)로, 서블릿 매핑, 필터, 리스너, 세션 설정 등을 정의합니다. Tomcat에는 글로벌 web.xml과 각 애플리케이션의 web.xml 두 가지가 있습니다.
web.xml 계층 구조
$CATALINA_HOME/conf/web.xml ← 글로벌 기본값 (Tomcat 제공)
↓ 덮어쓰기 / 병합
$APP/WEB-INF/web.xml ← 애플리케이션별 설정
글로벌 web.xml은 Tomcat이 관리하므로 직접 수정하지 않는 것이 좋습니다. 대부분의 커스터마이징은 애플리케이션의 WEB-INF/web.xml에서 합니다.
글로벌 web.xml 주요 설정
$CATALINA_HOME/conf/web.xml에는 Tomcat의 기본 서블릿과 JSP 서블릿이 정의되어 있습니다.
<!-- DefaultServlet — 정적 파일 서빙 -->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value> <!-- 디렉터리 목록 비활성화 -->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- JSP 서블릿 -->
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value> <!-- X-Powered-By 헤더 숨김 -->
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
애플리케이션 web.xml 구조
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<display-name>My Application</display-name>
<description>My Web Application</description>
<!-- 전역 파라미터 -->
<context-param>
<param-name>applicationEnvironment</param-name>
<param-value>production</param-value>
</context-param>
</web-app>
버전 주의: Tomcat 10부터
jakarta.ee네임스페이스를 사용합니다. Tomcat 9 이하는javax.servlet네임스페이스입니다.
서블릿 등록과 매핑
<!-- 서블릿 정의 -->
<servlet>
<servlet-name>ApiServlet</servlet-name>
<servlet-class>com.example.api.ApiServlet</servlet-class>
<init-param>
<param-name>maxRequestSize</param-name>
<param-value>10485760</param-value> <!-- 10MB -->
</init-param>
<load-on-startup>1</load-on-startup> <!-- 1 이상: 시작 시 즉시 로드 -->
<async-supported>true</async-supported>
</servlet>
<!-- URL 매핑 -->
<servlet-mapping>
<servlet-name>ApiServlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<!-- Spring MVC DispatcherServlet 예시 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/mvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
필터(Filter) 등록
<!-- 인코딩 필터 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- CORS 필터 -->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>https://example.com,https://www.example.com</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Content-Type,Authorization,X-Requested-With</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
세션 설정
<!-- 세션 설정 -->
<session-config>
<session-timeout>30</session-timeout> <!-- 분 단위 -->
<cookie-config>
<name>JSESSIONID</name>
<http-only>true</http-only> <!-- JavaScript 접근 차단 -->
<secure>true</secure> <!-- HTTPS에서만 전송 -->
<same-site>Strict</same-site> <!-- CSRF 방지 (Servlet 6.0+) -->
</cookie-config>
<tracking-mode>COOKIE</tracking-mode> <!-- URL 세션 추적 비활성화 -->
</session-config>
보안:
<tracking-mode>COOKIE</tracking-mode>를 명시하면 URL에jsessionid=xxx가 노출되는 것을 방지합니다. URL 세션 추적은 세션 고정 공격(Session Fixation)에 취약합니다.
JNDI 리소스 참조
web.xml에서 JNDI 리소스를 참조할 수 있습니다.
<!-- 데이터소스 참조 선언 -->
<resource-ref>
<description>Production Database</description>
<res-ref-name>jdbc/myDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
실제 데이터소스는 context.xml 또는 server.xml의 GlobalNamingResources에 정의합니다.
// 애플리케이션 코드에서 JNDI 룩업
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/myDB");
Connection conn = ds.getConnection();
에러 페이지 설정
<!-- HTTP 상태 코드별 에러 페이지 -->
<error-page>
<error-code>404</error-code>
<location>/error/404.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.jsp</location>
</error-page>
<!-- 예외 타입별 에러 페이지 -->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error/general.jsp</location>
</error-page>
<error-page>
<exception-type>java.sql.SQLException</exception-type>
<location>/error/db-error.jsp</location>
</error-page>
보안 제약 설정
<!-- 특정 URL에 인증 요구 -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/admin/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<!-- 폼 기반 로그인 -->
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login-error.jsp</form-error-page>
</form-login-config>
</login-config>
<!-- 역할 정의 -->
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
context.xml — 컨텍스트 전용 설정
WEB-INF/context.xml 또는 conf/context.xml에서 Tomcat 특화 컨텍스트 설정을 합니다.
<!-- WEB-INF/context.xml -->
<Context>
<!-- 세션 지속성 (재시작 후에도 세션 유지) -->
<Manager className="org.apache.catalina.session.PersistentManager"
maxIdleBackup="1">
<Store className="org.apache.catalina.session.FileStore"
directory="/var/tomcat-sessions"/>
</Manager>
<!-- 리소스 캐시 설정 -->
<Resources cachingAllowed="true"
cacheMaxSize="100000"/>
<!-- 와치독 (클래스 변경 감지 — 개발용) -->
<!-- <WatchedResource>WEB-INF/web.xml</WatchedResource> -->
<!-- <WatchedResource>WEB-INF/lib/*.jar</WatchedResource> -->
</Context>
Summary
| 파일 | 위치 | 역할 |
|---|---|---|
| 글로벌 web.xml | $CATALINA_HOME/conf/web.xml | 기본 서블릿, 기본 MIME 타입 |
| 앱 web.xml | $APP/WEB-INF/web.xml | 서블릿, 필터, 세션, 보안 설정 |
| Context XML | conf/Catalina/localhost/앱.xml | 앱별 Tomcat 설정, 데이터소스 |
| context.xml | conf/context.xml 또는 WEB-INF/context.xml | 세션 지속성, 리소스 캐시 |
| 세션 보안 | <tracking-mode>COOKIE</tracking-mode> | URL 세션 노출 방지 |
| JNDI 데이터소스 | Context XML + <resource-ref> | DB 연결 풀 외부화 |