-
Notifications
You must be signed in to change notification settings - Fork 4
SOP & CORS
SOP(동일 출처 정책, Same-Origin Policy)는 웹 브라우저 보안을 위해 Same-Origin(프로토콜, 호스트, 포트가 같은 출처)의 서버로만 리소스를 주고 받도록 상호작용을 제한하는 보안 정책이다. SOP의 정책은 웹 페이지의 자바스크립트가 다른 출처(Origin)로부터 리소스를 요청하는 것을 방지함으로써 정보 누출과 CSRF(Cross-Site Request Forgery)와 같은 공격을 방지한다.
- 도메인(Hostname):
myshop.com - 출처(Origin):
https://www.myshop.com

- 웹 페이지의 자바스크립트가 다른 출처(Origin)로부터 리소스를 요청하는 것을 방지함으로 써 정보 누출 방지
- CSRF(Cross-Site Request Forgery) 공격 방지
- SOP는 다른 출처로부터 API 호출을 제한하여 웹 애플리케이션에서 외부 서비스의 데이터를 활용하기 어려움
- 다른 출처로부터 리소스 요청을 가능하도록 하기 위해 CORS(Cross-Origin Resource Sharing) 정책 구현이 필수
CORS(교차 출처 리소스 공유, Cross-Origin Resource Sharing) 를 설정한다는 의미는 ‘출처가 다른 서버 간의 리소스 공유’를 허용한다는 것이다. SOP가 서로 다른 출처일 때 리소스 요청과 응답을 차단하는 정책이라면, CORS는 반대로 서로 다른 출처라도 리소스 요청, 응답을 허용할 수 있도록 하는 정책이다.
뒤에 나올 해결 방법에서 사용되는 헤더인 Access-Control-Allow-Origin도 ‘허용되는 출처에 대한 접근제어’는 의미라고 이해할 수 있다.
아래 예시를 통해 어떤 URL이 SOP에 부합하는지 한 번 확인한다.

서버에서 Access-Control-Allow-Origin 헤더를 설정해서 요청을 수락할 출처를 명시적으로 지정할 수 있다.
이 헤더를 세팅하면 출처가 다르더라도 https://myshop.com의 리소스 요청을 허용하게 된다.
'Access-Control-Allow-Origin': <origin> | **를 설정하면 출처에 상관없이 리소스에 접근할 수 있는 와일드카드이기 때문에 보안에 취약하다.
따라서 'Access-Control-Allow-Origin': https://myshop.com과 같이 직접 허용할 출처를 세팅하는 방법을 권장한다.
CORS 종류는 Simple Request, Preflight Request, Credential Request, Non-Credential Request 네 가지이다.
이번 학습에서는 Simple Request, Preflight Request 에 대해 학습한다.
Simple Request 는 예비 요청(Prefilght Request) 과정 없이 자동으로 CORS 가 작동하여 서버에 본 요청을 한 후, 서버가 응답의 헤더에
Access-Control-Allow-Origin 과 같은 값 을 전송하면 브라우저가 서로 비교 후 CORS 정책 위반여부를 검사하는 방식이다.
뒤에서 학습할 예비 요청(Preflight Request) 와 단순 요청 시나리오는 전반적인 로직 자체는 같지만, 예비 요청의 존재 유무만 다르다.
요청 시나리오는 아래 조건들을 모두 만족해야만 예비 요청을 생략할 수 있다.
- 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
- 헤더는 Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width Width 만 가능하고, Custom Header 를 포함한 나머지 헤더는 허용되지 않는다.
- 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain 만 허용된다.
이 중 2, 3번 조건이 까다롭다. 사용자 인증에 사용되는 Authorization 헤더도 사용하면 안되고, 대부분의 HTTP API 에서 사용되는 text/xml 이나 application/json 컨텐츠 타입도 가지면 안된다. 따라서 Simple Request 요청 시나리오는 현실적으로 조건을 만족시키기 어렵다.

일반적으로 웹 개발 시 가장 자주 마주치게 되는 요청 시나리오로, 이 시나리오에 해당되는 상황일 경우 브라우저는 요청을 예비 요청(Preflight Request)과 본 요청으로 나누어 서버에 전송하게 된다.
이때 브라우저가 본 요청을 보내기 전에 보내는 예비 요청을 Preflight 라고 부른다. 이는 본 요청을 보내기 전 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하는 과정으로, HTTP 메소드 중 OPTIONS 메소드가 사용된다.


브라우저가 보낸 요청을 보면 출처(Origin)에 대한 정보 뿐만 아니라 예비 요청 이후에 전송할 본 요청에 대한 다른 정보들도 함께 포함되어 있는 것을 볼 수 있다.
해당 예비 요청에서 브라우저는 Access-Control-Request-Headers 를 사용하여 자신이 본 요청에서 Content-Type 헤더를 사용할 것을 알려주거나, Access-Control-Request-Method를 사용하여 GET 메소드를 사용할 것을 서버에게 미리 알려주고 있다.

서버가 보내준 응답 헤더에 포함된 Access-Control-Allow-Origin: https://security.io 의 의미는 해당 URL 이 아닌 다른 출처로 요청할 경우에는 CORS 정책을 위반했다고 판단하고 오류 메시지를 내고 응답을 거부한다.

Access-Control-Allow-Origin
- 헤더에 작성된 출처만 브라우저가 리소스를 접근할 수 있도록 허용한다.
*, https://security.io
Access-Control-Allow-Methods
- 예비 요청(Preflight Request) 에 대한 응답으로 실제 요청 중에 사용할 수 있는 메서드를 나타낸다.
- 기본값은
GET,POST,HEAD,OPTIONS, *
Access-Control-Allow-Headers
- 예비 요청(Preflight Request)에 대한 응답으로 실제 요청 중에 사용할 수 있는 헤더 필드 이름을 나타낸다.
- 기본값은
Origin,Accept,X-Requested-With,Content-Type, Access-Control-Request-Method,Access-Control-Request-Headers, Custom Header, *
Access-Control-Allow-Credentials
- 실제 요청에 쿠기나 인증 등의 사용자 자격 증명이 포함될 수 있음을 나타낸다.
- 클라이언트(Client) 의
credentials:include옵션일 경우true는 필수이다.
Access-Control-Max-Age
- 예비 요청(Preflight Request) 결과를 캐시 할 수 있는 시간을 나타내는 것으로 해당 시간 동안은 예비 요청을 다시 하지 않게 된다.
CORS 의 사전 요청(Preflight request)에는 쿠키(JSESSIONID)가 포함되어 있지 않기 때문에 Spring Security 가 수행되기 이전에 처리되어야 한다. 사전 요청에 쿠키가 없는 상태에서 Spring Security 가 가장 먼저 처리되면, 해당 요청은 사용자가 인증되지 않았다고 판단하고 거부할 수 있다. CORS 가 먼저 처리되도록 하기 위해서 CorsFilter 를 사용할 수 있으며 CorsFilter 에 CorsConfigurationSource 를 제공함으로써 Spring Security 와 통합 할 수 있다.
@RequiredArgsConstructor
@Configuration
public class CustomSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(httpSecurityCorsConfigurer ->
httpSecurityCorsConfigurer.configurationSource(corsConfigurationSource()));
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOriginPatterns(List.of("*")); // 어디서든 허락
corsConfiguration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"));
corsConfiguration.setAllowedHeaders(List.of("Authorization", "Cache-Control", "Content-Type"));
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}CorsConfiguration 클래스 메서드
CorsConfiguration 클래스는 CORS를 구성하는 데 사용되는 메서드들을 포함하고 있으며 각 메서드들의 역할은 다음과 같다.
-
setAllowedOrigins(List<String> allowedOrigins)- 허용된 출처(Origin)를 설정(클라이언트가 요청을 보낼 수 있는 출처의 목록)
- 여러 개의 출처를 허용할 수 있으며, 각 출처는 문자열로 표시한다.
-
setAllowedMethods(List<String> allowedMethods)- 허용된 HTTP 메서드를 설정(클라이언트가 사용할 수 있는 HTTP 메서드의 목록)
- 주로
GET,POST,PUT,DELETE등의 메서드가 사용된다.
-
setAllowedHeaders(List<String> allowedHeaders)- 허용된 요청 헤더를 설정(클라이언트가 요청 헤더에 포함할 수 있는 목록)
- 일반적으로는 인증 헤더인
Authorization과 컨텐츠 유형 헤더인Content-Type이 포함된다.
-
setExposedHeaders(List<String> exposedHeaders)- 노출할 응답 헤더를 설정(클라이언트에게 응답으로 보낼 헤더의 목록)
- 클라이언트에서 접근해야 하는 특정 헤더가 있을 경우 여기에 추가한다.
-
setAllowCredentials(boolean allowCredentials)- 자격 증명 허용 여부를 설정
- 클라이언트가 요청에 자격 증명(예: 쿠키, 인증 헤더)을 포함할 수 있는지 여부를 결정한다.
-
setMaxAge(long maxAge)- 사전 검증(PreFlight) 요청의 유효 기간을 설정
- 클라이언트가 사전 검증 요청의 결과를 캐시할 수 있는 시간(초 단위)을 지정한다.
-
addAllowedOrigin(String allowedOrigin)- 특정 출처를 추가(이 메서드를 사용하여 허용된 출처를 추가)
-
addAllowedMethod(String allowedMethod)- 특정 HTTP 메서드를 추가(이 메서드를 사용하여 허용된 HTTP 메서드를 추가)
-
addAllowedHeader(String allowedHeader)- 특정 요청 헤더를 추가(이 메서드를 사용하여 허용된 요청 헤더를 추가)
CorsConfigurationSource 인터페이스는 CORS 정책을 정의하는 인터페이스이다.
// 인증 및 인가에 대한 설명은 생략한다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}
// CORS 설정은 생략됨
}CORS 설정과 인증 및 인가 두 방식으로 SOP를 준수할 수 있다.