인프런/스프링 시큐리티 완전 정복 [6.x 개정판]

16) 악용보호 - CORS

backend dev 2024. 10. 25.

CORS(Cross Origin Resource Sharing, 교차 출처 리소스 공유)

 

 

 

웹에서는 보안을 위해 기본적으로 한 웹 페이지(출처 A)에서 다른 웹 페이지(출처 B)의 데이터를 직접 불러오는 것을 제한하는데 이를 '동일 출처 정책(Same-Origin Policy)' 이라고 한다

 

• 만약 다른 출처의 리소스를 안전하게 사용하고자 할 경우 CORS 가 등장하며 CORS는 특별한 HTTP 헤더를 통해 한 웹 페이지가 다른 출처의 리소스에 접근할 수 있도록 '허가'를 구하는 방법이라 할 수 있다. 즉, 웹 애플리케이션이 다른 출처의 데이터를 사용하고자 할 때, 브라우저가 그 요청을 대신해서 해당 데이터를 사용해도 되는지 다른 출처에게 물어보는 것이라 할 수 있다

 

 

출처를 비교하는 로직은 서버에 구현된 스펙이 아닌 브라우저에 구현된 스펙 기준으로 처리되며 브라우저는 클라이언트의 요청 헤더와 서버의 응답헤더를 비교해서 최종 응답을 결정한다

[

  • 웹 애플리케이션의 요청:
    • 예를 들어, https://myapp.com이라는 웹 애플리케이션이 있고, 이 애플리케이션이 https://api.example.com/data에서 데이터를 가져오려고 한다고 가정해 봅시다.
    • 두 URL은 서로 다른 출처이므로 CORS 정책에 의해 브라우저는 이 요청을 그냥 허용하지 않고, 서버에 먼저 물어봐야 합니다.
  • 브라우저가 서버에 요청:
    • 브라우저는 먼저 https://api.example.com 서버에 "이 요청을 허용할 수 있습니까?"라는 의미의 CORS 요청을 보냅니다. 이때, 이 요청을 "Preflight 요청"이라고도 부르는데, 이는 서버에게 요청을 허용할지 여부를 묻는 사전 요청입니다.
  • 서버의 응답:
    • 서버는 Access-Control-Allow-Origin이라는 HTTP 헤더를 통해 브라우저에게 이 요청이 허용되는지 알려줍니다.
      • 예를 들어, 서버가 "허용"이라고 응답하면, 브라우저는 https://api.example.com/data에서 데이터를 받아올 수 있습니다.
      • 만약 서버가 "허용하지 않음"이라고 응답하거나, 아예 응답을 하지 않으면 브라우저는 해당 데이터를 가져오지 않고 요청을 차단합니다

 

]

 

두개의 출처를 비교하는 방법은 URL의 구성요소 중 Protocol, Host, Port 이 세가지가 동일한지 확인하면 되고 나머지는 틀려도 상관없다

[

출처를 비교하는 기준은 Protocol(프로토콜), Host(호스트, 도메인), 그리고 Port(포트)가 동일한지 확인하는 것입니다.

  • Protocol: http와 https는 서로 다른 프로토콜로 간주됩니다.
  • Host: example.com과 api.example.com은 서로 다른 도메인입니다.
  • Port: 포트가 명시적으로 다르다면(80, 8080 등), 출처가 다른 것으로 간주됩니다.

나머지가 틀려도 상관없다는 의미

출처 비교에서는 오직 Protocol, Host, Port 세 가지가 중요한 요소입니다.

URL의 나머지 구성 요소인 경로(path)쿼리 파라미터(query parameters) 등은 출처 비교와 관련이 없습니다.

즉, 경로나 쿼리 파라미터가 다르더라도 출처는 동일할 수 있습니다.

예시

  1. 같은 출처: 경로가 다르더라도, Protocol이 https, Host가 example.com, Port가 기본 443으로 동일하므로 같은 출처입니다.
  2. 다른 출처: 프로토콜(https와 http)이 다르기 때문에 다른 출처로 간주됩니다.
  3. 다른 출처: 도메인(example.com과 api.example.com)이 다르므로 다른 출처입니다.
  4. 다른 출처: 포트 번호가 다르므로 다른 출처로 간주됩니다.

따라서 출처 비교에서 중요한 것은 Protocol, Host, Port의 일치 여부이고, 경로나 쿼리 파라미터의 차이는 출처 비교에 영향을 미치지 않습니다.

]

 

 

 

 

  CORS를 Disable 한다는것은?

 

[

cors().disable()을 사용하면 Spring Security에서 CORS 관련 보안 검사를 비활성화하는 것을 의미

 

즉, 클라이언트(다른 출처)가 이 서버로 요청을 보낼 때, CORS 규칙에 따른 출처 검사를 하지 않겠다는 의미입니다.

다른 서버에서 요청을 받을 수 있지만, CORS 관련 보안 검사를 하지 않겠다는 말이 됩니다.

 

 CORS 정책을 사용하지않는다는것

== 허용된 출처에서의 요청인지를 검사하지 않겠다는것

== 모든 출처에서 오는 요청을 다 허용하겠다 

]

 

 

 

domain-a.com 에서 domain-a.com을 요청하는것은 얼마든지 가능하다.

 

domain-a.com 에서 domain-b.com을 요청하는것은 Cross-origin 요청으로서 CORS에 제한을 받는다.

 

 

https://domain-a.com 의 프론트 엔드 JavaScript 코드가 XMLHttpRequest를 사용하여 https://domain-b.com/data.json을 요청하는 경우 보안 상의 이유로, 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한한다.

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

**동일 출처 정책(SOP)**은 웹 보안을 위해 브라우저에서 적용하는 기본적인 보안 메커니즘입니다. 이 정책은 한 출처(origin)에서 실행되는 스크립트가 다른 출처의 리소스에 임의로 접근하는 것을 제한합니다.

 

 

교차 출처 리소스 공유 (CORS, Cross-Origin Resource Sharing)

CORS는 동일 출처 정책의 예외로, 서버가 명시적으로 교차 출처 요청을 허용할 수 있도록 해주는 메커니즘입니다. 즉, 한 출처에서 다른 출처의 리소스에 접근할 수 있도록 허가를 설정할 수 있는 방식입니다.

 

CORS의 동작 방식

  • 클라이언트(브라우저)가 다른 출처에 리소스를 요청할 때, 서버가 해당 요청을 허용할지 여부를 결정합니다.
  • 서버는 HTTP 응답 헤더Access-Control-Allow-Origin을 포함시켜, 특정 출처(또는 모든 출처)를 허용할 수 있습니다.

CORS의 예시

CORS 요청 흐름

  1. 클라이언트 요청: 클라이언트가 다른 출처로 교차 출처 요청을 보냅니다.
  2. 서버 응답: 서버는 응답 헤더에 Access-Control-Allow-Origin을 포함하여 요청을 허용할지 여부를 브라우저에게 알립니다.
  3. 브라우저 처리: 서버에서 교차 출처 요청이 허용되었다면, 브라우저는 그 요청을 처리하고 데이터를 받아옵니다. 그렇지 않으면 요청이 차단됩니다.

SOP와 CORS의 차이점

  • 동일 출처 정책(SOP): 웹 보안의 기본 원칙으로, 다른 출처 간의 리소스 접근을 기본적으로 제한하는 역할을 합니다.
  • CORS: SOP의 예외 규칙으로, 서버가 명시적으로 교차 출처 요청을 허용할 수 있는 메커니즘입니다.

CORS는 SOP가 너무 엄격하게 동작할 경우, 이를 유연하게 처리할 수 있게 하여 다양한 API나 리소스를 서로 다른 도메인에서 사용할 수 있도록 돕는 역할을 합니다.

 

 

CORS 종류

 

1. Simple Request

Simple Request예비 요청(Prefilght) 과정 없이 자동으로 CORS가 작동하여 서버에 본 요청을 한 후,

서버가 응답의 헤더에 Access-Control-Allow-Origin 과 같은 값 을 전송하면 브라우저가 서로 비교 후

CORS 정책 위반여부를 검사하는 방식이다

 

 

제약 사항
• GET, POST, HEAD 중의 한가지 Method를 사용해야 한다


• 헤더는 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 만 가능하다

[ application/json도 안된다. ]

 

 

 

A서비스에서 B서버로 request하면 B 서버가 응답을 보내면서 헤더에 Access-Control-Allow-Origin의 값을 넣어준다.

여기서는 *이므로 모든 출처에 대한 요청을 허가한다는것이다.

 

브라우저가 해당 응답을 보고 A서비스에 응답을 내려준다.

 

 

2. Preflight Request (예비요청)

 

  브라우저는 요청을 한번에 보내지 않고, 예비 요청과 본 요청으로 나누어 서버에 전달하는데 브라우저가 예비요청을 보내는 것을 Preflight 라고 하며 이 예비요청의 메소드에는 OPTIONS이 사용된다

 

예비요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 확인하는 것으로

요청 사양이 Simple Request 에 해당하지 않을 경우 브라우저가 Preflight Request 을 실행한다

 

[Simple Request는 브라우저가 CORS 요청을 보낼 때, 특정 조건을 만족하는 요청에 대해 사전 확인(Preflight) 없이 바로 서버에 요청을 보낼 수 있도록 하는 요청 유형
만약 Simple Request 조건을 하나라도 충족하지 못하면, 브라우저는 안전성을 보장하기 위해 Preflight 요청을 먼저 보냅니다. Preflight 요청은 OPTIONS 메서드를 사용하며, 서버가 요청을 허용할지 확인합니다. ]

 

브라우저 [ A ]

 

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

 

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

 

 

서버 [ B ]

 

서버가 보내준 응답 헤더에 포함된 Access-Control-Allow-Origin: https://security.io 의 의미는

해당 URL 외의 다른 출처로 요청할 경우에는 CORS 정책을 위반했다고 판단하고 오류 메시지를 내고 응답을 버리게 된다

 

 

 

동일 출처 기준

 

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

Access-Control-Allow-Origin

헤더에 작성된 출처만 브라우저가 리소스를 접근할 수 있도록 허용한다

Access-Control-Allow-Origin: *

 

  • 모든 출처의 요청을 허용한다는 뜻입니다.
  • 주로 공개된 리소스(API)나, 누구나 접근할 수 있어야 하는 리소스에 적합합니다.

들어갈수 있는값  *[모든경로 ] , https://security.io [특정경로 ]

 

 

 

Access-Control-Allow-Methods

preflight request 에 대한 응답으로 실제 요청 중에 사용할 수 있는 메서드를 나타낸다

• 설정가능한값은  GET,POST,HEAD,OPTIONS, *

 

서버가 특정 요청 메서드(예: GET, POST, PUT, DELETE)에 대해 교차 출처 요청을 허용할지 여부를 명시하는 HTTP 헤더

 

  • 교차 출처 요청 시 허용할 HTTP 메서드를 서버가 브라우저에 알립니다.
  • Preflight 요청(사전 확인 요청)을 통해 브라우저가 서버에 먼저 허용 여부를 확인하게 되며, 서버는 Access-Control-Allow-Methods에 지정된 메서드만 허용합니다.

예시)

Access-Control-Allow-Methods: GET, POST, PUT, DELETE

 

 

 

Access-Control-Allow-Headers

preflight request 에 대한 응답으로 실제 요청 중에 사용할 수 있는 헤더 필드 이름을 나타낸다

CORS 응답에서 클라이언트가 접근할 수 있는 응답 헤더 목록을 명시합니다.

브라우저는 기본적으로 Content-Type 등의 일부 헤더만 클라이언트에 노출합니다. 추가적인 헤더에 접근하려면 서버가 Access-Control-Expose-Headers를 통해 허용해야 합니다.

 

설정가능한값

Origin,Accept,X-Requested-With,Content-Type, Access-Control-Request-Method,Access-Control-Request-Headers, Custom Header, *

 

예시)

Access-Control-Expose-Headers: X-Total-Count, X-Request-ID

 

 

 

Access-Control-Allow-Credentials

실제 요청에 쿠기나 인증 등의 사용자 자격 증명이 포함될 수 있음을 나타낸다.

Client의 credentials:include 옵션일 경우 true 는 필수

교차 출처 요청에 자격 증명(쿠키, Authorization 헤더 등)을 포함할 수 있는지 여부를 지정합니다.

예시)

Access-Control-Allow-Credentials: true

 

 

Access-Control-Max-Age

 

preflight 요청 결과를 캐시 할 수 있는 시간을 나타내는 것으로 해당 시간동안은 preflight 요청을 다시 하지 않게 된다

Preflight 요청의 응답을 브라우저가 캐시할 수 있는 시간을 초 단위로 지정합니다.

Preflight 요청을 자주 보내지 않게 하여 성능을 최적화하는 데 유용합니다.

 

예시)

Access-Control-Max-Age: 3600

 

 

 

 

cors() & CorsFilter

 

CORS의 사전 요청(pre-flight request)에는 쿠키 (JSESSIONID)가 포함되어 있지 않기 때문에 

Spring Security 이전에 처리되어야 한다

 

사전 요청에 쿠키가 없고 Spring Security가 가장 먼저 처리되면 

요청은 사용자가 인증되지 않았다고 판단하고 거부할 수 있다


CORS 가 먼저 처리되도록 하기 위해서 CorsFilter 를 사용할 수 있으며

 CorsFilter 에 CorsConfigurationSource 를 제공함으로써 Spring Security 와 통합 할 수 있다

 

[사전요청은 실제요청이 아니므로 쿠키가 포함되어있지않다. 하지만 Spring Security는 쿠키[JSESSIONID]가 없으므로 인증되지 않은 사용자로 판단할 수 있다. 그러므로 사전요청은 Spring Security 필터 이전에 처리되야한다.]

 

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.cors(cors -> cors.configurationSource(corsConfigurationSource())); 
        // 커스텀하게 사용할 CorsConfigurationSource 를 설정한다.
		// CorsConfigurationSource 를 설정하지 않으면 Spring MVC 의 CORS 구성을 사용한다
        return http.build();
    }

 

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.addAllowedOrigin("https://example.com"));
    configuration.addAllowedMethod("GET","POST"));
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

위에는 커스텀하게 설정한 CorsConfigurationSoucre 예시코드

 

댓글