스프링 시큐리티 인 액션

Ch10-2. CORS 이용

webmaster 2025. 4. 24. 10:50
728x90

CORS란? 

기본적으로 브라우저는 사이트가 로드된 도메인 이외의 도메인에 대한 요청을 허용하지 않는다. 브라우저는 CORS 메커니즘으로 엄격한 정책을 완화하고 일부 조건에서 서로 다른 출처 간의 요청을 허용한다고 할 수 있다.

CORS 작동 방식

애플리케이션이 두 개의 서로 다른 도메인 간에 호출하는 것은 모두 금지된다. 호출이 필요할 때는 CORS를 이용해 애플리케이션이 요청을 허용할 도메인, 공유할 수 있는 세부 정보를 지정할 수 있다.

CORS 메커니즘은 HTTP 헤더를 기반으로 작동하며, 가장 중요한 헤더는 아래와 같다.

  • Access-Control-Allow-Origin: 도메인의 리소스에 접근할 수 있는 외부 도메인(원본)을 저장한다.
  • Access-COntrol-Allow-Methods: 다른 도메인에 대해 접근을 허용하지만 특정 HTTP 방식만 허용하고 싶을 때 일부 HTTP 방식을 지정할 수 있다.
  • Access-Control-Allow-Headers: 특정 요청에 이용할 수 있는 헤더에 제한을 추가한다.

스프링 시큐리티는 기본적으로 이러한 헤더를 응답에 추가하지 않는다.

Controller

@Controller
public class MainController {

    private Logger logger = Logger.getLogger(MainController.class.getName());

    @GetMapping("/")
    public String main() {
        return "main.html";
    }

    @PostMapping("/test")
    @ResponseBody
    public String test() { //CORS 작동을 확인하기 위해 다른 출처에서 호출할 엔드포인트
        logger.info("TEST method called");
        return "HELLO WORLD";
    }

}

Config

public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        
        http.authorizeRequests()
                .anyRequest().permitAll();
    }
}
  • Main.html에서 test 엔트포인트를 호출하면 CORS가 설정이 안되어 아무것도 표시가 안된다.
  • Access-Control-Allow-Origin HTTP 헤더가 없어서 응답이 수락되지 않은 것이다.
    • Spring boot는 기본적으로 CORS 관련 헤더를 설정하지 않는다.
  • CORS는 교차 도메인 호출의 엄격한 제한을 완화하도록 도와주는 기능이다.

CORS 메커니즘은 결국 브라우저에 관한 것이며 엔드포인트를 보호하는 방법이 아니다. CORS 메커니즘이 유일하게 보장하는 것은 허용하는 출처 도메인만 브라우저의 특정 페이지에서 요청을 수행할 수 있다는 것이다.

@CrossOrigin 어노테이션으로 CORS 정책 적용

@CrossOrigin 어노테이션으로 각 엔드포인트에 맞게 손쉽게 CORS를 구성할 수 있다.

Controller

@PostMapping("/test")
@CrossOrigin("http://localhost:8080") //localhost 출처에 대한 교차 출처 요청을 허용한다
@ResponseBody
public String test() { //CORS 작동을 확인하기 위해 다른 출처에서 호출할 엔드포인트
    logger.info("TEST method called");
    return "HELLO WORLD";
}
  • @CrossOrigin으로 localhost 출처에 대한 교차 출처 요청을 허용한다.
  • @CrossOrigin의 값 매개 변수는 여러 출처를 정의하는 배열을 받을 수 있다.
  • allowedHeader, methods 특성으로 허용되는 헤더와 메서드를 지정할 수 있다.
  • 출처와 헤더에 별표(*)를 이용하면 모든 헤더나 출처를 지정할 수 있다. 
    • 단, 이 방식은 주의해야 하며, 혀용 하려는 출처와 헤더를 필터링하고 어떤 도메인이든 애플리케이션의 리소스에 접근하는 코드를 구현하는 것을 허용하지 않는 것이 더 좋다.
    • 모든 출처를 허용 시 XSS 요청에 노출되고 DDOS 공격에 취약해진다.
    • 테스트 환경에서도 모든 출처를 허용하지 않는 것이 바람직하다.
  • @CrossOrigin으로 직접 규칙 지정 시, 코드가 장황해지고 중복 코드가 발생하는 단점이 있다.

CorsConfigurer로 CORS 적용


public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors(c -> { //cors() 호출해 CORS 구성을 정의한다. 여기에서 허용되는 출처와 메서드를 설정하는 CorsConfiguration 객체를 생성한다.
            CorsConfigurationSource source = request -> {
                CorsConfiguration config = new CorsConfiguration();
                config.setAllowedOrigins(List.of("example.com", "example.org"));
                config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
                return config;
            };
            c.configurationSource(source);
        });
        http.csrf().disable();
        http.authorizeRequests()
                .anyRequest().permitAll();
    }
}
  • cors() 메서드는 Customizer <CorsConfigurer> 객체를 매개 변수로 받는다.
    • 이 객체를 위해 HTTP 요청의 CorsConfiguration을 반환하는 CorsConfigurationSource를 설정했다.
  • CorsConfigurer은 허용되는 출처, 메서드, 헤더를 지정하는 객체다.
  • 최소한 허용할 출처와 메서드를 지정해야 하며, 출처만 지정하면 애플리케이션이 요청을 허용하지 않는다.
    • CorsConfiguration 객체가 기본적으로 아무 메서드도 정의하지 않기 때문
  • CorsConfigurationSource의 구현을 다른 클래스로 나누는 것이 좋음(실제는 매우 복잡해서, 코드 읽기가 어려움)

 

728x90

'스프링 시큐리티 인 액션' 카테고리의 다른 글

발표  (0) 2025.05.05
Ch12. OAuth2가 작동하는 방법  (0) 2025.04.29
Ch11. 실전. 책임의 분리  (0) 2025.04.25