CORS(Cross-Origin Resource Sharing)이란 무엇인가요?

김상욱·2024년 12월 4일

CORS(Cross-Origin Resource Sharing)이란 무엇인가요?

CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)는 웹 애플리케이션에서 다른 도메인, 프로토콜 또는 포트에서 리소스를 요청할 때 발생하는 보안 메커니즘입니다. CORS는 보안상의 이유로 브라우저가 리소스를 요청할 때 발생하는 동일 출처 정책(Same-Origin Policy)을 우회할 수 있도록 합니다.

동일 출처 정책(Same-Origin Policy)

브라우저가 보안상의 이유로 한 출처(origin)에서 로드된 웹 페이지가 다른 출처(Origin)의 리소스에 액세스하는 것을 차단하는 정책
출처(origin) = 프로토콜 + 도메인 + 포트
ex) https://example.com:443과 https://example.com:80은 다른 출처로 간주(포트가 다름)
https://example.com과 http://example.com은 다른 출처(프로토콜이 다름)

-> 동일 출처 정책은 보안을 강화하지만, REST API나 외부 리소스를 사용해야 할 경우 불편을 초래합니다. 이를 해결하기 위해 CORS가 도입

CORS의 작동 방식
CORS는 서버와 클라이언트 간의 협력을 통해 이루어집니다. 클라이언트(웹 브라우저)가 다른 리소스를 요청하면, 서버는 요청을 허용하거나 차단합니다. 이를 위해 서버는 HTTP 응답 헤더에 특정 정보를 포함해야 합니다.
1) CORS 요청의 종류
CORS의 요청은 단순 요청(Simple Request)과 사전 요청(Preflight Request)로 나뉩니다.

단순 요청은 GET, POST, HEAD 메서드로 이루어진 요청으로 요청헤더가 다음의 조건을 만족해야 합니다.
Accept, Accept-Language, Content-Language, Content-Type

GET /resource HTTP/1.1
Host: api.example.com
Origin: https://example.com

서버는 응답 시 아래와 같은 CORS 허용 헤더를 포함

Access-Control-Allow-Origin: https://example.com

사전 요청은 단순 요청 이외의 요청(Put, DELETE 메서드, 커스텀 헤더 사용)은 브라우저가 먼저 서버에 HTTP OPTIONS 메서드로 사전 요청을 보냅니다.
서버는 이 요청에 대해 허용 여부를 응답해야 합니다.

OPTIONS /resource HTTP/1.1
Host: api.example.com
Origin: https://example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: X-Custom-Header

서버의 응답:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: X-Custom-Header

2) CORS 관련 헤더

  • 클라이언트가 보낸 요청 헤더
    Origin : 요청의 출처를 나타냄. 서버는 이 값을 기반으로 요청을 허용하거나 차단함
  • 서버가 응답에 포함하는 헤더
Origin: https://example.com

Access-Control-Allow-Origin : 요청을 허용할 출처를 명시. *를 사용하면 모든 출처 허용

Access-Control-Allow-Origin: https://example.com

Access-Control-Allow-Methods : 허용할 HTTP 메서드 목록

Access-Control-Allow-Methods: GET, POST, OPTIONS

Access-Control-Allow-Headers : 클라이언트가 사용할 수 있는 요청 헤더를 지정.

Access-Control-Allow-Headers: Content-Type, X-Custom-Header

Access-Control-Allow-Credentials : 쿠키나 인증 정보를 포함할 수 있도록 설정. 반드시 출처를 특정해야 함(* 사용 불가)

Access-Control-Allow-Credentials: true

구현 예시

const express = require('express');
const cors = require('cors');
const app = express();

// 모든 출처 허용
app.use(cors());

// 특정 출처만 허용
app.use(cors({ origin: 'https://example.com' }));

app.get('/data', (req, res) => {
  res.json({ message: 'CORS is working!' });
});

app.listen(3000, () => console.log('Server running on port 3000'));
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(origins = "https://example.com")
public class MyController {
    @GetMapping("/data")
    public String getData() {
        return "CORS is working!";
    }
}

CORS에서 발생할 수 있는 문제

  • CORS 설정 누락 : 서버에서 Access-Control-Allow-Origin 헤더가 없으면 브라우저가 요청을 차단
  • Credentials 관련 문제 : 클라이언트가 withCredentails 옵션을 사용했는데 서버에서 Access-Control-Allow-Credentials를 설정하지 않은 경우.
  • Preflight 요청 실패 : 서버가 OPTIONS 요청을 제대로 처리하지 못할 경우

CORS가 필요한 이유
보안 강화 : 악의적인 스크립트가 사용자의 브라우저를 통해 무단으로 다른 도메인의 리소스를 가져오는 것을 방지
API 사용 : 클라이언트와 서버가 다른 출처에서 동작하는 현대적인 웹 애플리케이션에서 필수적


CORS(Cross-Origin Resource Sharing)에 대한 개념을 익힌 후, 신입 또는 취업 준비 중인 Java, Spring 백엔드 개발자 입장에서 실습할 만한 몇 가지 사례를 소개합니다. 이러한 실습은 실제 프로젝트 환경에서 발생할 수 있는 상황을 대비하며, 이론과 실무를 연결하는 데 큰 도움이 됩니다.


1. 기본 CORS 설정 실습

Spring Boot에서 CORS를 설정하고 테스트하는 간단한 실습입니다.

1) CORS 설정

Spring Boot 애플리케이션을 생성한 후, CORS 설정을 추가합니다.

a. Global CORS Configuration
import org.springframework.context.annotation.Bean;
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 CorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**") // 모든 경로에 대해 CORS 허용
                        .allowedOrigins("http://localhost:3000") // 특정 출처만 허용
                        .allowedMethods("GET", "POST", "PUT", "DELETE") // 허용할 HTTP 메서드
                        .allowedHeaders("*") // 모든 헤더 허용
                        .allowCredentials(true); // 쿠키, 인증정보 허용
            }
        };
    }
}
b. Controller 예제
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @GetMapping("/test")
    public String test() {
        return "CORS Test Success!";
    }
}

2) 테스트

  • 프론트엔드 코드 (React)

    fetch('http://localhost:8080/test', {
      method: 'GET',
      credentials: 'include', // 쿠키 전송 포함
    })
      .then(response => response.text())
      .then(data => console.log(data))
      .catch(error => console.error('Error:', error));
  • 결과

    • CORS 설정이 제대로 되었으면 브라우저 콘솔에 "CORS Test Success!" 출력.
    • 설정을 변경하여 테스트할 때 브라우저의 CORS 관련 에러 메시지를 확인.

2. Preflight 요청 테스트

Preflight 요청의 필요성과 작동 방식을 이해하기 위한 실습입니다.

1) Controller에 Preflight 요청 조건 추가

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

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

    @PostMapping("/data")
    public String postData(@RequestHeader("X-Custom-Header") String header) {
        return "Received header: " + header;
    }
}

2) 테스트

  • Preflight 요청을 발생시키는 프론트엔드 코드

    fetch('http://localhost:8080/api/data', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Custom-Header': 'TestHeader', // Custom 헤더 추가
      },
      body: JSON.stringify({ message: 'Hello' }),
    })
      .then(response => response.text())
      .then(data => console.log(data))
      .catch(error => console.error('Error:', error));
  • 결과

    • Preflight 요청(OPTIONS 메서드)이 발생하고 서버가 이를 허용하면 POST 요청이 성공.
    • Preflight 요청이 실패하면 브라우저 콘솔에 CORS 관련 에러 메시지 출력.

3. Credentials 설정 실습

쿠키와 같은 인증 정보를 포함한 요청에서 CORS가 어떻게 작동하는지 실습합니다.

1) Controller에 쿠키 사용

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

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

@RestController
public class CookieController {

    @GetMapping("/set-cookie")
    public String setCookie(HttpServletResponse response) {
        Cookie cookie = new Cookie("myCookie", "cookieValue");
        cookie.setHttpOnly(true);
        cookie.setPath("/");
        response.addCookie(cookie);
        return "Cookie set!";
    }

    @GetMapping("/get-cookie")
    public String getCookie(@CookieValue(value = "myCookie", defaultValue = "NoCookie") String cookie) {
        return "Cookie value: " + cookie;
    }
}

2) CORS 설정 수정

allowCredentials(true)를 반드시 설정합니다.

3) 테스트

  • 쿠키 설정 요청

    fetch('http://localhost:8080/set-cookie', {
      method: 'GET',
      credentials: 'include', // 쿠키 전송 허용
    });
  • 쿠키 확인 요청

    fetch('http://localhost:8080/get-cookie', {
      method: 'GET',
      credentials: 'include', // 쿠키 전송 허용
    })
      .then(response => response.text())
      .then(data => console.log(data))
      .catch(error => console.error('Error:', error));
  • 결과

    • 쿠키가 제대로 설정되고, 요청 시 브라우저가 쿠키를 전송하여 응답으로 쿠키 값을 반환.

4. 실습 아이디어

  1. 다양한 클라이언트 환경 테스트
    • Postman, 브라우저, React/Vue/Angular 등 다양한 클라이언트 환경에서 CORS 테스트.
  2. Mock 서버와의 통신
    • JSONPlaceholder 또는 Mockoon 같은 도구를 사용하여 Mock API 서버와 CORS 통신 실습.
  3. 배포 환경 테스트
    • 프론트엔드와 백엔드를 다른 서버(도메인)로 배포하여 CORS 작동 방식 확인.
  4. CORS 정책 변경 실습
    • *와 특정 출처 설정, 메서드 및 헤더 제한 등을 변경하여 정책의 효과 비교.

5. 실습을 통해 얻을 수 있는 것

  • CORS 기본 원리 및 브라우저 동작 이해: 실습을 통해 단순 요청과 Preflight 요청의 차이를 명확히 알 수 있습니다.
  • Spring에서 CORS 구현: Spring의 다양한 CORS 설정 방법을 익혀 실제 프로젝트에 활용 가능.
  • 디버깅 역량 강화: CORS 에러 메시지를 통해 문제를 파악하고 해결하는 능력 향상.
  • 배포 환경 대응 능력: 개발 환경과 배포 환경에서의 CORS 차이를 이해하고, 실무 문제를 해결하는 역량 습득.

궁금한 점이 있거나 추가 실습 아이디어가 필요하면 언제든 질문하세요! 😊

0개의 댓글