CORS란 무엇인가?
CORS(Cross-Origin Resource Sharing)는 웹 브라우저의 동일 출처 정책(Same-Origin Policy)으로 인해, 한 출처에서 로드된 웹 페이지가 다른 출처의 리소스에 접근할 때 발생하는 제약을 해결하기 위한 메커니즘입니다. CORS는 이러한 보안 제약을 제어하고, 특정 조건 하에서 다른 출처의 리소스에 대한 접근을 허용합니다.
동일 출처 정책이란?
동일 출처 정책은 웹 애플리케이션에서, 자바스크립트 코드가 자신이 로드된 도메인, 프로토콜, 포트와 동일한 출처에서만 데이터를 요청할 수 있도록 제한하는 보안 메커니즘입니다. 이는 보안을 강화하지만, 여러 출처에서 데이터를 요청해야 하는 경우 문제가 발생할 수 있습니다. 이때 필요한 것이 바로 CORS 정책입니다.
CORS 문제가 발생하는 이유
브라우저는 동일 출처 정책(Same-Origin Policy)에 따라, 다른 출처(origin)에서 온 요청을 기본적으로 차단합니다. 출처는 프로토콜, 도메인, 포트로 결정되며, 출처가 다르면 CORS 정책에 의해 차단됩니다. 예를 들어:
- 클라이언트 출처: https://client.example.com
- API 서버 출처: https://api.example.com
이 경우, 클라이언트는 API 서버에 교차 출처 요청을 하게 되며, 브라우저는 기본적으로 이를 차단합니다. 이때, 서버가 CORS 헤더를 적절히 설정하지 않으면 요청은 실패하고, CORS 관련 오류가 발생하게 됩니다.
이는 다른 출처로의 요청이기 때문에 CORS 오류가 발생하는 것입니다.
이 문제를 해결하려면 서버 측에서 적절한 CORS 응답 헤더를 설정해 주거나 아래의 내용을 습득하시면 됩니다.
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Origin: *
이런 식으로 특정 url을 허용해주거나 혹은 *를 통해서 모두 허용해주거나 잘 선택해야 합니다.
CORS 문제 해결 방법
Simple Request 사용
Simple Request란?
Simple Request는 CORS(Cross-Origin Resource Sharing) 요청의 한 유형으로, 브라우저가 Preflight 요청 없이 서버에 직접 요청을 보낼 수 있는 간단한 교차 출처 요청입니다.
Preflight가 무엇인 지 모를 수 있습니다. 아래에 나오니 그냥 무시하셔도 됩니다.
일반적으로, 브라우저는 보안상의 이유로 다른 출처로의 요청을 제한하지만, 특정 조건을 만족하는 요청은 예외적으로 Preflight 요청 없이 바로 처리할 수 있습니다.
그리고 서버는 요청을 처리하고, 응답에 CORS 관련 헤더(예: Access-Control-Allow-Origin)를 포함하여 반환합니다.
Simple Request는 간단한 HTTP 요청에 사용되며, 그 목적은 성능을 향상시키고, 불필요한 네트워크 요청을 줄이는 데 있습니다.
즉, 성능이 중요한 웹 애플리케이션에서는 가능한 한 Preflight 요청 없이 Simple Request를 사용하여 불필요한 네트워크 트래픽을 줄일 수 있습니다.
그렇지만 중요한 점은 특정 조건에서만 가능하다는 것입니다.
Simple Request의 조건
허용된 HTTP 메서드
Simple Request로 전송할 수 있는 메서드는 다음 세 가지로 제한됩니다:
- GET
- POST
- HEAD
이 세 메서드 이외의 메서드(예: PUT, DELETE, PATCH)는 Simple Request로 간주되지 않으며, Preflight 요청이 필요합니다.
허용된 요청 헤더
Simple Request에서는 특정 헤더만 사용할 수 있습니다. 만약 요청에 허용되지 않은 헤더가 포함된다면, 브라우저는 Preflight 요청을 통해 먼저 서버에 허용 여부를 확인해야 합니다. Simple Request에서 허용되는 헤더는 다음과 같습니다:
- Accept
- Accept-Language
- Content-Language
- Content-Type (단, 특정 값만 허용)
- DPR, Downlink, Save-Data, Viewport-Width, Width (클라이언트 힌트 헤더)
Content-Type 헤더는 아래의 세 가지 값만 허용됩니다:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
위의 조건을 벗어난 커스텀 헤더(예: X-Custom-Header)나 허용되지 않은 Content-Type 값을 포함한 요청은 Simple Request가 아니며, Preflight 요청을 통해 서버의 허용 여부를 확인해야 합니다.
XMLHttpRequest.upload 사용 금지
Simple Request에서는 XMLHttpRequest.upload 객체를 사용할 수 없습니다. 만약 이 객체를 사용하는 이벤트 리스너(예: onuploadprogress)가 추가된 경우, 요청은 Simple Request로 간주되지 않으며 Preflight 요청이 필요합니다.
Spring Boot 사용
@CrossOrigin 어노테이션 사용
Spring Boot에서는 간단하게 @CrossOrigin 어노테이션을 사용해 특정 컨트롤러나 메서드에 CORS 설정을 적용할 수 있습니다.
다음은 특정 엔드포인트에 CORS를 허용하는 예시입니다:
@RestController
@RequestMapping("/api")
public class MyController {
@CrossOrigin(origins = "https://example.com")
@GetMapping("/data")
public ResponseEntity<String> getData() {
return ResponseEntity.ok("CORS 테스트");
}
}
위 코드는 https://example.com
에서 들어오는 요청에 대해 CORS를 허용합니다.
즉, 해당 도메인에서만 `/api/data` 엔드포인트에 접근할 수 있습니다.
전역 CORS 설정 (WebMvcConfigurer)
여러 컨트롤러에서 CORS를 설정해야 할 때는, WebMvcConfigurer를 사용해 **전역적으로 CORS 설정**을 할 수 있습니다. 다음은 전체 애플리케이션에 대해 특정 도메인에서의 CORS 요청을 허용하는 방법입니다:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("https://example.com") // 허용할 도메인
.allowedMethods("GET", "POST", "PUT", "DELETE") // 허용할 HTTP 메서드
.allowedHeaders("*") // 허용할 헤더
.allowCredentials(true); // 자격 증명 허용 (쿠키 등)
}
}
위의 설정은 전역적으로 모든 경로(예시 :/api 등)에 대해 CORS를 허용하며, 특정 도메인(https://example.com)에서만 접근할 수 있도록 설정합니다.
Filter를 사용한 CORS 설정
때로는 보다 세밀한 제어가 필요할 수 있습니다. 이때는 Filter를 사용하여 CORS 설정을 직접 처리할 수 있습니다.
@Component
public class SimpleCorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Origin", "https://example.com");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(request, response);
}
}
}
이 방식은 필터를 사용하여 모든 요청을 가로채고 CORS 응답 헤더를 설정합니다.
이렇게 하면 더 세밀하게 CORS 정책을 제어할 수 있습니다.
프록시 서버를 통한 CORS 문제 해결
또 다른 방법으로 프록시 서버를 사용하여 CORS 문제를 우회할 수 있습니다. 클라이언트는 직접 다른 출처의 서버에 요청하지 않고, 프록시 서버를 통해 요청을 전달하면 됩니다. 프록시 서버가 요청을 대신 처리하고, 클라이언트는 CORS 문제 없이 데이터를 수신하게 됩니다.
Spring Boot에서는 이를 Spring Cloud Gateway를 이용해 쉽게 구현할 수 있습니다.
프록시 서버가 뭐냐? 라는 생각이 드시면 아래의 글을 참고해 주세요.
[네트워크] 프록시 서버 너는 누구냐?
프록시 서버란?프록시 서버(Proxy Server)는 네트워크에서 클라이언트와 서버 간의 중간에 위치하는 중개 서버를 의미합니다.클라이언트가 요청을 보낼 때, 직접 서버에 요청하지 않고 프록시 서버
gotobill.tistory.com
클라이언트는 실제로 교차 출처의 서버와 통신하는 대신, 프록시 서버로 요청을 보내고, 프록시 서버가 이를 대신 처리하여 교차 출처 서버에 요청을 전달합니다.
이 과정에서 클라이언트는 브라우저의 동일 출처 정책을 위반하지 않게 되며, CORS 제한에 걸리지 않습니다.
실제로 API 서버와 통신하는 것은 프록시 서버이기 때문에, 클라이언트는 교차 출처 요청을 직접 보내는 것이 아니므로 CORS 오류가 발생하지 않습니다.
혼자 들었던 의문
아니 그러면 프록시 서버도 교차 서버에서 cors 제한에 안 걸리려면 따로 허용을 해줘야 하나?
-> 정답은 '그럴 필요가 없다.' 이다.
- 프록시 서버는 클라이언트와 동일한 도메인이나 IP 주소를 사용하므로, CORS 규칙을 위반하지 않는다.
- 클라이언트는 프록시 서버를 직접 호출하므로, CORS 설정 없이도 동일 출처로 간주된다.
안 궁금하실 수도 있지만 개인적인 저의 의문이었습니다.
Spring Cloud Gateway로 프록시 설정
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("api_route", r -> r.path("/api/**")
.uri("https://api.example.org"))
.build();
}
}
이 설정은 클라이언트가 https://example.com/api/**
로 요청을 보내면, 해당 요청을 https://api.example.org
로 프록싱하여 전달하는 역할을 합니다. 이를 통해 CORS 문제를 우회할 수 있습니다.
Nginx 프록시 설정
server {
listen 80;
server_name myapp.com;
location /api/ {
proxy_pass https://api.example.org; # API 서버로 직접 전달
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Preflight 요청 허용
아까 위에서 simple request를 배웠습니다. 제약이 많다는 것도 알았습니다.
이에 대한 해결책으로 Preflight가 있습니다.
Preflight 요청은 CORS(Cross-Origin Resource Sharing) 요청에서 복잡한 요청을 하기 전에 브라우저가 먼저 서버의 허용 여부를 확인하기 위해 보내는 사전 요청(OPTIONS 요청)입니다.
브라우저는 교차 출처 요청이 서버에서 허용되는지 미리 확인한 후, 실제 요청을 보냅니다. 이는 보안을 강화하고 서버와 클라이언트 간의 교차 출처 정책을 준수하기 위해 설계된 절차입니다.
만약 내가 엄청 큰 파일이나 데이터를 보내고 싶은데 막상 보냈더니 이게 허가가 안되면 엄청난 낭비잖아요?
그렇기 때문에 미리 사전 요청을 보내서 보낼 수 있는 지 확인하는 과정이라고 생각하면 됩니다.
Preflight 요청은 주로 클라이언트가 특정한 HTTP 메서드나 헤더를 포함한 요청을 보낼 때 발생합니다. 이러한 요청이 서버에서 허용되는지 사전에 검증하는 과정입니다.
동작원리
Preflight 요청은 브라우저가 교차 출처 요청을 할 때, OPTIONS 메서드를 사용하여 서버에 다음과 같은 정보를 묻는 요청을 보냅니다:
- 요청 메서드: 클라이언트가 실제로 사용하려고 하는 메서드(예: PUT, DELETE 등).
- 요청 헤더: 클라이언트가 요청에 포함하려는 커스텀 헤더(예: X-Custom-Header).
- 출처: 요청이 어느 출처에서 왔는지(Origin 헤더).
서버는 이 Preflight 요청을 받으면, 응답에 다음과 같은 CORS 관련 헤더를 포함하여 클라이언트에 응답합니다:
- Access-Control-Allow-Origin: 요청을 허용할 수 있는 출처.
- Access-Control-Allow-Methods: 허용되는 HTTP 메서드.
- Access-Control-Allow-Headers: 허용되는 커스텀 헤더.
만약 서버가 해당 요청을 허용한다면, 브라우저는 실제 요청(본 요청)을 수행합니다. 반대로, 서버가 허용하지 않으면 브라우저는 실제 요청을 차단합니다.
1. Preflight 요청 (OPTIONS 메서드)
OPTIONS /api/resource HTTP/1.1
Host: api.example.com
Origin: https://client.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Custom-Header
2. 서버의 Preflight 응답
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://client.example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: X-Custom-Header
3. 허가된 경우 -> 실제 요청 (본 요청)
POST /api/resource HTTP/1.1
Host: api.example.com
Origin: https://client.example.com
X-Custom-Header: value
Preflight 요청은 Simple Request가 아닌 경우에만 브라우저가 Preflight 요청을 수행합니다.
스프링 설정을 이렇게 OPTIONS 설정을 넣어주면 된다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://example.com") // 허용할 출처
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
'네트워크' 카테고리의 다른 글
[네트워크] 소켓 너는 대체 누구냐 (1) | 2024.10.27 |
---|---|
[네트워크] 컴퓨터 네트워크 정리1 (0) | 2024.10.10 |
[CS] CS 기술면접 (1) (0) | 2024.10.07 |
[네트워크] 프록시 서버 너는 누구냐? (0) | 2024.10.07 |