Skip to content

SOP & CORS

DAEILLIM edited this page Apr 30, 2024 · 1 revision

1. SOP (Same Origin Policy)


SOP(동일 출처 정책, Same-Origin Policy)는 웹 브라우저 보안을 위해 Same-Origin(프로토콜, 호스트, 포트가 같은 출처)의 서버로만 리소스를 주고 받도록 상호작용을 제한하는 보안 정책이다. SOP의 정책은 웹 페이지의 자바스크립트가 다른 출처(Origin)로부터 리소스를 요청하는 것을 방지함으로써 정보 누출과 CSRF(Cross-Site Request Forgery)와 같은 공격을 방지한다.


  • 도메인(Hostname): myshop.com
  • 출처(Origin): https://www.myshop.com

image

SOP 장점

  • 웹 페이지의 자바스크립트가 다른 출처(Origin)로부터 리소스를 요청하는 것을 방지함으로 써 정보 누출 방지
  • CSRF(Cross-Site Request Forgery) 공격 방지

SOP 단점

  • SOP는 다른 출처로부터 API 호출을 제한하여 웹 애플리케이션에서 외부 서비스의 데이터를 활용하기 어려움
  • 다른 출처로부터 리소스 요청을 가능하도록 하기 위해 CORS(Cross-Origin Resource Sharing) 정책 구현이 필수



2. CORS

CORS(교차 출처 리소스 공유, Cross-Origin Resource Sharing) 를 설정한다는 의미는 ‘출처가 다른 서버 간의 리소스 공유’를 허용한다는 것이다. SOP가 서로 다른 출처일 때 리소스 요청과 응답을 차단하는 정책이라면, CORS는 반대로 서로 다른 출처라도 리소스 요청, 응답을 허용할 수 있도록 하는 정책이다.

뒤에 나올 해결 방법에서 사용되는 헤더인 Access-Control-Allow-Origin도 ‘허용되는 출처에 대한 접근제어’는 의미라고 이해할 수 있다.

아래 예시를 통해 어떤 URL이 SOP에 부합하는지 한 번 확인한다.


image




3. CORS 에러 대응하기


서버에서 Access-Control-Allow-Origin 응답 헤더 세팅하기


서버에서 Access-Control-Allow-Origin 헤더를 설정해서 요청을 수락할 출처를 명시적으로 지정할 수 있다.
이 헤더를 세팅하면 출처가 다르더라도 https://myshop.com의 리소스 요청을 허용하게 된다.

'Access-Control-Allow-Origin': <origin> | *

*를 설정하면 출처에 상관없이 리소스에 접근할 수 있는 와일드카드이기 때문에 보안에 취약하다.
따라서 'Access-Control-Allow-Origin': https://myshop.com과 같이 직접 허용할 출처를 세팅하는 방법을 권장한다.




4. CORS 종류

CORS 종류는 Simple Request, Preflight Request, Credential Request, Non-Credential Request 네 가지이다.
이번 학습에서는 Simple Request, Preflight Request 에 대해 학습한다.


Simple Request

Simple Request 는 예비 요청(Prefilght Request) 과정 없이 자동으로 CORS 가 작동하여 서버에 본 요청을 한 후, 서버가 응답의 헤더에 Access-Control-Allow-Origin 과 같은 값 을 전송하면 브라우저가 서로 비교 후 CORS 정책 위반여부를 검사하는 방식이다.

뒤에서 학습할 예비 요청(Preflight Request) 와 단순 요청 시나리오는 전반적인 로직 자체는 같지만, 예비 요청의 존재 유무만 다르다.
요청 시나리오는 아래 조건들을 모두 만족해야만 예비 요청을 생략할 수 있다.

  1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
  2. 헤더는 Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width Width 만 가능하고, Custom Header 를 포함한 나머지 헤더는 허용되지 않는다.
  3. 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain 만 허용된다.

이 중 2, 3번 조건이 까다롭다. 사용자 인증에 사용되는 Authorization 헤더도 사용하면 안되고, 대부분의 HTTP API 에서 사용되는 text/xml 이나 application/json 컨텐츠 타입도 가지면 안된다. 따라서 Simple Request 요청 시나리오는 현실적으로 조건을 만족시키기 어렵다.

image


Rreflight Request

일반적으로 웹 개발 시 가장 자주 마주치게 되는 요청 시나리오로, 이 시나리오에 해당되는 상황일 경우 브라우저는 요청을 예비 요청(Preflight Request)과 본 요청으로 나누어 서버에 전송하게 된다.

이때 브라우저가 본 요청을 보내기 전에 보내는 예비 요청을 Preflight 라고 부른다. 이는 본 요청을 보내기 전 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하는 과정으로, HTTP 메소드 중 OPTIONS 메소드가 사용된다.

image


Reflight Request - JS

image

브라우저가 보낸 요청을 보면 출처(Origin)에 대한 정보 뿐만 아니라 예비 요청 이후에 전송할 본 요청에 대한 다른 정보들도 함께 포함되어 있는 것을 볼 수 있다.

해당 예비 요청에서 브라우저는 Access-Control-Request-Headers 를 사용하여 자신이 본 요청에서 Content-Type 헤더를 사용할 것을 알려주거나, Access-Control-Request-Method를 사용하여 GET 메소드를 사용할 것을 서버에게 미리 알려주고 있다.

image

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




5. 동일 출처 기준

image




6. CORS 해결 - 서버에서 Access-Control-Allow-* 세팅

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) 결과를 캐시 할 수 있는 시간을 나타내는 것으로 해당 시간 동안은 예비 요청을 다시 하지 않게 된다.



7. cors() & CorsFilter

CORS 의 사전 요청(Preflight request)에는 쿠키(JSESSIONID)가 포함되어 있지 않기 때문에 Spring Security 가 수행되기 이전에 처리되어야 한다. 사전 요청에 쿠키가 없는 상태에서 Spring Security 가 가장 먼저 처리되면, 해당 요청은 사용자가 인증되지 않았다고 판단하고 거부할 수 있다. CORS 가 먼저 처리되도록 하기 위해서 CorsFilter 를 사용할 수 있으며 CorsFilter 에 CorsConfigurationSource 를 제공함으로써 Spring Security 와 통합 할 수 있다.


CORS 설정

@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를 준수할 수 있다.