Spring BootでCORSを設定する方法 - @CrossOriginとWebMvcConfigurerの使い分け
ReactやVueでフロントエンドを作ってSpring Boot REST APIと繋ごうとしたとき、必ずと言っていいほど遭遇するのがCORSエラーですよね。
Access to XMLHttpRequest at 'http://localhost:8080/api/users'
from origin 'http://localhost:3000' has been blocked by CORS policy
「@CrossOrigin を付けたのに効かない」「Spring Securityを入れたら突然設定が無視された」という経験、ありませんか。この記事では3つの設定パターンを順番に解説しつつ、Spring Security導入時の落とし穴まで押さえます。
CORSとは - ブラウザがエラーを出す仕組み
CORSとは Cross-Origin Resource Sharing の略で、ブラウザのセキュリティ機能である 同一オリジンポリシー を安全に緩和する仕組みです。
オリジンとは プロトコル + ホスト + ポート の組み合わせのこと。http://localhost:3000 と http://localhost:8080 はポートが違うので 別オリジン になります。
ブラウザはPOSTやカスタムヘッダーを含むリクエストを送る前に、OPTIONSメソッドで Preflightリクエスト を投げます。サーバーが Access-Control-Allow-Origin などのヘッダーを返さないと、ブラウザ側でブロックされます。
curlやPostmanでは同じリクエストが成功するのに、ブラウザだけエラーになるのはこのためです。サーバーが正しいレスポンスヘッダーを返すよう設定するのがCORS対応です。
パターン1: @CrossOriginアノテーション
最もシンプルな方法です。コントローラーに直接付けるだけで動きます。
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {
@GetMapping
public List<User> getUsers() { ... }
// このエンドポイントだけ別のオリジンを許可したい場合
@PostMapping
@CrossOrigin(origins = "https://app.example.com")
public User createUser(@RequestBody UserRequest req) { ... }
}
手軽ですが、エンドポイントが増えるたびに付け忘れが発生しやすいです。Spring Securityなし・試作段階・特定エンドポイントだけ許可したいケース向けの選択肢と考えてください。
パターン2: WebMvcConfigurerでグローバル設定
全エンドポイントに一括でCORSを適用するなら WebMvcConfigurer を使います。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
addMapping("/**") でアプリ全体に適用できます。パスごとに設定を分けたい場合は addMapping("/api/**") のように書けます。
Spring Securityを使っていない環境ではこれで完璧です。ただし、Spring Securityを導入すると突然この設定が効かなくなります。
Spring Securityを導入するとCORS設定が効かなくなる理由
これが多くの人がハマるポイントです。
Spring SecurityはDispatcherServletよりも 前段のFilter層 で動作します。WebMvcConfigurer のCORS設定はDispatcherServlet以降のSpringMVC層に適用されるため、SecurityFilterChainがリクエストを先に捕まえてしまうのです。
PreflightのOPTIONSリクエストが来た際、SecurityFilterChainが「認証されていないリクエスト」として 401を返してしまい 、ブラウザがCORSエラーと認識します。WebMvcConfigurerの設定にはそもそも到達すらしていません。
解決策はSecurityFilterChain側にもCORSを認識させることです。
パターン3: SecurityFilterChainでCORSを設定する
Spring Security 6.x対応の書き方です。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}
CorsConfigurationSource をBeanとして定義しておくと、将来 WebMvcConfigurer と設定を共有することもできて管理しやすいです。Spring Security 5.x(WebSecurityConfigurerAdapter)を使っている場合は書き方が異なりますが、Spring Boot 3.x環境では上記の形式が標準です。
allowedOriginsとallowedOriginPatternsの違い
本番環境でよく問題になる点です。
allowCredentials(true) にしているのに allowedOrigins("*") を指定すると、Springがエラーを投げます。CORSの仕様上、Credentialsを含むリクエストではワイルドカード指定が禁止されているためです。
代わりに allowedOriginPatterns を使うとワイルドカードが使えます。
// allowCredentials(true)と組み合わせて使える
config.setAllowedOriginPatterns(List.of("https://*.example.com"));
// 開発環境では * でも可
config.setAllowedOriginPatterns(List.of("*"));
本番環境では具体的なオリジンを明示するのが安全です。環境ごとに切り替えたい場合は Spring Profilesを使った設定切り替え が参考になります。
Preflightをcurlで確認する
設定が正しく適用されているかはcurlで確認できます。
curl -v -X OPTIONS http://localhost:8080/api/users \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type"
レスポンスに Access-Control-Allow-Origin: http://localhost:3000 が含まれていればOKです。含まれていない場合は設定が正しく適用されていません。Spring Securityを入れている場合、OPTIONSリクエストが401になっていないかもここで確認できます。
なお、よくあるミスとして allowCredentials(true) と allowedOrigins("*") の組み合わせ、SecurityFilterChainで cors() の記述を忘れてWebMvcConfigurerだけ設定している、などが挙げられます。設定が効いていない場合はまずこのcurlコマンドで動作確認してみてください。
まとめ
3パターンの使い分けをまとめます。
| 状況 | 推奨パターン |
|---|---|
| Spring Securityなし・特定エンドポイントのみ | @CrossOrigin |
| Spring Securityなし・全体に一括適用 | WebMvcConfigurer |
| Spring Securityあり(本番環境) | SecurityFilterChain + CorsConfigurationSource |
Spring Securityを使っているなら基本的にパターン3一択です。CorsConfigurationSource をBeanとして切り出しておくと設定の一元管理もしやすくなります。
認証・認可の実装については Spring Security JWTの記事 も合わせてどうぞ。FilterとInterceptorの仕組みをもっと詳しく知りたい方は InterceptorとFilterの違いと使い分け も参考になります。