Web

[WEB] Preflight 요청: 브라우저의 안전장치 🛡️

xeunnie 2025. 1. 18. 01:00
728x90
반응형

Preflight 요청 톺아보기! 🛡️

웹 개발을 하다 보면 CORS(Cross-Origin Resource Sharing)와 함께 등장하는 Preflight 요청이라는 말을 들어봤을 거예요.

이 Preflight 요청은 브라우저가 보안을 위해 요청 전에 “미리 탐색”하는 과정이에요. 이번 글에서는 Preflight 요청이 무엇인지, 왜 필요하며, 어떻게 동작하는지, 그리고 프론트엔드 개발자가 알아야 할 실무 팁을 자세히 살펴볼게요.


🌟 Preflight 요청이란?

Preflight 요청은 브라우저가 교차 출처 요청(Cross-Origin Request)을 하기 전에 OPTIONS 메소드를 사용해 서버의 정책을 확인하는 과정이에요.

브라우저는 요청을 보내기 전에, 이 요청이 허용 가능한지 서버에게 묻는 것이죠.

🛡️ 왜 Preflight 요청이 필요할까요?

Preflight 요청은 보안을 강화하기 위해 도입된 기능이에요.

악의적인 사이트가 브라우저의 신뢰를 악용해 민감한 데이터를 탈취하지 못하도록 하기 위함입니다.

🧩 언제 Preflight 요청이 발생할까요?

Preflight 요청은 다음 조건 중 하나라도 만족하면 발생합니다:

  1. 사용되는 HTTP 메소드가 “단순하지 않은 경우”
    • 단순 메소드: GET, POST, HEAD
  2. 헤더가 “단순하지 않은 경우”
    • 허용된 헤더: Accept, Accept-Language, Content-Type (단, Content-Typetext/plain, multipart/form-data, application/x-www-form-urlencoded만 허용)
  3. CORS 정책으로 교차 출처 요청이 발생할 때
    • 출처(origin)가 다른 경우

⚙️ Preflight 요청의 동작 원리

Preflight 요청은 브라우저가 교차 출처 요청을 실행하기 전에 OPTIONS 메소드로 서버에 미리 요청을 보내는 방식으로 작동합니다.

🧭 Preflight 요청의 흐름

1️⃣ Preflight 요청 전송 (OPTIONS 메소드)

브라우저는 서버에 다음과 같은 질문을 보냅니다:

  • “이 출처에서, 이 메소드로, 이 헤더를 포함한 요청을 보내도 될까요?”

2️⃣ 서버의 응답 확인

서버는 브라우저의 질문에 대해 허용 여부를 응답합니다:

  • 허용된 메소드, 헤더, 출처 등이 포함된 응답을 전송

3️⃣ 실제 요청 실행

Preflight 요청이 성공하면 브라우저는 원래 의도했던 요청(예: POST)을 실행합니다.


🛤️ Preflight 요청의 예제

🎯 Preflight 요청 (OPTIONS)

다음은 브라우저가 Preflight 요청을 보낼 때의 예입니다:

 

요청 헤더:

OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

 

서버 응답:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400

 

  • Access-Control-Allow-Origin: 요청 출처를 허용할지 여부
  • Access-Control-Allow-Methods: 허용된 메소드 목록
  • Access-Control-Allow-Headers: 허용된 커스텀 헤더 목록
  • Access-Control-Max-Age: Preflight 요청 결과를 캐싱할 시간(초)

🪔 Preflight 요청을 최소화하기

Preflight 요청은 성능에 영향을 줄 수 있기 때문에 가능하면 줄이는 것이 좋아요.

다음은 Preflight 요청을 최소화하는 몇 가지 팁입니다:

1️⃣  단순 요청(Simple Request) 사용

  • HTTP 메소드는 GET, POST, HEAD만 사용
  • Content-Typeapplication/x-www-form-urlencoded, text/plain, multipart/form-data로 제한

2️⃣ 서버 설정 최적화

  • Preflight 응답에 Access-Control-Max-Age를 설정해 결과를 캐싱하세요.

예: Access-Control-Max-Age: 86400 (하루 동안 캐싱)

3️⃣ CORS 정책 검토

  • 클라이언트와 서버가 동일한 출처를 사용하도록 설정하거나, 허용된 출처를 명확히 정의

4️⃣ API Gateway 사용

  • API Gateway를 사용해 Preflight 요청을 프록시 처리하여 서버의 부하를 줄입니다.

📝 Preflight 요청과 관련된 문제 해결

1️⃣ Preflight 요청이 차단될 때

  • 서버에서 CORS 헤더를 올바르게 설정했는지 확인하세요:
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers

2️⃣ OPTIONS 요청 처리

  • 서버에서 OPTIONS 메소드를 수락하도록 설정하세요.
  • Express.js 예제:
const cors = require("cors");
app.use(cors());

 

3️⃣ 디버깅

  • 브라우저 개발자 도구의 네트워크 탭에서 Preflight 요청을 확인하세요.
  • 서버 로그를 점검하여 요청이 처리되지 않은 이유를 분석.

💼 실무에서 Preflight 요청 활용

🎯 예제: React와 Spring을 이용한 Preflight 요청 예제 🌟

Preflight 요청은 React 클라이언트와 Spring Boot 백엔드에서도 자주 마주칩니다. React에서 API 요청을 보낼 때 CORS 문제를 해결하려면 Spring Boot에서 CORS를 적절히 설정해야 합니다. 이 과정에서 Preflight 요청이 발생하는 원리와 해결 방법을 함께 알아볼게요!

📇 프로젝트 개요

  • React: 클라이언트 애플리케이션으로, 서버에 POST 요청을 보냅니다.
  • Spring Boot: 백엔드 API를 제공하며, CORS를 지원하도록 설정합니다.

📬 React 클라이언트

API 요청 예제:

React에서 Spring Boot 서버로 POST 요청을 보냅니다. 이때 Content-Type 헤더를 설정하면 Preflight 요청이 발생합니다.

import React from "react";

const App = () => {
  const sendRequest = async () => {
    try {
      const response = await fetch("http://localhost:8080/api/data", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ name: "Alice" }),
      });
      const data = await response.json();
      console.log("Response:", data);
    } catch (error) {
      console.error("Error:", error);
    }
  };

  return (
    <div>
      <h1>Preflight 요청 예제</h1>
      <button onClick={sendRequest}>Send Request</button>
    </div>
  );
};

export default App;
  • Content-Type: application/json: 이 헤더 때문에 브라우저는 Preflight 요청을 보내게 됩니다.
  • React 개발 서버(localhost:3000)에서 Spring Boot 서버(localhost:8080)로 요청을 보내므로 CORS 설정이 필요합니다.

📬 Spring Boot 백엔드

1️⃣  CORS 설정

Spring Boot에서 CORS를 설정하려면 다음 방법 중 하나를 선택하세요.

방법 1️⃣ : @CrossOrigin 사용

간단한 경우, 특정 컨트롤러 또는 메소드에 @CrossOrigin을 사용해 CORS를 허용할 수 있습니다.

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:3000")
public class ApiController {
    @PostMapping("/data")
    public Map<String, String> handleData(@RequestBody Map<String, String> payload) {
        System.out.println("Received data: " + payload);
        return Map.of("message", "Data received successfully!");
    }
}
  • @CrossOrigin: 특정 출처(http://localhost:3000)에서만 CORS 요청을 허용합니다.

방법 2️⃣ : 전역 CORS 설정

CORS를 애플리케이션 전체에서 허용하려면 WebMvcConfigurer를 사용합니다.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")  // /api로 시작하는 경로에 대해 CORS 허용
                .allowedOrigins("http://localhost:3000")  // 허용할 출처
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")  // 허용할 HTTP 메소드
                .allowedHeaders("*")  // 모든 헤더 허용
                .allowCredentials(true);  // 인증 정보 허용
    }
}

2️⃣ Preflight 요청 처리

Spring Boot는 기본적으로 Preflight 요청(OPTIONS 메소드)을 처리합니다. 특별한 추가 작업은 필요하지 않지만, 필요하면 다음처럼 직접 처리할 수도 있습니다.

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class ApiController {

    @RequestMapping(value = "/data", method = RequestMethod.OPTIONS)
    public ResponseEntity<Void> handlePreflight() {
        return ResponseEntity.ok().build();
    }

    @PostMapping("/data")
    public Map<String, String> handleData(@RequestBody Map<String, String> payload) {
        System.out.println("Received data: " + payload);
        return Map.of("message", "Data received successfully!");
    }
}

🧾 실행 및 결과

1️⃣ React에서 버튼 클릭

  • 브라우저는 Preflight 요청(OPTIONS 메소드)을 Spring Boot 서버로 보냅니다.
  • 서버가 Preflight 요청을 처리하고 응답하면 실제 POST 요청이 실행됩니다.

2️⃣ 콘솔 출력 (Spring Boot 서버)

  • Preflight 요청 처리:
OPTIONS /api/data

 

  • 실제 POST 요청 처리:
Received data: {name=Alice}

3️⃣ React 콘솔 출력

  • 서버의 응답 확인:
Response: {message: 
"Data received successfully!"
}

💡 실무 팁: Preflight 요청 최적화

1️⃣ 헤더 간소화

  • Preflight 요청을 피하려면 단순 요청(Simple Request) 조건을 만족
  • Content-Type을 application/x-www-form-urlencoded로 설정
  • 커스텀 헤더 사용 자제

2️⃣ CORS 캐싱

  • Access-Control-Max-Age를 설정해 Preflight 요청 결과를 캐싱하세요.
registry.addMapping("/api/**")
        .allowedOrigins("http://localhost:3000")
        .allowedMethods("GET", "POST")
        .allowedHeaders("*")
        .maxAge(3600);  // 1시간 동안 캐싱

 

3️⃣ 동일 출처 구성

  • 클라이언트와 서버가 동일한 출처(origin)에서 실행되도록 설정하면 CORS 문제를 피할 수 있습니다.

🌟 마무리

Preflight 요청의 핵심 포인트

  1. Preflight 요청의 목적: 보안 강화를 위해 교차 출처 요청 전에 서버의 허용 여부를 확인
  2. OPTIONS 메소드: Preflight 요청은 항상 OPTIONS 메소드를 사용
  3. 최적화: 불필요한 Preflight 요청을 최소화하려면 단순 요청을 사용하고 서버 응답을 캐싱
  4. CORS 정책 설정: 서버에서 올바르게 CORS 헤더를 설정해야 브라우저가 요청을 허용

Preflight 요청은 브라우저 보안을 위한 필수적인 절차이지만, 제대로 이해하고 최적화하면 성능과 사용자 경험을 개선할 수 있어요.

React와 Spring Boot를 함께 사용하면 Preflight 요청을 적절히 처리하고, 서버와의 통신을 안전하고 효율적으로 구현할 수 있습니다.

Preflight 요청은 처음엔 번거로워 보이지만, 이를 잘 이해하면 보안과 성능 모두를 챙길 수 있어요.

 

🌷전설의 개발자가 되어봅시당!🌷

728x90
반응형