![[보안] SOP와 CORS](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8UYJW%2FbtsOdBeuxuM%2FKvnIdRKHmNkkVvZml1r4T1%2Fimg.webp)
SOP(Same-Origin Policy)
SOP 개념
SOP(동일 출처 정책, Same-Origin Policy)은 웹 보안의 핵심 원칙 중 하나로, 서로 다른 출처(origin)의 리소스 간 접근을 제한하는 정책이다.
이는 브라우저에서 자동으로 적용되는 보안 메커니즘이며, 사용자 정보를 보호하고 악의적인 스크립트의 실행을 막기 위해 고안되었다.
동일 출처는 세 가지 요소가 모두 같아야 한다.
- 프로토콜(http/https)
- 도메인(example.com)
- 포트번호(80/443 등)
https://example.com | http://example.com | 프로토콜 다름 |
https://api.example.com | https://example.com | 호스트(도메인) 다름 |
https://example.com:443 | https://example.com:8443 | 포트 다름 |
SOP가 없으면 생길 수 있는 문제
브라우저는 기본적으로 아래와 같은 크로스 도메인 요청이 발생하면 JavaScript의 응답 접근을 막는다.
- 사용자가 A 사이트에 로그인 (쿠키에 세션 저장)
- 공격자가 만든 B 사이트를 방문
- B 사이트가 A 사이트에 백그라운드 요청 (`fetch('https://a.com/api/data')`)
- A 사이트의 쿠키가 자동 전송되어 민감 데이터 유출 가능
SOP는 이런 시도를 막아준다.
SOP가 허용및 차단하는 것들
허용하는 것들
- 같은 출처의 요청 및 응답: 문제없이 수행됨
- <img src="...">, <script src="...">, <link href="...">는 제한 없이 접근 가능 (단, JS 접근 불가)
- <form> 전송은 가능하나, JS로 응답 읽기 불가능
차단하는 것들
- 다른 출처의 JSON 데이터를 JS에서 직접 읽는 것
- 다른 출처의 쿠키/로컬스토리지/세션스토리지 접근
- window.postMessage 없이 iframe 내 다른 출처 문서에 접근
SOP 우회 방법
만약 다른 출처 간 통신이 꼭 필요하다면, 서버가 명시적으로 허용해야 한다.
이때 사용하는 게 CORS (Cross-Origin Resource Sharing)이다.
서버가 HTTP 응답 헤더에 다음과 같이 설정하면 브라우저가 허용한다
Access-Control-Allow-Origin: https://example.com |
핵심은 서버가 허용하는 클라이언트를 명확히 지정해야한다는 것이다.
예를 들어, React 앱이 http://localhost:3000에서 실행되고 있다면 백엔드는 다음과 같이 응답 헤더를 보내면 된다.
Access-Control-Allow-Origin: http://localhost:3000 |
CORS(Cross-Origin Resource Sharing)
CORS 개념
CORS(Cross-Origin Resource Sharing)는 교차 출처 요청을 안전하게 허용하기 위한 브라우저의 보안 정책이다.
기본적으로 브라우저는 SOP(Same-Origin Policy, 동일 출처 정책)을 따르며, 이는 보안을 위해 다음 조건을 모두 만족해야 요청을 허용한다.
- 프로토콜(http/https)
- 도메인(example.com)
- 포트번호(80/443 등)
즉, http://example.com 과 https://example.com 은 다른 출처(origin)로 간주되며 기본적으로 자바스크립트에서의 요청은 차단된다.
CORS 필요성
- 예를 들어, 프론트엔드(React SPA)는 http://localhost:3000에서 구동되고, 백엔드는 http://localhost:8080에서 API를 제공한다고 하자.
- 이때 프론트엔드에서 백엔드로 fetch 요청을 보내면, 출처가 다르므로 브라우저가 SOP 정책에 따라 이를 차단한다.
- 이러한 합법적인 요청을 허용하기 위해 서버가 "내가 특정 출처에서 오는 요청을 허용할게" 라고 명시적으로 알려줘야 하는데, 이때 사용하는 메커니즘이 바로 CORS이다.
작동 과정
프론트엔드 앱이 http://localhost:3000에서 구동되고 있다고 가정
단순(GET, POST, HEAD) 요청
POST라도 Content-Type이 특별하면 비단순 처리된다.
Content-Type이 다음 중 하나가 아닌 경우
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
사용자 지정 헤더 존재
- Authorization, X-Custom-Header 등
브라우저는 Origin 헤더만 붙여서 서버에 보낸다.
Origin: http://localhost:3000 |
서버는 응답 헤더에 다음과 같이 응답하면 성공이다.
Access-Control-Allow-Origin: http://localhost:3000 |
만약 이 헤더가 없거나, 다르면 브라우저가 응답을 차단한다
특별(POST + JSON, PUT, DELETE) 요청
POST + JSON, PUT, DELETE 니 "안전하지 않은 메서드나 헤더"가 포함된 요청일 경우 브라우저는 먼저 서버에 OPTIONS 메서드로 사전 요청(preflight)을 보낸다.
OPTIONS /api/data HTTP/1.1 Origin: http://localhost:3000 Access-Control-Request-Method: POST Access-Control-Request-Headers: Content-Type |
서버는 이에 아래와 같이 응답해야 한다.
Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Methods: POST, OPTIONS Access-Control-Allow-Headers: Content-Type |
이렇게 응답하면, 브라우저는 이후 실제 POST 요청을 수행하게 된다.
주의해야할 점
- Access-Control-Allow-Methods에는 반드시 요청하려는 메서드가 포함되어야 한다.
- Access-Control-Allow-Headers는 요청 헤더에 있는 모든 커스텀 헤더를 포함해야 한다.
- Access-Control-Allow-Origin은 Origin과 정확히 일치해야 한다 (credentials 사용하는 경우).
스프링 부트에서 설정
스프링 시큐리티
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(Customizer.withDefaults()) // CORS 활성화 (WebMvcConfigurer의 설정 사용)
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // (1)
.allowedOrigins("http://localhost:3000") // (2)
.allowedMethods("GET", "POST", "PUT", "DELETE") // (3)
.allowedHeaders("Authorization") // (4)
.allowCredentials(true); // (5)
}
}
- 요청 URL이 /api/** 경로일 것
- 요청 출처가 http://localhost:3000일 것
- HTTP 메서드가 GET, POST, PUT, DELETE 중 하나일 것
- 요청에 Authorization 헤더를 포함할 수 있음 (꼭 포함해야 한다는 뜻은 아님)
- 인증 정보(쿠키 또는 헤더)를 포함하는 요청을 허용 (credentials: 'include' 가능)
프론트엔드 코드에서도 아래와 같이 설정되어있어야 한다.
fetch('http://localhost:8080/api/user/profile', {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + accessToken
},
credentials: 'include' // 이게 없으면 allowCredentials(true) 설정도 무효
});
credentials
브라우저는 보안 강화를 위해, 크로스 오리진 요청에서는 기본적으로 쿠키나 인증 정보를 자동으로 포함시키지 않는다.
하지만, 다음과 같은 정보들을 명시적으로 포함하려면 credentials: true 설정이 필요하다.
- Cookie (세션 기반 인증용)
- Authorization 헤더 (ex: Bearer 토큰)
- TLS 클라이언트 인증서
'네트워크 > 보안' 카테고리의 다른 글
[보안] CSP(Content Security Policy) (0) | 2025.05.28 |
---|---|
[보안] CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조) (0) | 2025.05.28 |
[보안] JWT(Json Web Token) (0) | 2025.05.27 |