ブラウザからSpring BootのAPIを叩いた瞬間に、画面がこう言って止まるやつありますよね。
「CORS policyでブロックされました」みたいなやつです。いや、こっちはただ fetch() しただけなんですけど…ってなりますよね。
典型パターンはこれです。
フロントが React/Vue(http://localhost:3000)、バックが Spring Boot(http://localhost:8080)。
Postmanだと普通に200返ってくるのに、ブラウザだけ失敗する。
これ、サーバーが壊れてるというより、ブラウザ側の安全ルールに引っかかってる可能性が高いです。
この記事では、まずCORSの正体を1分でつかんでから、最小の設定だけで動かすコピペを2パターン用意します。
- Spring Securityなし
- Spring Securityあり
さらに、よくある地雷(OPTIONSが通ってない、*の危険、Cookieあり/なし問題)も先に潰します。
最短ゴールは「localhost:3000 → 8080 がブラウザでも通る」状態にすることです。
危ない設定でごまかさず、安全にスッと直していきましょう。
CORSは「ブラウザの安全ルール」:まず1分で理解
CORSって聞くと急にむずかしそうですが、正体はめちゃシンプルです。
「ブラウザは、知らない場所に勝手に情報を渡さないように“門番”を置いてる」ってだけです。
なので、サーバー側が「この人は入っていいですよ〜」って許可の札(ヘッダー)を出してあげないと、ブラウザが止めちゃいます。
なぜブラウザだけ失敗?(Postmanが通る理由)
ポイントはこれです:CORSは“ブラウザだけ”が守る安全ルールです。
Postmanやcurlは「テスト用の道具」なので、門番チェックをしません。だから Postmanは通るのに、ブラウザだけ落ちるが起きます。
つまり、API自体が死んでるとは限らず、ブラウザが「許可札ないよ?」って言ってるだけのケースが多いです。
例でイメージ:localhost:3000 → localhost:8080
たとえばこの形、超あるあるです。
ブラウザから見ると、ポートが違うだけでも“別の場所(別オリジン)”扱いです。
なので 3000 → 8080 の時点で、ブラウザは「ほんとに行っていい?」って確認モードに入ります。
CORSで決まる3つ(Origin / Method / Header)
CORSでチェックされやすいのは主にこの3つです。
サーバーがレスポンスで、たとえば Access-Control-Allow-Origin: http://localhost:3000 みたいに許可の札を返すと、ブラウザは「OK、通ってよし!」になります。
逆にこの札がない・条件がズレてると、ブラウザは容赦なくブロックします。

まずこれだけ!最小の正解(コピペ2パターン)
最初に分岐です。あなたはどっちですか?
パターンA:Spring Securityなし → WebMvcConfigurerでOK
// CorsConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// ★ここだけ自分のフロントURLに変える
.allowedOrigins("http://localhost:3000")
// ★必要なものだけ(例)
.allowedMethods("GET", "POST")
// ★必要なものだけ(例:JSON + 認証ヘッダー)
.allowedHeaders("Content-Type", "Authorization")
// Cookie使うなら true(後で説明)
.allowCredentials(false);
}
}
パターンB:Spring Securityあり → Security側にもCORSが必要
Securityありは「MVCで許可しても、Securityフィルターで止まる」ことがあるので、SecurityにCORSを渡すのがコツです。
// SecurityConfig.java(Spring Boot 3 / Security 6系の書き方)
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.cors(Customizer.withDefaults()) // ★CORS有効化(重要)
.csrf(csrf -> csrf.disable()) // 開発中の例(必要に応じて)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/**").permitAll() // 例:まず動かす(本番は要調整)
)
.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
// ★ここだけ自分のフロントURLに変える
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "OPTIONS")); // OPTIONSも忘れがち
config.setAllowedHeaders(List.of("Content-Type", "Authorization"));
config.setAllowCredentials(false); // Cookie使うなら true(後で説明)
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}
許可は必要最小限:Origin / Method / Header の決め方
※そして実務で一番役立つのは、ブラウザのDevTools(Networkタブ)です。CORSはブラウザが止めるので、ここを見るのが最短ルートです。
「Cookieを使う/使わない」で設定が変わる(超重要)
Cookie(セッション/HttpOnly Cookieなど)を使うなら:
// fetch例(Cookie使うとき)
fetch("http://localhost:8080/api", {
method: "GET",
credentials: "include",
});
Cookieを使わない(AuthorizationヘッダーでBearerなど)なら:

この章のゴールはここです:「まず最小で動く形」を固定して、あとで必要な分だけ狭める。
次の章で、初心者が踏みがちな地雷(特にOPTIONS)を先に避けにいきます。
よくある詰まりポイントTOP3(先に地雷を避ける)
CORSって「設定したはずなのに動かない…」が起きやすいんですが、だいたいハマり所はこの3つに集約されます。症状から当てにいきましょう。
preflight(OPTIONS)が通ってない問題(いちばん多い)
イメージはこんな流れです:
① OPTIONS /api(確認) → 200 + Allow系ヘッダーOK
② GET/POST /api(本番) → 200
ここで①が落ちたら、②は永遠に来ません。まずOPTIONSを見てください。
allowedOrigins と allowedOriginPatterns の違いで詰む
※「とりあえずワイルドカードで…」は事故りやすいので、最初は固定が正解です。
allowCredentials=true なのに Origin=* にしてる(危ない&動かない)
⚠️ここ、動かないだけじゃなくて危ないので「*で雑に直す」はやめた方がいいです。
※切り分けに使う“道具”は無料で十分です:ブラウザのNetworkログと、Spring Bootのコンソールログ。どこで止まってるかが見えるだけで、解決スピードが一気に上がります。
開発用と本番用を分けよう(安全な許可の考え方)
CORSで一番やりがち事故が、「動かないから * で全許可!」→そのまま本番に出しちゃうやつです。これ、あとで普通に燃えます。なので合言葉いきますね。
合言葉:開発は小さく許可。本番はもっと小さく許可。
許可が広いほどラクですが、広いほど「変なところから叩けちゃう入口」も増えます。
開発:localhostだけ許可でOK(例:3000だけ)
開発中は相手が自分のPCなので、http://localhost:3000 だけ許可でだいたい十分です。
「とりあえず動かす」も大事ですが、“必要な相手だけ通すクセ”をここで作るのが大事です。
本番:自分のドメインだけ許可(*は基本やめる)
本番は、たとえばフロントが https://app.example.com なら、それだけ許可が基本です。
* は「世界中のサイトから呼べます」になりやすいので、特殊な理由がない限り避けた方が安全です。
やっていい/ダメ早見表
| 環境 | やっていい例 | ダメ寄り例 |
|---|---|---|
| 開発 | http://localhost:3000 のみ許可 | * を常用 |
| 本番 | https://app.example.com のみ許可 | */不要な複数ドメイン許可 |
設定を切り替える方法(profiles / 環境変数の方針)
細かい実装より方針だけ押さえるとOKです。
※本番で「自分のドメインだけ許可」を自然にやるには、前提として 独自ドメイン(年数千円〜) と、フロント配信(例:Vercel/Netlifyの無料枠〜有料)みたいな土台があるとスムーズです。

次の章では、まだハマりやすい地雷(特にOPTIONSまわり)を診断フローに落として、確実に潰していきます。
まだ直らないときの診断フロー(順番に潰せばOK)
「設定したのに無理!」ってときは、寄り道せず この順番で見れば当たりやすいです。
エラー文の読み方(どこがダメか当てるコツ)
ブラウザで見る場所:NetworkでOPTIONS/Response Header確認
- DevTools → Network を開く
- まず OPTIONS があるか探す(あったらそこが優先)
- Statusが200系かを見る(403/404ならそこが原因)
- Response Headersでこれを“探し物”してください
Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers- Cookie使うなら
Access-Control-Allow-Credentials: true
ありがちな見落とし:URL末尾、http/https、ポート、リダイレクト
最後のチェックリスト&まとめ(うっかり危険設定を防ぐ)
ここまでで「とりあえず動く」は作れたはずなので、最後に 事故らないための10秒チェックだけ置いておきます。これ、チームのテンプレにもそのまま使えます。
10秒チェックリスト
「全部許可していいのはいつ?」限定条件だけ
正直、基本はおすすめしません。それでも「全部許可(広め許可)」が許されるのは、せめてこの3条件が揃うときだけにしてください。
この3つのどれかが欠けるなら、* はやめた方が安全です。
次にやること(チーム共有テンプレ/本番前の確認)
次の一手はこれが強いです。
共有先は、カテゴリとしては「チーム共有ツール」でOKで、価格帯も 無料〜で十分です(例:Notion / Google Docs / GitHub Gist)。
一回テンプレ化すると、CORS事故はかなり減りますよ。
よくある質問
- QPostmanだと動くのに、ブラウザだけCORSで止まるのはなぜですか?
- A
CORSはブラウザだけが守る安全ルールだからです。Postmanはそのチェックをしないので通ります。つまり「APIが壊れてる」じゃなくて「ブラウザが門番してる」ことが多いです。
- Q
localhostなのにCORSになるのはおかしくないですか? - A
おかしくないです。
localhostでも ポートが違うだけで別物扱いです。
例:http://localhost:3000→http://localhost:8080は別オリジンです。
- Qとりあえず
Access-Control-Allow-Origin: *にしたらダメですか? - A
動くことはありますが、基本おすすめしません。
特に Cookieを使う(credentialsあり)場合は*は仕様的にNGで、危ない上に動きません。
- Qpreflight(OPTIONS)って何ですか?なんで勝手に飛ぶんですか?
- A
ブラウザの「本番前の確認電話」みたいなものです。
Authorizationヘッダーを付けたり、Content-Type: application/jsonだったりすると、ブラウザが先にOPTIONSで「この条件で送っていい?」を確認します。
- QOPTIONSが403/404になります。何を直せばいいですか?
- A
まずはここを疑ってください。
ブラウザのNetworkで OPTIONSのStatus を見て、そこから潰すのが早いです。
- QSpring Securityを使ってると、CORS設定はどこに書けばいいですか?
- A
Security側にもCORSが必要になることが多いです。
「MVCで許可したのに動かない」場合、Securityフィルターで止まってる可能性が高いです。
- Q
allowedOriginsとallowedOriginPatternsはどう使い分けますか? - A
ざっくりこれです。
開発中はまず
http://localhost:3000を完全一致で許可するのが安定です。
- QCookie(セッション)を使う場合、何が変わりますか?
- A
重要ポイントが増えます。
ここがズレると「設定したのにダメ」が起きがちです。
- Q開発と本番でCORS設定は分けるべきですか?
- A
分けた方が安全です。
「開発で広げた設定を本番に持ち込む」のが事故パターンです。
- Qどこを見れば原因が一番早く分かりますか?
- A
ブラウザの DevTools → Network が最短です。
これだけで「どこで止まってるか」がかなり当てられます。
