概要
Swagger Editor の Try it out から REST API を実行できる様にするために、Spring の設定を変更して CORS を有効にする方法を説明します。但し、Spring MVC ではなく、Spring WebFlux を使う場合の方法です。コードは Kotlin で書かれています。
目次
確認環境
- Spring Boot 2.0.3
- Kotlin 1.2.50
参考情報
- オリジン間リソース共有 (CORS) - HTTP | MDN
- オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) について
- java - Enable CORS in Spring 5 Webflux? - Stack Overflow
- Spring WebFlux で CORS を有効にする方法
解説
Swagger Editor で発生するエラー
Swagger Editor の Try it out から REST API を実行すると CORS に関するエラーが発生します。ブラウザの画面の Swagger Editor には、以下のエラーが表示されます。(読みやすくなるよう、途中に改行を入れています。)
Possible cross-origin (CORS) issue? The URL origin (http://localhost:8080) does not match the page (http://editor.swagger.io). Check the server returns the correct 'Access-Control-Allow-*' headers.
Spring のデフォルト設定では CORS は無効になっており、swagger.io のドメイン(オリジン)で動作している Web アプリケーションから、REST API を実行するドメイン(オリジン)へのリソース要求は拒否されます。
CORS を有効にする方法
WebFilter を実装したクラスを作成
CORS を有効にするには以下のとおりにします。
import org.springframework.http.HttpMethod import org.springframework.http.HttpStatus import org.springframework.web.cors.reactive.CorsUtils import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.WebFilter import org.springframework.web.server.WebFilterChain import reactor.core.publisher.Mono @Component class CorsFilter : WebFilter { private val allowedOrigin = "http://editor.swagger.io" private val allowedMethods = "GET, PUT, POST, DELETE, OPTIONS" private val allowedHeaders = "Content-Type" private val maxAge = "3600" override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> { val request = exchange.request if (CorsUtils.isCorsRequest(request)) { val response = exchange.response response.headers.apply { add("Access-Control-Allow-Origin", allowedOrigin) add("Access-Control-Allow-Methods", allowedMethods) add("Access-Control-Allow-Headers", allowedHeaders) add("Access-Control-Max-Age", maxAge) } if (request.method == HttpMethod.OPTIONS) { response.statusCode = HttpStatus.OK return Mono.empty<Void>() } } return chain.filter(exchange) } }
上記のコードでは WebFilter インターフェースを実装したクラスを作成して、Bean として登録しています。filter メソッドの処理では、request に Origin ヘッダーが設定されているとき (CorsUtils.isCorsRequest(request)
が true のとき)、response header に Access-Control-* ヘッダーを追加しています。更に、プリフライト要求の場合には OPTIONS メソッドでの request が送られるため、その場合には response body を空のままで応答します。単純要求の場合には Access-Control-* ヘッダーを追加して、次のフィルタに処理を委譲します。CORS 要求でない場合にも、次のフィルタに処理を委譲します。フィルタが委譲を繰り返し、いずれ応答します。
この WebFilter によって、異なるオリジンから Origin ヘッダーを付加して送信された要求に対して、Access-Control-* ヘッダーを付加した応答が返されます。
Configure で Bean を登録
別の方法として、以下の方法でも設定できます。
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.HttpMethod import org.springframework.http.HttpStatus import org.springframework.web.cors.reactive.CorsUtils import org.springframework.web.server.WebFilter import reactor.core.publisher.Mono @Configuration class CorsConfiguration { private final val allowedOrigin = "http://editor.swagger.io" private final val allowedMethods = "GET, PUT, POST, DELETE, OPTIONS" private final val allowedHeaders = "Content-Type" private final val maxAge = "3600" @Bean fun corsFilter(): WebFilter = WebFilter { exchange, chain -> val request = exchange.request if (CorsUtils.isCorsRequest(request)) { val response = exchange.response response.headers.apply { add("Access-Control-Allow-Origin", allowedOrigin) add("Access-Control-Allow-Methods", allowedMethods) add("Access-Control-Allow-Headers", allowedHeaders) add("Access-Control-Max-Age", maxAge) } if (request.method == HttpMethod.OPTIONS) { response.statusCode = HttpStatus.OK return@WebFilter Mono.empty<Void>() } } return@WebFilter chain.filter(exchange) } }
WebFilter の適用順をカスタマイズしたい場合には、こちらの方法よりも WebFilter を実装したクラスを作成した方が簡単に対応できます。