이 장에서는 다음을 다룹니다.
- 교차 출처(Cross-Origin) 리소스 공유(CORS)란 무엇인가?
- 교차 출처 리소스 공유 구성 적용하기
이 장에서는 교차 출처 리소스 공유(CORS)에 대해 논의하고 Spring Security와 함께 적용하는 방법을 다룹니다. 먼저, CORS란 무엇이며 왜 중요한지 알아봅니다. CORS가 필요한 이유는 웹 애플리케이션의 구조에서 비롯됩니다. 기본적으로, 브라우저는 사이트가 로드된 도메인 외의 다른 도메인에 대한 요청을 허용하지 않습니다. 예를 들어, example.com에서 사이트에 접근한 경우, 브라우저는 사이트가 api.example.com에 요청을 보내는 것을 허용하지 않습니다.
웹 개발을 하다 보면 다른 출처(origin)의 리소스를 사용해야 할 때가 있습니다. 여기서 출처란 프로토콜, 호스트(도메인), 포트를 의미합니다. 예를 들어, http://example.com에서 서비스되는 웹 페이지가 https://api.example.com에서 데이터를 가져오려고 하는 경우, 두 출처는 프로토콜(http와 https)과 서브도메인이 다르기 때문에 '다른 출처'로 간주됩니다.
이런 상황에서는 보안상의 이유로 브라우저의 '동일 출처 정책(Same-Origin Policy)'이 작동하여, 기본적으로 다른 출처에 대한 요청을 제한합니다. 동일 출처 정책은 웹 보안의 중요한 부분이며, XSS(Cross-Site Scripting) 공격과 같은 여러 보안 위협으로부터 사용자를 보호하는 데 도움을 줍니다.
그러나 현대의 웹 애플리케이션은 다양한 출처에서 리소스를 가져와 사용하는 경우가 많습니다. 이를 위해 CORS 정책이 도입되었습니다. CORS는 웹 애플리케이션에서 다른 출처의 리소스에 안전하게 접근할 수 있도록 하는 메커니즘을 제공합니다. 서버 측에서는 특정 출처에서의 요청을 허용하거나, 모든 출처에서 요청을 허용할 수 있는 설정을 할 수 있습니다. 예를 들어, api.example.com 서버는 CORS 헤더인 Access-Control-Allow-Origin을 사용하여 example.com에서 오는 요청을 허용할 수 있습니다. 이 헤더를 응답에 포함시키면, 브라우저는 example.com이 api.example.com의 데이터를 요청하는 것을 허용합니다.
Spring Security와 함께 CORS를 구성하는 것은 이러한 허용 과정을 보다 세밀하게 제어할 수 있게 해줍니다. 예를 들어, 어떤 HTTP 메서드(GET, POST 등)를 허용할지, 어떤 헤더를 허용할지, 인증 정보(쿠키 등)의 사용을 허용할지 등을 설정할 수 있습니다. Spring Security의 CORS 설정은 보안과 관련된 다른 설정들과 함께 중앙 집중적으로 관리될 수 있어, 애플리케이션의 보안을 강화하는 데 도움이 됩니다.
이러한 CORS 설정을 통해, 개발자는 웹 애플리케이션의 보안을 유지하면서도 필요에 따라 다양한 출처의 리소스를 자유롭게 활용할 수 있는 유연성을 얻을 수 있습니다.
교차 출처 리소스 공유(CORS). example.com에서 접근했을 때, 웹사이트는 api.example.com으로 요청을 할 수 없습니다. 왜냐하면 그것들은 교차 도메인 요청이기 때문입니다.
간단히 말해서, 앱은 CORS 메커니즘을 사용하여 이 엄격한 정책을 완화하고 일정 조건에서 다른 출처 간의 요청을 허용합니다. 프론트엔드와 백엔드가 별도의 애플리케이션인 현재와 같은 시대에, 특히 애플리케이션에 이를 적용해야 할 가능성이 높기 때문에 이를 알아야 합니다. Angular, ReactJS, Vue와 같은 프레임워크를 사용하여 개발된 프론트엔드 애플리케이션이 example.com과 같은 도메인에서 호스팅되고, 다른 도메인인 api.example.com에 호스팅된 백엔드의 엔드포인트를 호출하는 것은 흔한 일입니다.
이 장에서는 웹 애플리케이션에 CORS 정책을 적용하는 방법을 배울 수 있는 몇 가지 예제를 개발합니다. 또한 애플리케이션에 보안 취약점을 남기지 않도록 주의해야 할 몇 가지 세부 사항도 설명합니다.
10.1 CORS는 어떻게 작동하나요?
이 섹션에서는 CORS(Cross-Origin Resource Sharing)가 웹 애플리케이션에서 어떻게 작동하는지에 대해 설명합니다. 예를 들어, 여러분이 example.com의 소유주이고, example.org 개발자들이 그들의 웹사이트에서 여러분의 REST 엔드포인트(api.example.com)를 호출하려고 한다면, 기본적으로 그들은 이를 할 수 없습니다. 이는 동일 출처 정책(Same-Origin Policy) 때문에 발생하는 문제입니다. 동일 출처 정책은 웹 브라우저가 다른 출처에서의 리소스를 로드하는 것을 제한하는 보안 기능입니다.
CORS와 동일 출처 정책
웹 애플리케이션이 다른 출처의 리소스를 호출할 때, 브라우저는 기본적으로 이를 차단합니다. 예를 들어, example.com에서 호스팅되는 웹페이지가 example.org의 리소스를 iframe으로 삽입하려는 경우, 또는 example.com이 api.example.com의 데이터를 요청하려는 경우, 브라우저는 이를 보안상 이유로 막습니다.
이런 제한을 해결하기 위해 CORS가 사용됩니다. CORS는 서버가 특정 출처에서의 요청을 허용할 수 있도록 하는 메커니즘입니다. CORS 정책은 HTTP 헤더를 기반으로 작동하며, 이 헤더들은 서버에서 보내는 응답에 포함되어 브라우저에 어떤 출처의 웹 페이지가 리소스에 접근할 수 있는지를 알려줍니다.
CORS 정책을 설정하는 주요 HTTP 헤더
- Access-Control-Allow-Origin: 서버가 응답에서 이 헤더를 포함시켜, 특정 출처(origin)의 웹 페이지가 해당 리소스에 접근할 수 있도록 허용합니다.
- 예: Access-Control-Allow-Origin: https://example.com은 example.com에서 온 요청만 허용.
- *를 사용하면 모든 출처에서의 접근을 허용.
- Access-Control-Allow-Methods: 특정 HTTP 메소드만을 허용할 수 있도록 설정합니다. 예를 들어 GET, POST 등 제한할 수 있습니다.
- Access-Control-Allow-Headers: 요청에서 사용할 수 있는 헤더를 제한합니다. 예를 들어 특정 요청에서 어떤 헤더를 사용하지 못하도록 할 수 있습니다.
브라우저의 역할
브라우저는 CORS 정책을 자동으로 적용합니다. 예를 들어, 웹 페이지가 다른 출처의 리소스를 요청할 때, 브라우저는 응답 헤더에서 Access-Control-Allow-Origin을 확인하여, 해당 출처가 허용된 출처인지 검사합니다. 만약 해당 출처가 허용되지 않으면, 브라우저는 요청을 차단하고, 웹 페이지는 리소스를 사용할 수 없습니다.
사전 요청(Preflight Request)
특정 조건에서는 브라우저가 실제 요청을 보내기 전에 사전 요청(preflight request)을 보냅니다. 이 사전 요청은 HTTP OPTIONS 메소드로, 서버가 특정 요청을 허용하는지 확인하는 과정입니다. 예를 들어, POST 요청을 보내기 전에 서버가 이 메소드를 허용하는지 확인하는 요청이 사전 요청에 해당합니다. 만약 이 사전 요청이 실패하면, 실제 요청은 실행되지 않습니다.
CORS 설정을 하지 않았을 때 발생하는 문제
Spring Security를 사용하여 CORS를 설정하지 않으면, 교차 출처 요청을 차단하는 기본 동작이 발생합니다. 예를 들어, localhost와 127.0.0.1은 서로 다른 출처로 간주되기 때문에, localhost에서 실행되는 웹 페이지가 127.0.0.1에서 제공되는 API에 접근하려고 하면, CORS 정책에 의해 요청이 차단됩니다.
다음은 이러한 동작을 실험하기 위한 간단한 예제 코드입니다. CORS 설정을 하지 않았을 때, localhost에서 127.0.0.1으로 보내는 요청은 Access-Control-Allow-Origin 헤더가 없어서 차단됩니다. 브라우저의 콘솔에서 다음과 같은 오류 메시지를 확인할 수 있습니다:
Access to XMLHttpRequest at 'http://127.0.0.1:8080/test' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
이 오류는 Access-Control-Allow-Origin 헤더가 없기 때문에 발생하며, 이 문제를 해결하려면 서버에서 이 헤더를 설정해야 합니다.
결론
CORS는 도메인 간의 요청을 허용하는 중요한 보안 메커니즘입니다. 이를 통해 서버는 어떤 출처에서 오는 요청을 허용할지, 어떤 HTTP 메소드를 허용할지 등을 설정할 수 있습니다. CORS를 적절히 설정하지 않으면, 브라우저에서 요청을 차단하고, 애플리케이션은 리소스에 접근할 수 없게 됩니다.
10.2 Applying CORS policies with the @CrossOrigin annotation
이 섹션에서는 @CrossOrigin 어노테이션을 사용하여 CORS(Cross-Origin Resource Sharing) 정책을 엔드포인트별로 적용하는 방법에 대해 설명합니다. @CrossOrigin 어노테이션을 사용하면 각 엔드포인트에 대해 허용되는 출처와 메서드를 쉽게 설정할 수 있습니다. 이를 통해 다른 도메인에서 오는 요청을 허용하거나 제한할 수 있습니다.
CORS 동작 방식
CORS 요청은 두 가지 유형이 있습니다: 단순 요청과 비단순 요청.
- 단순 요청(Simple Request):
- GET, POST, HEAD와 같은 안전한 메서드로 요청할 수 있으며, 추가적인 검사 없이 요청이 처리됩니다.
- 예를 들어, POST 요청을 보낼 때, 브라우저는 미리 OPTIONS 요청을 보내지 않고 바로 요청을 전송합니다.
- 비단순 요청(Non-Simple Request):
- 보안이 민감한 PUT, DELETE와 같은 메서드나, 특정 헤더를 포함하는 요청은 OPTIONS 메서드로 사전 요청(preflight request)을 보내어 서버의 CORS 정책을 확인합니다. 서버가 이 요청을 허용하면 실제 요청이 실행됩니다.
@CrossOrigin 어노테이션 사용
@CrossOrigin을 메소드나 클래스에 적용하면 CORS 설정을 쉽게 관리할 수 있습니다. 예를 들어:
@PostMapping("/test")
@ResponseBody
@CrossOrigin("http://localhost:8080") // localhost에서 오는 요청을 허용
public String test() {
logger.info("Test method called");
return "HELLO";
}
위 코드에서 @CrossOrigin 어노테이션을 사용하여 http://localhost:8080에서 오는 요청을 허용합니다. @CrossOrigin을 사용하면 특정 출처를 명시하거나, 여러 출처를 배열로 지정할 수 있습니다.
CORS 정책 설정
- allowedOrigins: 허용되는 출처를 설정합니다.
- allowedMethods: 허용되는 HTTP 메서드를 설정합니다.
- allowedHeaders: 요청 헤더에 대한 허용 설정을 합니다.
- exposedHeaders: 응답 헤더를 설정합니다.
예를 들어, 여러 출처를 허용하려면 다음과 같이 설정할 수 있습니다.
@CrossOrigin(origins = {"http://example.com", "http://example.org"})
보안 고려 사항
모든 출처를 허용하는 * 설정은 보안상 위험할 수 있습니다. 예를 들어, 테스트 환경에서는 여러 출처를 허용할 수 있지만, 프로덕션 환경에서는 특정 도메인만을 허용하는 것이 좋습니다. 또한, 서버에서 모든 요청을 허용하는 경우, 잠재적으로 XSS(교차 사이트 스크립팅) 공격이나 DDoS 공격에 노출될 수 있습니다.
CORS 정책의 테스트
웹 페이지에서 CORS를 테스트할 때, localhost와 127.0.0.1을 다른 도메인처럼 설정하여 OPTIONS 요청을 보냅니다. 이 사전 요청이 성공하면, 실제 POST, PUT 등의 요청이 전송됩니다.
요약
@CrossOrigin 어노테이션을 사용하면 각 엔드포인트에 대해 CORS 정책을 설정할 수 있습니다. 이를 통해 다른 출처에서의 요청을 세밀하게 제어할 수 있으며, 보안을 고려한 적절한 설정을 통해 애플리케이션을 안전하게 보호할 수 있습니다.
10.3 Applying CORS using a CorsConfigurer
이 섹션에서는 @CrossOrigin 어노테이션 대신, CORS 구성을 중앙 집중식으로 설정하는 방법에 대해 설명합니다. HttpSecurity 객체의 cors() 메서드를 사용하여 CORS 정책을 설정할 수 있습니다. 이를 통해 애플리케이션 전반에 걸쳐 일관된 CORS 규칙을 정의할 수 있습니다.
CORS 구성을 중앙에서 정의하기
HttpSecurity 객체에서 cors() 메서드를 사용하여 CORS 구성을 설정할 수 있습니다. 이 방법은 여러 엔드포인트에서 @CrossOrigin 어노테이션을 사용하는 것보다 더 편리할 수 있습니다. 다음은 이를 구현하는 예제입니다.
@Configuration
public class ProjectConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.cors(c -> { // #A
CorsConfigurationSource source = request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("*")); // 모든 출처 허용
config.setAllowedMethods(List.of("*")); // 모든 HTTP 메서드 허용
config.setAllowedHeaders(List.of("*")); // 모든 헤더 허용
return config;
};
c.configurationSource(source); // CORS 정책을 설정
});
http.csrf(c -> c.disable()); // CSRF 보호 비활성화
http.authorizeHttpRequests(c -> c.anyRequest().permitAll()); // 모든 요청 허용
return http.build(); // 보안 필터 체인 빌드
}
}
설명
- cors() 메서드 호출:
- HttpSecurity 객체에서 cors() 메서드를 호출하여 CORS 구성을 설정합니다. 이 메서드는 Customizer<CorsConfigurer> 객체를 매개변수로 받습니다.
- CorsConfigurationSource 설정:
- CorsConfigurationSource는 HTTP 요청에 대한 CORS 구성을 반환하는 역할을 합니다. 여기서 CorsConfiguration 객체를 생성하고, 허용된 출처(allowedOrigins), 메서드(allowedMethods), 헤더(allowedHeaders) 등을 설정합니다.
- 이 예제에서는 모든 출처, 메서드, 헤더를 허용하도록 설정하였습니다.
- 구성의 분리:
- 위의 예제에서는 SecurityFilterChain 빈을 사용하여 CORS 구성을 람다 표현식으로 직접 설정했습니다. 하지만 실제 애플리케이션에서는 코드가 길어질 수 있으므로, CORS 구성을 별도의 클래스나 메서드로 분리하는 것이 좋습니다.
보안 및 권장 사항
- 보안 고려: 예제에서는 allowedOrigins에 *를 사용하여 모든 출처를 허용하고 있습니다. 실제 애플리케이션에서는 특정 출처만 허용하는 것이 좋습니다. 모든 출처를 허용하면 보안상 취약점이 생길 수 있습니다.
- 메서드와 헤더: 기본적으로 CorsConfiguration은 메서드나 헤더를 설정하지 않으므로, 최소한 allowedOrigins와 allowedMethods를 설정해야 합니다. 그렇지 않으면 애플리케이션에서 CORS 요청을 거부할 수 있습니다.
결론
- @CrossOrigin 어노테이션을 사용하여 각 엔드포인트에 대해 CORS를 설정할 수 있지만, 애플리케이션 규모가 커지면 HttpSecurity 객체의 cors() 메서드를 사용하여 중앙에서 CORS를 관리하는 것이 더 효율적입니다.
- 이 방법을 사용하면 CORS 정책을 한 곳에서 관리하고, 애플리케이션의 유지보수성을 높일 수 있습니다.
'Spring Security in Action' 카테고리의 다른 글
12장 Implement filtering at the method level (0) | 2024.12.01 |
---|---|
11장 Implement authorization at the method level (1) | 2024.12.01 |
9장 Configuring Cross-Site Request Forgery(CSRF) protection (0) | 2024.12.01 |
8장 Configuring endpoint-level authorization: Applying restrictions (0) | 2024.12.01 |
7장 Configuring endpoint-level authorization: Restricting access (0) | 2024.12.01 |