1. The “Spring Web MVC Framework”
- Spring Web MVC는 풍부한 기능을 가진 "model-view-controller" 웹 프레임워크 입니다.
Controller
- @Controller (or @RestController) 빈으로 들어오는 요청을 처리하도록 돕습니다
- @Controller 빈의 메서드는 @RequestMapping을 사용하여 요청을 URI 기반으로 매핑할 수 있습니다.
ConversionService
- 객체의 타입을 다른 타입으로 변환하는 인터페이스입니다.
항목 | ApplicationConversionService | @ConfigurationProperties 바인딩 |
Spring Web MVC
|
역할 | 중앙 변환 서비스 | 프로퍼티 → 객체 바인딩 시 사용 |
요청 파라미터
→ 컨트롤러 파라미터 변환 |
기본 지원 타입 | Period, Duration, DataSize 등 (자동 설정에서 관련 컨버터 추가) |
Period, Duration, DataSize 등 | ❌ 포함되지 않음 |
기본 위임 대상 | 내부 컨버터/포맷터 | DateTimeFormatter 특수 Formatter (DurationFormatter 등) |
DateTimeFormatter ConversionService |
등록 방식 | 직접 등록 가능 - addConverter() - addFormatter() |
ConfigurationPropertiesBindingPostProcessor가 DataBinder에 위임 |
WebMvcConfigurer
|
자동 등록 포맷터 | ✅ 지원 | ✅ 지원 | ❌ 별도 등록 필요 |
커스터마이징 방법 | 직접 수정 | @ConfigurationPropertiesBinding 커스터마이징 | WebMvcConfigurer |
관련 프로퍼티 | 없음 | 없음 |
spring.mvc.format.* → DateTimeFormatter에 위임됨
|
예제) ApplicationConversionService
더보기

@Configuration
public class ConversionServiceConfig {
@Bean
public ConversionService conversionService() {
ApplicationConversionService service = new ApplicationConversionService();
service.addConverter(new StringToCustomTypeconverter());
return service;
}
public static class StringToCustomTypeconverter implements Converter<String, CustomType> {
@Override
public CustomType convert(String source) {
return new CustomType(source.toUpperCase());
}
}
public static class CustomType {
private final String value;
public CustomType(String value) {
this.value = value;
}
@Override
public String toString() {
return "컨버팅 성공! 값은 " + value + " 입니다.";
}
}
}
@GetMapping("/conversion-service")
public String conversionService() {
ConversionServiceConfig.CustomType hello = conversionService.convert("hello", ConversionServiceConfig.CustomType.class);
return hello.toString();
}

예제) @ConfigurationProperties
더보기
app:
timeout: 10s
size: 512MB
retention: P1Y2M3D
type: test
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private Duration timeout;
private DataSize size;
private Period retention;
private ConversionServiceConfig.CustomType type;
}
@Bean
@ConfigurationPropertiesBinding
public Converter<String, CustomType> stringToCustomTypeconverter() {
return new Converter<>() {
@Override
public CustomType convert(String source) {
return new CustomType(source.toUpperCase());
}
};
}
@GetMapping("/configuration-properties")
public String configurationProperties() {
System.out.println(appProperties.getTimeout());
System.out.println(appProperties.getRetention());
System.out.println(appProperties.getSize().toKilobytes());
System.out.println(appProperties.getType());
return "ok";
}
예제) Spring Web MVC
더보기
public class DurationFormatter implements Formatter<Duration> {
@Override
public Duration parse(String text, Locale locale) throws ParseException {
return Duration.parse(text);
}
@Override
public String print(Duration object, Locale locale) {
return object.toString();
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DurationFormatter());
}
}
@GetMapping("/spring-web-mvc")
public String springWebMvc(@RequestParam("timeout") Duration timeout) {
System.out.println(timeout.toMinutes());
return "ok";
}
예제) Spring Web MVC - LocalDateTime
더보기
spring:
mvc:
format:
date-time: iso
@GetMapping("/spring-web-mvc")
public String springWebMvc(@RequestParam("time") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime time) {
System.out.println(time);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(formatter.format(time));
return "ok";
}
HttpMessageConverter
항목 | 설명 |
역할 |
HTTP Request → Java 객체
Java 객체 → HTTP Response 로 직렬화/역직렬화 처리 |
사용되는 구현체 (기본) |
- MappingJackson2HttpMessageConverter → JSON 처리
- MappingJackson2XmlHttpMessageConverter → XML 처리 (jackson-dataformat-xml 필요) - Jaxb2RootElementHttpMessageConverter → XML 처리 (JAXB 기반) |
XML 처리 방식 |
- jackson-dataformat-xml 의존성 없으면 → JAXB 사용
- 있으면 → Jackson XML 사용 |
커스터마이징 방법 ① |
WebMvcConfigurer를 구현한 설정 클래스에서
configureMessageConverters(List<HttpMessageConverter<?>> converters) 또는 extendMessageConverters(List<HttpMessageConverter<?>> converters) 오버라이드 |
커스터마이징 방법 ② |
HttpMessageConverters를 Bean으로 등록하면
Spring Boot가 자동 구성 시 우선 적용 |
자동 구성 관련 |
Spring Boot는 기본적으로 여러 MessageConverter들을 등록함
WebMvcConfigurer#configureMessageConverters는 직접 설정 (자동 구성이 무시될 수 있음) |
예제) MappingJackson2XmlHttpMessageConverter
더보기
dependencies {
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
}
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@JacksonXmlRootElement(localName = "person") // XML root element 이름 설정
public class Person {
private String name;
private int age;
// 기본 생성자 필수
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter/Setter 필수
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
@RestController
@RequestMapping("/api")
public class XmlController {
@GetMapping(value = "/person", produces = "application/xml")
public Person getPerson() {
return new Person("이름", 30);
}
}
예제) MappingJackson2HttpMessageConverter 커스터마이징
더보기
@Configuration
public class MessageConverterConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); // 빈 값 + NULL 인 필드를 JSON 출력에서 제외
mapper.setDateFormat(new SimpleDateFormat("yyyy/MM/dd")); // 날짜(Date, Calendar) -> 문자열 포맷
JavaTimeModule module = new JavaTimeModule();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); // 날짜(LocalDate, LocalDateTime) -> 문자열 포맷
mapper.registerModule(module);
return mapper;
}
@Bean
public HttpMessageConverters customConverters() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper());
return new HttpMessageConverters(converter);
}
}
java.time.* (LocalDate, LocalDateTime...) 을 포맷팅하고 싶으면 아래 모듈 추가 필요
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
MessageCodesResolver
- binding 실패 시, 실패 메시지를 위한 에러코드를 만드는 역할을 담당합니다.
- 에러코드 생성 전략을 spring.mvc.message-codes.resolver-format를 통해 선택할 수 있습니다
옵션 | 설명 |
에러 코드 형식 예시
|
PREFIX_ERROR_CODE | 에러 코드를 errorCode.objectname.field 순으로 생성 |
errorCode.user.name
errorCode.user.email |
POSTFIX_ERROR_CODE | 에러 코드를 field.objectname.errorCode 순으로 생성 |
name.user.errorCode
email.user.errorCode |
예제) PREFIX_ERROR_CODE
더보기
spring:
mvc:
message-codes-resolver-format: prefix_error_code
public class User {
@NotBlank(message = "name.blank")
private String name;
@NotBlank(message = "email.blank")
private String email;
// getter, setter..
}
- NotBlank.name.blank
- NotBlank.email.blank
Static Content
항목 | 설명 |
기본 디렉토리 | /static |
대체 디렉토리 |
/public, /resources, /META-INF/resources
|
정적 리소스 요청 경로 패턴 설정 |
spring.mvc.static-path-pattern
→ 기본값: /** → 예시: /static/** |
정적 리소스 위치 설정 |
spring.web.resources.static-locations
→ 기본값: classpath:/static/, classpath:/public/ 등 → 커스텀 가능 |
리소스 서빙 핸들러 |
ResourceHttpRequestHandler
→ 내부적으로 정적 파일을 처리하는 역할 담당 |
Path Matching
항목 | 설명 |
요청 매핑 |
HTTP 요청의 URL을 컨트롤러에 매핑
|
접미사 패턴 매칭 | ❌ 사용하지 않음 |
핸들러 없음 |
기본적으로 404 Not Found 반환
|
예외 설정 |
NoHandlerFoundException 발생시키려면:
spring.mvc.throw-exception-if-no-handler-found=true |
정적 컨텐츠 제공 |
기본 매핑: /**
컨트롤러가 없을 경우 정적 리소스로 응답 |
정적 리소스 설정 |
정적 경로 설정: spring.mvc.static-path-pattern=/resources/**
정적 리소스 제공 비활성화: spring.web.resources.add-mappings=false |
매칭 전략 설정 |
spring.mvc.pathmatch.matching-strategy 통해 설정 가능
|
Path Matching 전략 비교
구분 | PathPatternParser |
AntPathMatcher
|
도입 버전 | Spring 5.3부터 | 기본 전략 |
성능 | ✅ 더 효율적 |
⚠️ 대규모에서 성능 저하 가능
|
유연성 | 제한적 | 유연함 |
패턴 분석 | 엄격한 패턴 분석 | 덜 엄격함 |
허용되지 않는 패턴 | / {varName:.*} 지원 안 함 | 지원 |
DispatcherServlet 경로 접두사와 호환성 | ❌ 호환 안 됨 | ✅ 호환 |
기본 설정 여부 | ❌ 기본 아님 (설정 필요) | ✅ 기본 설정 |
예제) PathPatternParser
더보기
spring:
mvc:
pathmatch:
matching-strategy: path_pattern_parser # PathPatternParser 사용
throw-exception-if-no-handler-found: true # 핸들러가 없을 경우 404 Not Found 응답
web:
resources:
add-mappings: false # 정적 리소스 제공 비활성화
@GetMapping("/hi/{name}")
public String hi(@PathVariable String name) {
return "hi, " + name;
}
예제) AntPathMatcher
더보기
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
@GetMapping("/hi/*.log")
public String log() {
return "hi, log";
}
Content Negotiation
- 클라이언트가 어떤 형식의 데이터를 받고 싶은지 서버에 요청하고, 서버는 그에 맞게 응답하는 기능
방식 | 설명 | Spring 설정 필요 여부 | 권장 여부 |
Accept Header | HTTP 헤더에 원하는 형식을 명시 | ❌ (기본 지원) | ✅ 권장 |
Query Parameter | URL에 형식을 쿼리로 명시 | ✅ favor-parameter: true 설정 필요 | ⚠️ 테스트용/유연한 API 설계 시 사용 |
설정 키 | 설명 |
spring.mvc.contentnegotiation.favor-parameter |
쿼리 파라미터로 포맷 지정 허용 여부
|
spring.mvc.contentnegotiation.parameter-name |
포맷 지정 파라미터 이름 (기본: format)
|
spring.mvc.contentnegotiation.media-types |
포맷별 MIME 타입 지정
|
spring.mvc.contentnegotiation.favor-path-extension |
URL 확장자 사용 여부 (기본 false)
|
예제) Accept Header
더보기
spring:
mvc:
contentnegotiation:
favor-parameter: false # 쿼리 파라미터 방식 비활성화
favor-accept-header: true # Accept 헤더 방식 활성화
@RestController
@RequestMapping("/api")
public class ContentController {
@GetMapping(value = "/user", produces = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE
})
public User getUser() {
return new User("Noah", 25);
}
public record User(String name, int age) {}
}
GET localhost:8080/acceptheader
Accept: application/json
예제) Query Parameter
더보기
spring:
mvc:
contentnegotiation:
favor-parameter: true # 쿼리 파라미터 방식 활성화
parameter-name: format # 쿼리 파라미터 이름을 'format'으로 설정
media-types:
json: application/json # 'json' 값은 application/json 형식으로 응답
xml: application/xml # 'xml' 값은 application/xml 형식으로 응답
@RestController
@RequestMapping("/api")
public class ContentController {
@GetMapping("/user")
public User getUser() {
return new User("Noah", 25);
}
public record User(String name, int age) {}
}
GET /api/user?format=json
GET /api/user?format=xml
ConfigurableWebBindingInitializer
- WebBindingInitializer 인터페이스를 구현하는 클래스입니다.
- 컨트롤러 메소드에서 HTTP 요청의 매개변수를 바인딩 하기 위해 WebDataBinder 초기화를 수행합니다.
항목 | 설명 | 주요 역할/기능 |
WebDataBinder | 웹 요청 데이터를 Java Bean에 바인딩하는 핵심 객체 |
🔹 요청 파라미터 바인딩
🔹 타입 변환 🔹 유효성 검사 |
WebBindingInitializer | 각 요청에 대해 WebDataBinder를 초기화하는 인터페이스 |
🔹 PropertyEditor, Validator, Converter 등록
🔹 공통 바인딩 설정 재사용 |
CORS Support
항목 | 설명 |
정의 |
브라우저의 동일 출처 정책(Same-Origin Policy)을 완화하여, 다른 출처의 요청을 허용하기 위한 정책
|
사용 목적 |
보안을 유지하면서도 다른 도메인에서의 리소스 접근을 허용할 수 있도록 함
|
세부 제어 항목 |
- 허용 Origin (origin)
- 허용 HTTP 메소드 (method) - 허용 헤더 (header) |
설정 범위 |
- 핸들러(컨트롤러) 단위 설정
- 전역(Global) 설정 가능 |
예시) @CorsOrigin
더보기
// 컨트롤러에서 개별 설정
@CrossOrigin(origins = "https://example.com", methods = {RequestMethod.GET, RequestMethod.POST})
@GetMapping("/api/data")
public String getData() {
return "data";
}
예시) WebMvcConfigurer
더보기
// 전역 설정
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("https://example.com")
.allowedMethods("GET", "POST")
.allowedHeaders("*");
}
}
2. Embedded Servlet Container Support
Filter
항목 | 설명 |
주의 사항 |
HTTP 요청의 InputStream은 한 번만 읽을 수 있음
→ 본문을 먼저 읽는 필터는 다른 필터나 컨트롤러에서 본문 접근 불가 |
우선순위 주의 |
Ordered.HIGHEST_PRECEDENCE(가장 높은 우선순위) 사용 시 위험
→ 지양해야 함 |
우선순위 설정
방식 | 설명 | 예시 |
@Order 어노테이션 | 클래스에 직접 우선순위를 지정할 수 있음 | @Order(1) |
Ordered 인터페이스 | 우선순위를 지정하는 인터페이스 구현 (getOrder() 메서드 활용) |
implements Ordered
|
FilterRegistrationBean.setOrder() | Bean 등록 시 우선순위를 설정 |
bean.setOrder(2);
|
Wrapper Filter
항목 | 설명 |
Wrapper Filter 목적 |
HttpServletRequest 등을 감싸서 수정하거나 기능을 추가함
- 보통 요청 본문을 여러번 읽기위해 사용됨 |
우선순위 설정 기준 |
Ordered.REQUEST_WRAPPER_FILTER_MAX_ORDER 이하로 설정해야 함
→ 다른 필터들이 감싼 요청을 인식하도록 보장 |
예시) RequestWrapperFilter
더보기
public class CustomRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public CustomRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) sb.append(line);
}
this.body = sb.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() {
@Override
public boolean isFinished() { return byteArrayInputStream.available() == 0; }
@Override
public boolean isReady() { return true; }
@Override
public void setReadListener(ReadListener listener) {}
@Override
public int read() throws IOException { return byteArrayInputStream.read(); }
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() { return body; }
}
public class RequestWrapperFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
CustomRequestWrapper wrappedRequest = new CustomRequestWrapper(httpServletRequest);
System.out.println(wrappedRequest.getBody());
chain.doFilter(wrappedRequest, response);
}
}
@Bean
public FilterRegistrationBean<RequestWrapperFilter> requestWrapperFilter() {
FilterRegistrationBean<RequestWrapperFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new RequestWrapperFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 1);
return registrationBean;
}
Registering Servlets, Filters, and Listeners as Spring Beans
항목 | 설명 |
ServletRegistrationBean |
Servlet을 Spring Bean으로 등록하여 커스텀 서블릿을 매핑할 수 있음
|
FilterRegistrationBean |
Filter를 Spring Bean으로 등록하며, 필터 순서 및 URL 패턴 등 설정 가능
|
ServletListenerRegistrationBean |
ServletContextListener, HttpSessionListener 등 리스너를 Bean으로 등록
|
특징 |
- Spring Boot의 Embedded Servlet Container에서 유용
- application.properties 참조 가능 - Bean 이름이 경로 접두사로 사용될 수 있음 |
DelegatingFilterProxyRegistrationBean
- DelegatingFilterProxy를 Spring Bean으로 등록하고, 서블릿 필터 체인에 등록하기 위한 설정 도구.
항목 | 설명 |
DelegatingFilterProxy | Spring의 Filter 빈을 서블릿 필터로 위임하기 위한 프록시 Filter. 서블릿 필터 체인에 등록됨 실제 Filter 로직은 Spring Bean에 있는 Filter가 처리함. |
사용 이유 | 서블릿 컨테이너가 초기화 되기 전에, 빈이 완전히 초기화되지 않을 수 있음 |
장점 | 안정적인 의존성 주입 Spring Lifecycle 관리 |
등록 방법 | DelegatingFilterProxyRegistrationBean FilterRegistrationBean<DelegatingFilterProxy> |
예제) DelegatingFilterProxyRegistrationBean
더보기
@Component("myCustomSpringFilter")
public class MyCustomSpringFilter implements Filter {
private final LogService logService;
public MyCustomSpringFilter(LogService logService) {
this.logService = logService;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println(logService.hello());
chain.doFilter(request, response);
}
}
@Bean
public DelegatingFilterProxyRegistrationBean delegatingFilterProxy() {
DelegatingFilterProxyRegistrationBean registrationBean = new DelegatingFilterProxyRegistrationBean("myCustomSpringFilter");
registrationBean.setUrlPatterns(List.of("/*"));
registrationBean.setOrder(1);
return registrationBean;
}
예제) FilterRegistrationBean<DelegatingFilterProxy>
더보기
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() {
FilterRegistrationBean<DelegatingFilterProxy> registrationBean = new FilterRegistrationBean<>();
DelegatingFilterProxy proxy = new DelegatingFilterProxy("myCustomSpringFilter");
registrationBean.setFilter(proxy);
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
Servlet Context Initialization
- ServletContextInitializer 인터페이스의 onStartup 메서드를 구현하여 ServletContex 초기화를 담당합니다.
- WebApplicationInitializer를 가지고 구현합니다.
- web.xml 을 대체할 수 있습니다.
Scanning for Servlets, Filters, and listeners
- @WebServlet, @WebFilter, @WebListener로 빈을 등록할 수 있습니다.
- @ServletComponentScan 어노테이션으로 위의 빈을 스캔할 수 있습니다.
The ServletWebServerApplicationContext
항목 | 설명 |
정의 |
Spring Boot에서 서블릿 환경에서 사용하는 ApplicationContext
|
ServletWebServerFactory |
내장 서블릿 컨테이너를 설정
ServletWebServerApplicationContext에 등록 |
WebServer 제어 |
시작, 중지, 포트 변경 등 웹 서버를 직접 제어할 수 있음
|
ServletContext 빈 등록 |
ServletContext 객체를 Spring Bean으로 등록
|
Bean과 ServletContext 초기화 시점 차이 |
Bean이 먼저 초기화될 수 있어 ServletContext가 아직 준비되지 않은 경우가 발생 가능
|
문제점 |
Bean이 ServletContext에 접근해야 할 경우, 아직 초기화되지 않아 접근 실패 가능성 존재
|
해결 방법 |
ApplicationListener<ServletWebServerInitializedEvent>를 사용해, 서버가 완전히 초기화된 후 ServletContext에 접근하도록 처리
|
public class MyDemoBean implements ApplicationListener<ApplicationStartedEvent> {
private ServletContext servletContext;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
this.servletContext = ((WebApplicationContext) applicationContext).getServletContext();
}
}
SameSite Cookie
- 쿠키의 크로스 사이트 요청 동작을 제어하는 속성입니다.
쿠키 속성
속성 | 설명 | 예시 |
name | 쿠키의 이름 | JSESSIONID |
value | 쿠키에 저장되는 값 | abc123xyz |
domain | 도메인 기반 쿠키 범위 설정 (하위 도메인 포함 가능) |
.myapp.com → sub.myapp.com 가능
|
path | URL 경로 기반 쿠키 범위 설정 (하위 경로 포함 가능) |
/admin -> /admin/settings 가능 |
maxAge | 쿠키의 유효 시간 (초 단위) - 0: 즉시 삭제 - -1: 세션 쿠키 (브라우저 닫으면 자동 삭제됨) |
3600 (1시간) |
expires | 쿠키 만료 날짜 (RFC1123 형식) Set-Cookie 응답 헤더에 포함됨 |
Wed, 21 Oct 2025 07:28:00 GMT
|
secure | true일 경우 HTTPS 연결에서만 쿠키 전송 | Secure |
httpOnly | JavaScript로 쿠키 접근 불가 (XSS 방지) | HttpOnly |
sameSite | 크로스 사이트 요청에 대한 쿠키 전송 제어 Strict, Lax, None |
SameSite=Lax |
SameSite 쿠키 속성
속성 값 | 동작 | 크로스 사이트 요청 시 전송 여부 | 비고 |
Strict | 완전 차단 | ❌ 전송 안 됨 | 가장 보안이 강함 로그인 등 민감 정보 보호에 적합 단, 외부 서비스와 연동 어려움 |
Lax | 부분 허용 | ✅ 탐색 요청에는 전송 ❌ AJAX / 폼 전송 등에는 전송 안 됨 |
기본값(Lax) 링크 클릭, 주소창 직접 입력 시 쿠키 전송 |
None | 전체 허용 | ✅ 모든 요청에 전송 | 반드시 Secure 속성과 함께 사용해야 함 HTTPS 필수 |
요청 방식 | Strict | Lax | None |
링크 클릭 (a 태그) | ❌ | ✅ | ✅ |
GET 요청 (AJAX) | ❌ | ❌ | ✅ |
POST 요청 (AJAX / 폼) | ❌ | ❌ | ✅ |
이미지, iframe 등 외부 리소스 요청 | ❌ | ❌ | ✅ |
설정) server.servlet.session.cookie.same-site
더보기
server:
servlet:
session:
cookie:
same-site: Lax
- id가 JSESSIONID인 세션 쿠키일 경우, 자동으로 SameSite를 설정해줌
@GetMapping("/set-cookie")
public ResponseEntity<String> setCookie(HttpServletResponse response) {
// 세션 쿠키는 Spring에서 자동으로 관리되므로 아래는 실제로 추가할 필요가 없지만, 설명을 위해 작성
Cookie sessionCookie = new Cookie("JSESSIONID", "some-session-id");
sessionCookie.setPath("/"); // 모든 경로에서 쿠키 접근 가능
sessionCookie.setHttpOnly(true); // JavaScript에서 접근 불가능
sessionCookie.setSecure(true); // HTTPS를 사용하지 않으면 전송되지 않음
sessionCookie.setMaxAge(-1); // Max-Age를 -1로 설정하면 세션 쿠키로 설정됨
response.addCookie(sessionCookie); // 응답에 세션 쿠키 추가
return ResponseEntity.ok("쿠키 설정 완료");
}
설정) CookieSameSiteSupplier
더보기
@Configuration
public class CookieConfig {
@Bean
public CookieSameSiteSupplier cookieSameSiteSupplier() {
return (Cookie cookie) -> {
if (cookie.getName().matches("myapp.*")) {
return "Lax"; // SameSite의 Lax 값을 반환합니다.
}
return null; // 그 외의 쿠키는 변경하지 않습니다.
};
}
}
예시) Strict
더보기
@GetMapping("/set-cookie")
public ResponseEntity<String> setCookie(HttpServletResponse response) {
// 세션 쿠키는 Spring에서 자동으로 관리되므로 아래는 실제로 추가할 필요가 없지만, 설명을 위해 작성
Cookie sessionCookie = new Cookie("JSESSIONID", "some-session-id");
sessionCookie.setPath("/"); // 모든 경로에서 쿠키 접근 가능
sessionCookie.setHttpOnly(true); // JavaScript에서 접근 불가능
sessionCookie.setSecure(true); // HTTPS를 사용하지 않으면 전송되지 않음
sessionCookie.setMaxAge(-1); // Max-Age를 -1로 설정하면 세션 쿠키로 설정됨
response.addCookie(sessionCookie); // 응답에 세션 쿠키 추가
return ResponseEntity.ok("쿠키 설정 완료");
}
@GetMapping("/get-cookie")
public ResponseEntity<String> getCookie(@CookieValue(value = "JSESSIONID", required = false) String cookieValue) {
return ResponseEntity.ok("[GET] Received cookie: " + cookieValue);
}
@PostMapping(value = "/get-cookie", consumes = "application/json")
public ResponseEntity<String> getCookie2(@CookieValue(value = "JSESSIONID", required = false) String cookieValue) {
return ResponseEntity.ok("[POST-json] Received cookie: " + cookieValue);
}
@PostMapping(value = "/get-cookie", consumes = "multipart/form-data")
public ResponseEntity<String> getCookie3(@CookieValue(value = "JSESSIONID", required = false) String cookieValue) {
return ResponseEntity.ok("[POST-formdata] Received cookie: " + cookieValue);
}
@GetMapping("/cookie-test/image.png")
public ResponseEntity<byte[]> imageTest(@CookieValue(value = "JSESSIONID", required = false) String cookieValue) throws IOException {
System.out.println("[GET-img] Received cookie: " + cookieValue);
byte[] image = StreamUtils.copyToByteArray(new ClassPathResource("static/image.png").getInputStream()); // ...load from classpath
return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).body(image);
}
@GetMapping("/cookie-test/frame.html")
public ResponseEntity<String> frameTest(@CookieValue(value = "JSESSIONID", required = false) String cookieValue) throws IOException {
System.out.println("[GET-iframe] Received cookie: " + cookieValue);
String html = StreamUtils.copyToString(new ClassPathResource("static/frame.html").getInputStream(), UTF_8);
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(html);
}
❌ [GET] Received cookie: null # <a>
❌ [GET] Received cookie: null # ajax
❌ [POST-json] Received cookie: null
❌ [POST-formdata] Received cookie: null
❌ [GET-img] Received cookie: null
❌ [GET-iframe] Received cookie: null
예시) Lax
더보기
✅ [GET] Received cookie: some-session-id # <a>
❌ [GET] Received cookie: null # ajax
❌ [POST-json] Received cookie: null
❌ [POST-formdata] Received cookie: null
❌ [GET-img] Received cookie: null
❌ [GET-iframe] Received cookie: null
예시) None
더보기
✅ [GET] Received cookie: some-session-id # <a>
✅ [GET] Received cookie: some-session-id # ajax
✅ [POST-json] Received cookie: some-session-id
✅ [POST-formdata] Received cookie: some-session-id
✅ [GET-img] Received cookie: some-session-id
✅ [GET-iframe] Received cookie: some-session-id
참고
'Spring > Spring Boot' 카테고리의 다른 글
[Spring Boot] 5. Executable Jars (0) | 2023.10.13 |
---|---|
[Spring Boot] 4. Data: SQL Databases (0) | 2023.10.13 |
[Spring Boot] 2-10. Core Features: Auto-Configuration (0) | 2023.10.11 |
[Spring Boot] 2-6. Core Features: JSON (0) | 2023.10.09 |
[Spring Boot] 2-3. Core Features: Profile (0) | 2023.10.09 |