2023. 5. 19. 15:48ㆍSpring Framework/Web on Servlet Stack
Spring Web 모듈 필터 개요
Spring Web 모듈은 서블릿 기반 애플리케이션에서 웹 요청을 처리하는 데 유용한 여러 필터를 제공합니다. 이 필터들은 Form 데이터 처리, 전달된 헤더 관리, 얕은 ETag 캐싱, CORS(교차 출처 리소스 공유) 처리를 위한 기능을 제공합니다. 각 필터의 역할에 대해 자세히 살펴보겠습니다.
Form Data 필터
디폴트로 브라우저에서는 HTTP GET
또는 POST
메서드를 통해서만 폼 데이터를 전송할 수 있습니다. 하지만 Non-브라우저 클라이언트(예: REST 클라이언트)는 다른 HTTP 메서드인 PUT
, PATCH
, 또는 DELETE
를 사용하여 폼 데이터를 전송할 수 있습니다. Servlet API는 POST
요청에 대해서만 ServletRequest.getParameter*()
메서드를 통해 폼 데이터에 접근할 수 있도록 제한하고 있기 때문에, PUT
, PATCH
, 또는 DELETE
요청의 폼 데이터는 기본적으로 접근할 수 없습니다.
이를 해결하기 위해 Spring Web 모듈은 FormContentFilter를 제공합니다. 이 필터는 application/x-www-form-urlencoded
콘텐츠 유형의 PUT
, PATCH
, DELETE
요청을 가로채어 요청 본문에서 폼 데이터를 읽고, ServletRequest
를 래핑하여 ServletRequest.getParameter*()
메서드를 통해 폼 데이터를 접근할 수 있도록 합니다. 이는 POST
요청에서 폼 데이터를 처리하는 방식과 유사합니다.
사용 예:
PUT
,PATCH
,DELETE
메서드에서 폼 제출을 지원해야 할 때 FormContentFilter는 폼 필드를POST
요청과 동일하게 접근할 수 있도록 보장합니다.
Forwarded Headers 필터
여러 프록시(예: 로드 밸런서 또는 리버스 프록시)를 거치는 요청의 경우, 원본 요청의 호스트, 포트 및 스킴이 변경될 수 있습니다. 이로 인해 서버가 클라이언트 요청에 대해 링크나 리디렉션을 생성할 때 문제가 발생할 수 있습니다. 서버는 클라이언트가 본래 요청한 내용과 다른 정보로 처리할 수 있기 때문입니다.
Forwarded HTTP 헤더:
- RFC 7239는 원본 요청에 대한 정보를 전달하기 위해
Forwarded
라는 표준 헤더를 정의합니다. X-Forwarded-Host
,X-Forwarded-Port
,X-Forwarded-Proto
,X-Forwarded-Ssl
,X-Forwarded-Prefix
와 같은 표준이 아닌 헤더도 많이 사용됩니다.
주요 헤더:
- X-Forwarded-Host: 원본 호스트를 전달합니다.
- X-Forwarded-Port: 원본 포트를 전달합니다.
- X-Forwarded-Proto: 원본 프로토콜(예:
http
또는https
)을 전달합니다. - X-Forwarded-Ssl: SSL 사용 여부(
on
또는off
)를 전달합니다. - X-Forwarded-Prefix: 원본 URL 경로 접두어를 전달합니다.
ForwardedHeaderFilter:
Spring Web은 ForwardedHeaderFilter를 제공하며, 이 필터는 다음과 같은 역할을 합니다:
- 전달된 헤더에 따라 요청의
host
,port
,scheme
을 조정합니다. - 요청을 수정한 후, 해당 헤더를 제거하여 이후 처리에 영향을 미치지 않도록 합니다.
리버스 프록시를 사용할 때 ForwardedHeaderFilter는 서버가 원본 클라이언트 요청 정보와 일관되게 요청을 처리할 수 있도록 해줍니다.
보안 고려 사항:
X-Forwarded-Host
와 같은 헤더는 스푸핑될 수 있으므로, 신뢰할 수 있는 프록시만 이러한 헤더를 추가 또는 제거할 수 있도록 해야 합니다. 그렇지 않으면 악의적인 클라이언트가 헤더를 조작하여 애플리케이션을 속일 수 있습니다.removeOnly=true
로 ForwardedHeaderFilter를 구성하여 헤더를 사용하는 대신 제거만 할 수도 있습니다.
Shallow ETag 필터
ETag(엔터티 태그)는 동일한 데이터를 여러 번 전송하지 않도록 돕는 웹 캐싱 메커니즘입니다. Spring Web의 ShallowEtagHeaderFilter는 웹 응답에 대한 얕은 ETag를 생성하는 데 사용됩니다.
작동 방식:
- 필터는 응답 내용을 캡처하고 MD5 해시를 계산한 후, 이를 ETag 헤더로 설정합니다.
- 이후 클라이언트가 동일한 ETag 값을 포함한
If-None-Match
헤더를 제공하면, 서버는304 Not Modified
응답을 반환하여 대역폭을 절약할 수 있습니다.
얕은 ETag와 깊은 ETag:
- "얕은" ETag는 요청 세부 사항을 고려하지 않고 응답 내용만을 기반으로 ETag를 생성하는 것을 의미합니다. "깊은" ETag는 더 복잡한 체크를 포함할 수 있습니다.
- 이 필터는 RFC 7232에서 정의된 대로 "약한" ETag(예:
W/"etag-value"
)를 작성하는 기능을 지원합니다.
제한 사항:
- 이 필터는 CPU 자원을 절약하지는 않습니다. 응답이 완전히 생성된 후에야 ETag를 계산할 수 있기 때문입니다.
CORS (교차 출처 리소스 공유)
CORS는 웹 서버의 리소스를 다른 도메인에서 요청할 수 있도록 하는 메커니즘으로, 현대 웹 애플리케이션에서 필수적입니다. Spring은 다음과 같은 방법으로 CORS에 대한 세밀한 지원을 제공합니다:
- 어노테이션: Spring MVC 컨트롤러에서 직접 CORS를 구성할 수 있습니다.
- CorsFilter: CORS 정책을 처리하는 필터로, Spring Security의 필터 체인보다 앞서 배치되어야 합니다.
이를 통해 어떤 출처(origin)가 리소스에 접근할 수 있는지, 어떤 HTTP 메서드와 헤더가 허용되는지, 쿠키와 같은 자격 증명이 요청과 함께 전송될 수 있는지를 정의할 수 있습니다.
Spring Security 통합:
Spring Security를 사용하는 경우, CorsFilter를 사용하여 CORS를 관리하는 것이 좋습니다. Spring Security도 자체적으로 CORS 처리를 구현하고 있기 때문입니다.
Dispatcher Types 및 비동기 요청
ForwardedHeaderFilter와 ShallowEtagHeaderFilter와 같은 필터들이 비동기 요청을 올바르게 처리하려면 DispatcherType.ASYNC
와 함께 등록해야 합니다. 이렇게 하면 필터가 비동기 디스패치를 사용하는 요청을 처리할 수 있습니다. 비동기 요청 외에도 에러 처리를 위해 필터들이 DispatcherType.ERROR
와 함께 등록되어야 에러 디스패치 시나리오도 처리할 수 있습니다.
AbstractAnnotationConfigDispatcherServletInitializer
를 사용하는 경우, 모든 필터는 자동으로 모든 디스패처 타입(REQUEST
, ASYNC
, ERROR
등)으로 등록됩니다. 하지만 필터를 수동으로 등록하는 경우(Spring Boot에서 FilterRegistrationBean
을 사용하는 경우), DispatcherType.ASYNC
와 DispatcherType.ERROR
를 명시적으로 포함해야 합니다.
Summary
- FormContentFilter:
PUT
,PATCH
,DELETE
요청에서 폼 데이터를POST
요청과 동일하게 접근할 수 있게 합니다. - ForwardedHeaderFilter:
Forwarded
및X-Forwarded-*
와 같은 헤더를 처리하여 올바른 호스트, 포트, 스킴 정보를 유지합니다. - ShallowEtagHeaderFilter: 얕은 ETag 기반 캐싱을 제공하여 네트워크 효율성을 향상시킵니다.
- CorsFilter: 교차 출처 요청에 대한 세밀한 CORS 구성을 가능하게 합니다.
이 필터들은 폼 데이터 제출, 캐싱, 리버스 프록시 통합, CORS 처리 등의 다양한 시나리오를 다룰 수 있도록 하여 웹 애플리케이션의 유연성과 성능을 유지할 수 있게 해줍니다.
다음은 Spring Boot 기반의 데모 웹 애플리케이션을 구현하는 코드입니다. 이 애플리케이션은 Spring Web 모듈에서 제공하는 주요 필터(FormContentFilter
, ForwardedHeaderFilter
, ShallowEtagHeaderFilter
, CorsFilter
)를 설정하고 작동하는 예제입니다.
1. Spring Boot 프로젝트 설정
pom.xml
(Maven 사용 시)
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Security (CORS 필터와 함께 활용 가능) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot DevTools (옵션) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Lombok (선택사항) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
2. Spring Boot 애플리케이션 기본 설정
Application.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. Controller 구현
FormController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/form")
public class FormController {
@PostMapping
public String handlePost(@RequestParam Map<String, String> formData) {
return "POST 요청 처리됨: " + formData;
}
@PutMapping
public String handlePut(@RequestParam Map<String, String> formData) {
return "PUT 요청 처리됨: " + formData;
}
@PatchMapping
public String handlePatch(@RequestParam Map<String, String> formData) {
return "PATCH 요청 처리됨: " + formData;
}
@DeleteMapping
public String handleDelete(@RequestParam Map<String, String> formData) {
return "DELETE 요청 처리됨: " + formData;
}
}
✅ PUT
, PATCH
, DELETE
요청에서도 @RequestParam
을 통해 폼 데이터를 받을 수 있도록 FormContentFilter
가 필요합니다.
4. Filter 설정
WebConfig.java
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.FormContentFilter;
import org.springframework.web.filter.ForwardedHeaderFilter;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
public class WebConfig {
// FormContentFilter 등록 (PUT, PATCH, DELETE에서도 form-data 처리 가능)
@Bean
public FormContentFilter formContentFilter() {
return new FormContentFilter();
}
// ForwardedHeaderFilter 등록 (프록시 환경에서 올바른 요청 정보 유지)
@Bean
public ForwardedHeaderFilter forwardedHeaderFilter() {
return new ForwardedHeaderFilter();
}
// ShallowEtagHeaderFilter 등록 (ETag를 사용하여 응답 캐싱)
@Bean
public ShallowEtagHeaderFilter shallowEtagHeaderFilter() {
return new ShallowEtagHeaderFilter();
}
// CORS 설정을 위한 CorsFilter 등록
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("*")); // 모든 도메인 허용
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
5. 테스트용 CORS 컨트롤러
CorsTestController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/cors")
public class CorsTestController {
@GetMapping
public String testCors(@RequestHeader Map<String, String> headers) {
return "CORS 요청 성공: " + headers;
}
}
✅ GET /cors
요청 시 클라이언트가 CORS 관련 헤더를 전달하는지 확인할 수 있습니다.
6. 애플리케이션 실행 후 테스트
1️⃣ 폼 데이터 처리 테스트 (FormContentFilter)
PUT
, PATCH
, DELETE
요청에서 application/x-www-form-urlencoded
데이터를 처리하는지 확인:
curl -X PUT http://localhost:8080/form -d "name=John&age=30" -H "Content-Type: application/x-www-form-urlencoded"
2️⃣ 프록시 헤더 처리 테스트 (ForwardedHeaderFilter)
프록시 환경에서 요청이 올바르게 처리되는지 확인:
curl -H "X-Forwarded-Proto: https" -H "X-Forwarded-Host: example.com" http://localhost:8080/cors
3️⃣ ETag 캐싱 테스트 (ShallowEtagHeaderFilter)
같은 요청을 여러 번 보내면 ETag
가 생성되고, If-None-Match
헤더를 포함하면 304 응답을 받을 수 있는지 확인:
curl -v -H "If-None-Match: W/\"123456\"" http://localhost:8080/cors
4️⃣ CORS 테스트
서버가 CORS 요청을 올바르게 허용하는지 확인:
curl -H "Origin: http://example.com" -H "Access-Control-Request-Method: GET" --verbose http://localhost:8080/cors
7. Summary
✔ FormContentFilter: PUT
, PATCH
, DELETE
요청에서도 application/x-www-form-urlencoded
폼 데이터 접근 가능
✔ ForwardedHeaderFilter: 프록시 서버를 거친 요청에서 올바른 host
, port
, scheme
설정 유지
✔ ShallowEtagHeaderFilter: 응답에 ETag
헤더를 추가하여 캐싱 최적화
✔ CorsFilter: 교차 출처 요청을 허용하고 클라이언트가 서버 리소스를 요청할 수 있도록 구성
이렇게 구성하면 Spring Boot 기반 웹 애플리케이션에서 주요 필터들을 활용하여 다양한 환경에서의 요청을 효과적으로 처리할 수 있습니다. 🚀
출처 : https://docs.spring.io/spring-framework/reference/web/webmvc/filters.html
'Spring Framework > Web on Servlet Stack' 카테고리의 다른 글
Annotated Controllers[3] - @CookieValue (0) | 2024.10.09 |
---|---|
@RequestHeader (0) | 2024.10.09 |
Annotated Controllers[1] (1) | 2024.10.06 |
WebDataBinder (0) | 2023.05.01 |
Servlet (0) | 2023.04.17 |