OPTIONS(http)

OPTIONS는 특정 리소스에 대해 서버가 허용하는 통신 옵션(메서드, 헤더 등)을 확인하기 위한 HTTP 메서드이며, 실무에서는 주로 CORS 프리플라이트 요청에서 사용됩니다.

기본 동작

// 클라이언트: 이 리소스에 어떤 메서드를 쓸 수 있나?
OPTIONS /members HTTP/1.1

// 서버: 이 메서드들 사용 가능해
HTTP/1.1 200 OK
Allow: GET, POST, PUT, DELETE, OPTIONS

서버의 리소스를 변경하지 않으므로 안전(Safe)하고 멱등(Idempotent)합니다.

OPTIONS가 실무에서 중요한 이유 — CORS

CORS란?

[Same-Origin — 같은 출처]
프론트엔드: https://example.com
API 서버:   https://example.com/api
→ 같은 출처 → 제한 없이 통신 가능 O

[Cross-Origin — 다른 출처]
프론트엔드: https://frontend.com
API 서버:   https://api.backend.com
→ 다른 출처 → 브라우저가 기본적으로 차단 X

브라우저는 보안을 위해 다른 출처(Origin)로의 요청을 기본적으로 차단합니다. 이를 허용하기 위한 메커니즘이 CORS(Cross-Origin Resource Sharing)이며, 그 핵심에 OPTIONS 프리플라이트 요청이 있습니다.

Origin이란?

https://example.com:443/path
└─scheme─┘└──host──┘└port┘

Origin = Scheme + Host + Port
→ 이 세 가지 중 하나라도 다르면 Cross-Origin
요청 출처대상같은 출처?
https://example.comhttps://example.com/apiO 같음
https://example.comhttp://example.comX scheme 다름
https://example.comhttps://api.example.comX host 다름
https://example.comhttps://example.com:8080X port 다름

프리플라이트 요청 (Preflight Request)

브라우저가 본 요청을 보내기 전에 OPTIONS로 먼저 서버에 허락을 구하는 사전 요청입니다.

전체 흐름

[1단계 — 프리플라이트 (OPTIONS)]
브라우저 → 서버: "이런 요청 보내도 돼?"
OPTIONS /members HTTP/1.1
Origin: https://frontend.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

서버 → 브라우저: "이 조건이면 허용할게"
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600

[2단계 — 본 요청 (POST)]
브라우저: 프리플라이트 통과 확인 → 본 요청 전송
POST /members HTTP/1.1
Origin: https://frontend.com
Authorization: Bearer eyJhbG...
Content-Type: application/json

{"name": "홍길동"}

서버 → 브라우저:
HTTP/1.1 201 Created
Access-Control-Allow-Origin: https://frontend.com

프리플라이트 요청/응답 헤더 정리

요청 헤더 (브라우저 → 서버)

헤더설명
Origin요청을 보내는 출처
Access-Control-Request-Method본 요청에서 사용할 메서드
Access-Control-Request-Headers본 요청에서 사용할 커스텀 헤더

응답 헤더 (서버 → 브라우저)

헤더설명
Access-Control-Allow-Origin허용하는 출처 (* 또는 특정 도메인)
Access-Control-Allow-Methods허용하는 메서드 목록
Access-Control-Allow-Headers허용하는 헤더 목록
Access-Control-Max-Age프리플라이트 결과 캐시 시간 (초)
Access-Control-Allow-Credentials쿠키/인증 정보 포함 허용 여부

프리플라이트가 발생하는 조건

모든 Cross-Origin 요청에 프리플라이트가 발생하는 것은 아닙니다.

단순 요청 (Simple Request) — 프리플라이트 없음

아래 조건을 모두 만족하면 프리플라이트 없이 바로 본 요청을 보냅니다.

1. 메서드: GET, HEAD, POST 중 하나
2. 헤더: Accept, Content-Type 등 기본 헤더만 사용
3. Content-Type: 아래 세 가지 중 하나
   - application/x-www-form-urlencoded
   - multipart/form-data
   - text/plain

프리플라이트 발생 — 하나라도 해당되면

- PUT, PATCH, DELETE 메서드 사용
- Authorization 같은 커스텀 헤더 포함
- Content-Type: application/json 사용  ← 실무에서 거의 항상 해당
[실무에서 대부분 프리플라이트가 발생하는 이유]
REST API는 보통 Content-Type: application/json을 사용하고
Authorization 헤더로 토큰을 전달하므로
→ 단순 요청 조건을 만족하지 않음
→ 거의 모든 API 요청에 프리플라이트 발생

Spring Boot CORS 설정

방법 1: 전역 설정

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://frontend.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);  // 프리플라이트 캐시 1시간
    }
}

방법 2: Spring Security 설정

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .csrf(csrf -> csrf.disable());
        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("https://frontend.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        config.setAllowedHeaders(List.of("*"));
        config.setAllowCredentials(true);
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}

방법 3: 컨트롤러 레벨

@CrossOrigin(origins = "https://frontend.com")
@RestController
@RequestMapping("/api/members")
public class MemberController {
    // ...
}

프리플라이트 성능 최적화

매 요청마다 OPTIONS가 선행되면 요청 수가 2배가 되므로 성능에 영향을 줄 수 있습니다.

요청 1회 → OPTIONS + 본 요청 = 실제 2회 네트워크 통신

[해결: Access-Control-Max-Age]
Access-Control-Max-Age: 3600 (1시간)
→ 브라우저가 프리플라이트 결과를 1시간 동안 캐시
→ 캐시 기간 내에는 프리플라이트 생략하고 바로 본 요청 전송

면접 포인트

CSLOG에서 보기