본문 바로가기
Spring, Springboot

CORS 이해와 설정

by 이상한나라의개발자 2024. 1. 29.

Cors(교차 출처 자원공유, Cross-Origin Resource Sharing)는 웹 브라우저의 보안 정책으로 인해 동인한 출처(same-origin)에서만 데이터 접근할 수 있는 제약을 완화하는 메커니즘 입니다. 웹 페이지에서 다른 출처의 리소스에 안전하게 접근할 수 있도록 하는 표준화된 방법 입니다.

 

  • 출처(Origin) : 출처는 프로토콜(예:http, https), 호스트(도메인), 포트로 구성된 URL을 의미합니다. 두 URL이 동일한 출처에 속하려면 위 세가지 구성요소(프로토콜, 호스트or도메인, 포트)가 동일해야 합니다. 예를 들어, "http://localhost:8080" 과  "http://localhost:8082" 는 서로 다른 출처 입니다.
  • 동일 출처 정책(Same-Origin Policy) : 브라우저에서는 보안을 위해 동일 출처 정책을 따릅니다. 이는 스크립트로부터 다른 출처의 리소스에 직접 접근을 허용하지 않습니다.
  • CORS의 필요성 : 웹 어플리케이션에서는 클라이언트 측 JavaScript가 브라우저를 통해 서버에 HTTP 요청을 보내는데, 이때 다른 출처의 API에 접근하면 보안 정책에 의해 차단됩니다. 이러한 상황에서 CORS가 필요하며, 서버는 클라이언트에게 어떤 출처에서의 요청을 허용할지를 명시적으로 설정합니다.
  • CORS 헤더 : 서버는 응답에 CORS 관련 헤더를 포함하여 클라이언트에게 어떤 출처에서의 요청을 허용할지 알려줍니다. 주요 CORS헤더는 아래와 같습니다
    • Access-Control-Allow-Origin : 어떤 출처에서의 요청을 허용할지를 나타냅니다. 이 헤더의 값으로는 특정 출처나 "*" 모든 출처를 설정할 수 있습니다.
    • Access-Control-Allow-Mehtod :  어떤 HTTP 메서드를 허용할지 나타냅니다.
    • Access-Control-Allow-Headers : 허용되는 HTTP 헤더를 나타냅니다.
    • Access-Control-Allow-Credentials : 클라이언트에서 쿠키 및 인증 정보를 전송할지 여부를 나타냅니다.
    • Access-Control-Max-Age : Preflight 요청의 유효 기간을 나타냅니다.

 

요약하면, CORS는 웹 페이지에서 리소스가 다른 도메인 프로토콜 또는 포트에서 로드될 때 브라우저가 이를 허용하는 보안 기술입니다. 기본적으로 같은 출처 정책을 따르며, 이는 웹페이지에서 오리진(출처)간 요청을 제한하는 보안 메커니즘입니다. 요즘은 마이크로서비스등 다양한 출처에서 서로의 api를 호출하는 방법이 널리 사용되고 있어 CORS를 이용하여 다른 출처의 리소스에 대한 접근을 서버가 허용할 수 있게 됩니다.

 

 

CORS 테스트

아래 코드를 만들어 하나는 8080, 8082로 각각 실행합니다. 

<!DOCTYPE html> <html lang="en"> <head>
    <meta charset="UTF-8" />
    <title>CORS Test</title>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
</head>
<body>
<script>
    $.ajax({
        url: "http://localhost:8080/api/health-check",
        type: "GET",
    success : function(result){
        console.log(result); }
    }); </script>
</body>
</html>

 

 

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class HealthCheckController {

    private final Environment environment;

    @GetMapping("/health-check")
    public ResponseEntity<HealthCheckResponseDto> healthCheck() {
        log.info("activeProfiles={}",environment.getActiveProfiles());
        return ResponseEntity.ok(HealthCheckResponseDto.builder()
                .health("ok")
                .activeProfiles(List.of(environment.getActiveProfiles()))
                .build());
    }
}


@Controller
public class CorsController {

    @GetMapping("/cors")
    public String cors() {
        return "cors";
    }
}

 

 

http://localhost:8082/cors에 접근을 하여 개발자 도구를 열고 콘솔을 확인하면 아래와 같이 에러가 발생하게 됩니다. 

이유는 다른 origin(출처)에서 접근을 시도하기 때문입니다.

CORS 에러

 

이를 해결하기 위해서 아래와 같이 코드를 작성합니다.

 

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:8082", "http://localhost:8080")
                .allowedMethods(
                        HttpMethod.GET.name(),
                        HttpMethod.POST.name(),
                        HttpMethod.PUT.name(),
                        HttpMethod.DELETE.name(),
                        HttpMethod.OPTIONS.name(),
                        HttpMethod.HEAD.name(),
                        HttpMethod.PATCH.name()
                )
                .maxAge(3600);
    }

}

 

 

SpringSecurity 사용할 경우

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.httpBasic().disable();
        http.csrf().disable(); // 외부 POST 요청을 받아야하니 csrf는 꺼준다.
        http.cors(); // CORS를 커스텀 허용
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.authorizeHttpRequests()
                .requestMatchers("/**").permitAll()
                .anyRequest().authenticated();

        return http.build();
    }
}

@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();

        config.setAllowCredentials(true);
        config.setAllowedOrigins(List.of("http://localhost:8082"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
        config.setAllowedHeaders(List.of("*"));
        config.setExposedHeaders(List.of("*"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }

}

 

 

위처럼 작성후 다시 테스트 하게 되면 (http://localhost:8082/cors)  아래와 같이 잘 호출 되는걸 알 수 있습니다.

 

다른 오리진 데이터 정상 출력

 

 

헤더 값

 

Access-Control-Allow-Origin 이 추가 된걸 확인할 수 있습니다.

 

Preflight Request(사전 요청) 

 

Preflight request는 실제 요청을 전송하기 전에 브라우저가 서버에게 사전 정보를 확인하기 위해 보내는 추가적인 HTTP 요청입니다. 이는 CORS (Cross-Origin Resource Sharing) 정책에 따라 서버로의 안전한 교차 출처 요청을 가능케 하는 메커니즘 중 하나입니다.

 

브라우저는 실제 요청을 전송하기 전에 다음과 같은 정보를 확인하기 위해 Preflight request를 보냅니다:

 

  • HTTP 메서드와 헤더 확인: 실제 요청에서 사용될 HTTP 메서드와 헤더가 안전한지 확인합니다.
  • CORS 지원 확인: 서버가 CORS를 지원하는지 확인합니다.
  • 사용자 인증 정보 전송 확인: 만약 요청이 사용자 인증 정보(쿠키, HTTP 인증 등)를 전송한다면, 서버가 이를 허용하는지 확인합니다.
 

Preflight request는 브라우저에 의해 자동으로 처리되며, 브라우저는 실제 요청을 보내기 전에 서버의 응답을 확인하여 안전한 교차 출처 요청인지 여부를 결정합니다. 서버는 Preflight request에 대한 응답으로 CORS 관련 헤더를 포함하여 클라이언트에게 필요한 권한을 부여하거나 거부할 수 있습니다.

 

1. 클라이언트가 서버에게 "POST" 메서드를 사용하여 다른 도메인의 자원에 접근하고자 할 때, 브라우저는 사전에 다음과 같은 Preflight request를 보냅니다.

 

OPTIONS /api/resource HTTP/1.1
Host: example.com
Origin: https://client.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

 

 

 

2. 서버는 Preflight request에 대한 응답으로 다음과 같이 cors 헤더를 포함하여 응답합니다.

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://client.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Authorization

 

 

3. 브라우저는 이후에 실제 "post" 요청을 서버에게 보냅니다.

 

이러한 과정을 통해 브라우저는 서버로의 교차 출처 요청이 안전하게 이루어질 수 있도록 보장합니다.

'Spring, Springboot' 카테고리의 다른 글

자가 호출  (0) 2024.07.16
타입 기반 주입  (0) 2024.07.15
서비스 ( Service )  (1) 2024.07.10
Spring Cloud OpenFeign  (1) 2024.02.07
전역 에러 처리  (0) 2024.01.30