Spring Web Reactive | 1. Spring WebFlux | 1.7. CORS
Spring WebFlux을 사용하면 CORS(크로스 오리진 자원 공유)를 처리 할 수 있다. 이 섹션에서는 그 방법에 대해 설명한다.
1.7.1. 도입
보안상의 이유로, 브라우저는 현재 오리진의 외부 리소스에 대한 AJAX 호출을 금지하고 있다. 예를 들면, 은행 계좌를 하나의 탭에 evil.com을 다른 탭으로 가질 수 있다. evil.com 스크립트는 계정에서 돈을 인출 등 인증 정보를 사용하여 은행의 API를 AJAX 요청을 할 수 없다!
Cross-Origin Resource Sharing(CORS)는 대부분의 브라우저로 구현된 W3C 표준이며, IFRAME 또는 JSONP를 기반으로 안전성이 낮고, 강력하지 않은 해결 방법을 사용하는 것이 아니라 허용된 유형의 크로스 도메인 요청을 지정할 수 있다.
1.7.2. 처리
CORS 사양은 프리플라이트(preflight), 씸플(simple), 실제 요청을 구분한다. CORS가 어떻게 작동하는지 배우고, 다른 많은 사람들과 함께 이 기사 보거나, 자세한 내용 사양을 참조해라.
Spring WebFlux HandlerMapping
구현은 CORS 내장 지원을 제공한다. 요청을 핸들러에 정상적으로 매핑 한 후 HandlerMapping
는 지정된 요청과 핸들러의 CORS 설정을 확인하고, 작업을 수행한다. 프리플라이트 요청은 직접 처리되지만 간단하고, 심플하게 실제 CORS 요청은 차단되고, 검증되어 필요한 CORS 응답 헤더가 설정된다.
크로스 오리진 요청을 활성화하려면 (즉, Origin 헤더가 존재하고, 요청의 호스트와는 다르면) 명시적으로 선언된 CORS 설저이 필요한다. 일치하는 CORS를 찾지 못할 경우, 프리플라이트 요청은 거부된다. CORS 헤더는 단순하고 실제 CORS 요청의 응답에 추가되지 않기 때문에 브라우저는 그들을 거부한다.
각 HandlerMapping
URL 패턴 기반의 CorsConfiguration
매핑에서 개별적으로 구성 할 수 있다. 대부분의 경우 응용 프로그램은 WebFlux Java 구성을 사용하여 이러한 매핑을 선언한다. 이렇게 하면 하나의 글로벌 맵이 모든 HandlerMapping
구현에 전달된다.
HandlerMapping
수준의 글로벌 CORS 구성과 보다 세밀한 핸들러 수준의 CORS 구성을 결합 할 수 있다. 예 : 어노테이션이있는 컨트롤러 클래스 나 메소드 레벨의 @CrossOrigin
어노테이션을 사용할 수 있다 (다른 핸들러 CorsConfigurationSource
를 구현할 수 있다).
전역 설정과 로컬 설정을 결합하는 규칙은 일반적으로 추가적인이다. 예를 들어, 모든 글로벌 및 모든 로컬 오리진. allowCredentials
와 maxAge
같은 단일 값만 받아 들일 수 없는 속성의 경우 로컬 전역 값을 재정의한다. 자세한 내용은 CorsConfiguration#combine(CorsConfiguration)
을 참조해라.
소스에서 자세한 내용을 확인하거나, 고급 커스텀 지정하려면 다음을 참조해라.
CorsConfiguration
CorsProcessor
,DefaultCorsProcessor
AbstractHandlerMapping
1.7.3. @CrossOrigin
@CrossOrigin 어노테이션은 다음 예제와 같은 어노테이션 컨트롤러 메소드에서 크로스 오리진 요청을 활성화한다.
Java
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
Kotlin
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
기본적으로 @CrossOrigin
는 다음을 허용한다.
- 모든 오리진.
- 모든 헤더.
- 컨트롤러 메소드가 매핑되는 모든 HTTP 메소드
allowCredentials
은 기본적으로 활성화되어 있지 않는다. 이것은 사용자 고유의 기밀 정보(Cookie와 CSRF 토큰 등)를 공개하는 신뢰 수준을 설정하고 적절한 경우에만 사용해야 하기 때문이다. 활성화되어 있는 경우, allowOrigins
하나 이상의 특정 도메인으로 설정해야 한다(단, 특별한 값 "*"
은 설정하지 마십시오). 또는, allowOriginPatterns
속성을 사용하여 동적 기점 세트에 일치시킬 수 있다.
maxAge
는 30분으로 설정되어 있다.
@CrossOrigin
는 클래스 레벨에서 지원되는 모든 메소드에 상속된다. 다음 예제에서는 특정 도메인을 지정하고 maxAge
을 1시간으로 설정한다.
Java
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
Kotlin
@CrossOrigin("https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
다음의 예와 같이, @CrossOrigin
는 클래스 레벨과 메서드 레벨에서 모두 사용할 수 있다.
Java
@CrossOrigin(maxAge = 3600) // (1)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("https://domain2.com") // (2)
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
Kotlin
@CrossOrigin(maxAge = 3600) // (1)
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin("https://domain2.com") // (2)
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
- (1) 클래스 레벨에서
@CrossOrigin
을 사용한다. - (2) 메소드 레벨에서
@CrossOrigin
을 사용한다.
1.7.4. 전역 설정(Global Configuration)
세분화 된 컨트롤러 메소드 레벨 구성 이외에, 아마 글로벌 CORS 구성도 정의해야한다. URL 기반의 CorsConfiguration
매핑 HandlerMapping
에서 개별적으로 설정할 수 있다. 그러나 대부분의 응용 프로그램은 WebFlux Java 구성을 사용하여 이를 수행한다.
기본적으로 글로벌 구성하여 다음을 사용할 수 있다.
- 모든 오리진.
- 모든 헤더.
GET
,HEAD
,POST
메소드.
allowedCredentials
은 기본적으로 활성화되어 있지 않는다. 이것은 사용자 고유의 기밀 정보(Cookie와 CSRF 토큰 등)를 공개하는 신뢰 수준을 설정하고 적절한 경우에만 사용해야 하기 때문이다. 활성화되어 있는 경우는 allowOrigins
를 하나 이상의 특정 도메인으로 설정해야 한다(단, 특별한 값 "*"
은 설정하지 말아라). 또는, allowOriginPatterns
속성을 사용하여 동적 기점 세트에 일치시킬 수 있다.
maxAge
은 30분으로 설정되어 있다.
WebFlux Java 구성 CORS를 활성화하려면 다음 예제와 같이 CorsRegistry
콜백을 사용할 수 있다.
Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@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...
}
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600)
// Add more mappings...
}
}
1.7.5. CORS WebFilter
내장된 CorsWebFilter
을 사용하여 CORS 지원을 적용 할 수 있다. 이는 함수 엔드 포인트에 적합한다.
CorsFilter
를 Spring Security에서 사용하려고 하는 경우, Spring Security는 CORS의 지원이 포함되어 있다는 점에 유의해라.
필터를 구성하려면 다음 예제와 같이 CorsWebFilter
Bean을 선언하고 CorsConfigurationSource
를 생성자에 전달할 수 있다.
Java
@Bean
CorsWebFilter corsFilter() {
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);
return new CorsWebFilter(source);
}
Kotlin
@Bean
fun corsFilter(): CorsWebFilter {
val config = CorsConfiguration()
// Possibly...
// config.applyPermitDefaultValues()
config.allowCredentials = true
config.addAllowedOrigin("https://domain1.com")
config.addAllowedHeader("*")
config.addAllowedMethod("*")
val source = UrlBasedCorsConfigurationSource().apply {
registerCorsConfiguration("/**", config)
}
return CorsWebFilter(source)
}