Vue 3 + Vite + Spring Boot でCORSとProxyを突破するまでの記録
はじめに
フロントエンドをVue 3 + Vite(ポート5173)、バックエンドをSpring Boot(ポート8082)で分離して開発していると、
最初に必ずぶつかるのがCORSエラーです。
Access to XMLHttpRequest at 'http://localhost:8082/api/films'
from origin 'http://localhost:5173' has been blocked by CORS policy
この記事は、このエラーを突破するまでの記録と、環境ごとの正しい対処法をまとめたものです。
そもそもCORSとは
CORS(Cross-Origin Resource Sharing)は、ブラウザのセキュリティ機能です。
「http://localhost:5173 からのリクエストが http://localhost:8082 に行くのは別オリジンなので、ブラウザがブロックする」という話です。
重要なポイント:CORSはブラウザが行う制限です。
サーバー間通信(curlやPostmanのようなツール)ではCORSは発生しません。
開発時の解決策:Vite の devProxy
開発時は Viteのプロキシ機能 を使うのが最もシンプルです。
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8082',
changeOrigin: true,
// rewriteは今回不要(バックエンドも /api プレフィックスを使うため)
}
}
}
})
この設定により:
- ブラウザは
http://localhost:5173/api/filmsにリクエストを送る - Viteサーバーが受け取り、
http://localhost:8082/api/filmsに転送する - ブラウザから見ると「同一オリジン(ポート5173)への通信」なのでCORSが発生しない
フロントエンド側のAPI呼び出し
// api/films.ts
export async function fetchFilms(): Promise<Film[]> {
// 環境によってベースURLを切り替え
const baseUrl = import.meta.env.VITE_API_BASE_URL ?? ''
const res = await fetch(`${baseUrl}/api/films`)
if (!res.ok) throw new Error('API error')
return res.json()
}
開発時は VITE_API_BASE_URL を設定しないので空文字になり、/api/films → Viteプロキシ経由でSpring Bootへ届きます。
本番時の解決策:Spring Boot の CORS 設定
本番環境では、フロントとバックが別ドメイン(または別ポート)になる可能性があります。
方法1:グローバルCORS設定
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${app.cors.allowed-origins}")
private String[] allowedOrigins;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(allowedOrigins)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
# application.yml
app:
cors:
allowed-origins: http://localhost:5173
# application-prod.yml
app:
cors:
allowed-origins: https://your-production-frontend.com
方法2:@CrossOrigin アノテーション(コントローラ単位)
@RestController
@CrossOrigin(origins = "${app.cors.allowed-origins}")
@RequestMapping("/api/films")
public class FilmController {
// ...
}
つまずいたポイント
① プリフライトリクエスト(OPTIONS)を忘れる
CORSでは、実際のリクエストの前に OPTIONS メソッドで「このリクエストは許可されていますか?」という事前確認が来ます。
Spring Securityを使っている場合、OPTIONS リクエストが認証に弾かれることがあります。
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable()) // REST APIならCSRF無効化
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // OPTIONSは全許可
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
② allowedOrigins("*") と allowCredentials(true) は共存できない
ワイルドカードと認証情報の送信は同時に使えません。
When allowCredentials is true, allowedOrigins cannot contain the special value "*"
認証情報(セッションCookieなど)を使う場合は、具体的なオリジンを指定してください。
③ 本番URLのスラッシュ末尾に注意
# これはNGになることがある
allowed-origins: https://example.com/
# 末尾スラッシュなし
allowed-origins: https://example.com
環境変数による切り替え
# .env(開発時)
VITE_API_BASE_URL=
# .env.production(本番ビルド時)
VITE_API_BASE_URL=https://api.example.com
本番ビルドでは VITE_API_BASE_URL に本番APIのURLを入れると、プロキシ不要で直接バックエンドを呼びます。
まとめ
| 環境 | 解決策 |
|---|---|
| 開発(localhost) | Viteの server.proxy 設定 |
| 本番(同一ドメイン) | nginx等のリバースプロキシで /api を振り分け |
| 本番(別ドメイン) | Spring BootのCORS設定 + allowed-origins 本番URL指定 |
CORSエラーは「ブラウザのセキュリティ機能」なので、誤魔化すのではなく正しく許可することが重要です。
開発時はViteプロキシで、本番時はSpring Boot側で設定するのが責務的に正しい分割です。
このアプリでの実装
vite.config.ts(実際のコード)
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8082', // Spring Bootのポート
changeOrigin: true
// rewrite は不要(パスはそのまま転送)
}
}
}
})
/api から始まるリクエストはすべて http://localhost:8082 へ転送されます。
changeOrigin: true によりリクエストの Origin ヘッダーがターゲットに合わせて書き換えられます。
CorsConfig.java(実際のコード)
// config/CorsConfig.java
@Configuration
public class CorsConfig implements WebMvcConfigurer {
/**
* ローカル開発画面向けに API 配下の CORS ルールを登録する。
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
ポイント: allowCredentials(true) を指定しているため、allowedOrigins("*") は使えません。
具体的なオリジン http://localhost:5173 を指定しています。
開発では両方設定している理由: Viteプロキシは「ブラウザ → Vite → Spring Boot」のフローで CORSを回避しますが、Spring Boot 側にも CORS 設定があることで、 将来的にフロントとバックを別オリジンで動かすケースや、Swagger UI 等の直接アクセスにも対応できます。
このシリーズの記事マップ
→ dvdrental 管理アプリと対になるエンドユーザー向けDVDレンタルアプリを作っている話 — Vue 3 + Spring Boot の全体構成と記事マップ