교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더, Access-Control-Allow-Origin 헤더
를 사용하여 한 웹 페이지 상의 제한된 리소스를 최초 자원이 서비스 된 도메인 밖의 다른 도메인으로부터 요청할 수 있게 허용하는 것이다. 이를 사용하면 웹페이지는 교차 출처 이미지, 스타일시트, 스크립트, iframe, 동영상을 임베디드 할 수 있다.
ex) https://domain-a.com의 프론트엔드 JavaScript 코드가 XMLHttpRequest를 사용하여 https://domain-b.com/data.json을 요청하는 경우.
그러나 보안 상의 이유로, 특정 도메인 간(cross-domain) 요청, 특히 Ajax 요청은 동일-출처 보안 정책에 의해 기본적으로 금지된다.
Spring MVC에서 HandlerMapping을 구현할 때, CORS에 대해 처리할 수 있도록 설정할 수 있다. 요청이 핸들러에 매핑된 후, HandlerMapping은 요청과 핸들러의 CORS 구성을 확인하고 추가 작업을 진행한다. Preflight 요청은 곧바로 처리되고 실제 CORS 요청은 intercept되고, 검증되고, CORS 응답 헤더를 만들어낸다.
cross-origin 요청을 활성화하려면(Origin 헤더가 있고 요청 호스트와 다름), CORS 설정을 명시해 주어야 한다. 일치되는 CORS 구성이 발견되지 않으면, Preflight 요청은 거부된다. CORS 요청에 대한 응답에 CORS 헤더가 추가되지 않으면 브라우저는 이를 거부한다.
각각의 HandlerMaping은 개별적으로 URL 패턴을 기반으로한 CorsConfiguration Mapping을 구성할 수 있다. 대부분의 경우, 어플리케이션은 MVC 자바 구성 파일을 사용하거나 XML 네임스페이스에서 이러한 매핑을 명시할 수 있다.
또한 전역적인 CORS 구성을 세분화된 수준의 핸들러 매핑과 결합할 수 있다. 예를 들어, 어노테이션이 붙은 컨트롤러는 클래스나 메소드 레벨의 @CrossOrigin 어노테이션을 사용할 수 있다. 혹은 핸들러 매핑이나 필터를 구현할 때 CorsConfigurationSource를 사용한다.
전역 구성이나 지역 구성은 일반적으로 결합해서 사용하지만, 단일 값만 허용되는 경우 지역 구성이 전역 구성을 오버라이드한다.(ex) allowCredentials, maxAge)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
기본적으로 @CrossOrigin은 다음을 허용한다.
allowCredentials은 민감한 사용자 정보(쿠키나 CSRF token)을 노출하는 적절한 신뢰수준을 확립할 수 있어야 하고, 적절하게 사용되어야 하기 때문에 기본적으로 비활성화되어있다. allowCredentials가 활성화된 경우, 하나 이상의 특정한 도메인("*" 값 아님)으로 설정하거나 allowOriginPatterns 속성을 사용해야 한다.
@CrossOrigin는 클래스 레벨을 지원하며 모든 메소드가 이를 상속받을 수 있다.
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
또, @CrossOrigin은 다음과 같이 클래스 수준과 메서드 수준 모두에서 사용할 수 있다.
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("https://domain2.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
세분화된 컨트롤러 메소드 수준 구성 외에도, 전역 CORS 구성을 정의할 수 있다. 각각의 HandlerMaping은 개별적으로 URL 패턴을 기반으로한 CorsConfiguration Mapping을 구성할 수 있다. 그러나 대부분의 경우, MVC 자바 구성파일을 사용하거나 XML 네임스페이스 파일을 사용한다.
기본적으로 전역 구성은 다음을 활성화한다.
allowCredentials은 민감한 사용자 정보(쿠키나 CSRF token)을 노출하는 적절한 신뢰수준을 확립할 수 있어야 하고, 적절하게 사용되어야 하기 때문에 기본적으로 비활성화되어있다. allowCredentials가 활성화된 경우, 하나 이상의 특정한 도메인("*" 값 아님)으로 설정하거나 allowOriginPatterns 속성을 사용해야 한다.
자바 구성 파일
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
// Add more mappings...
}
}
XML 구성
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="https://domain1.com, https://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="true"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="https://domain1.com" />
</mvc:cors>
CorsConfiguration config = new CorsConfiguration();
// Possibly...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
CorsFilter filter = new CorsFilter(source);
참고