CORSとは?オリジン・プリフライト・エラーの仕組みまで完全解説

知識

フロントエンド開発を進める中で、ブラウザのコンソールに「Access to fetch … has been blocked by CORS policy」というエラーが表示されて困った経験はないでしょうか?

この記事では、オリジンとは何か・CORSがなぜ必要なのか・プリフライトの仕組みを順序立てて解説し、Vue+Spring Bootを使った具体的な対処法まで紹介します。初学者〜中級者のWeb開発者を対象としています。


この記事で学べること

  • オリジン(Origin)の定義と同一オリジン・別オリジンの判定方法
  • 同一オリジンポリシー(SOP)とCORSの関係
  • プリフライトリクエストが発生する条件と仕組み
  • Vue(Viteプロキシ)+ Spring Boot でのCORS対処法
  • 主要なCORS関連HTTPヘッダーの意味と注意点


オリジンとは何か

オリジン(Origin)とは、プロトコル・ホスト・ポート番号の3要素の組み合わせのことです。この3つがすべて一致するとき「同一オリジン」、一つでも異なると「別オリジン(クロスオリジン)」と呼びます。

基準オリジン https://example.com:443 を元に、各URLが同一オリジンかどうかを見てみましょう。

URL基準との関係理由
https://example.com:443/api/users✅ 同一オリジンパスは判定に関係ない
http://example.com:443❌ 別オリジンプロトコルが異なる
https://api.example.com:443❌ 別オリジンホストが異なる
https://example.com:8080❌ 別オリジンポートが異なる

💡 例え話:マンションのセキュリティ
オリジンを「マンションの住所+部屋番号」に例えると、建物・棟・部屋番号がすべて一致しないと「同じ部屋の住人」とは見なされません。プロトコル・ホスト・ポートのどれか一つでも違えば、ブラウザは「別の場所」と判断します。


CORSの仕組み

ブラウザには同一オリジンポリシー(SOP: Same-Origin Policy)というセキュリティ制約があります。これはデフォルトで、別オリジンへのリクエストで返ってきたレスポンスをJavaScriptに渡さないようにする仕組みです。

CORS(Cross-Origin Resource Sharing)はこの制約を安全に緩和するための仕組みです。サーバー側がHTTPレスポンスヘッダーで「このオリジンからのアクセスは許可する」と宣言することで、ブラウザがレスポンスをJavaScriptに渡すことを許可します。

CORSの基本的な流れは以下のとおりです。

  1. ブラウザがサーバーへHTTPリクエストを送信する
  2. サーバーがCORSヘッダー付きのレスポンスを返す
  3. ブラウザがヘッダーを確認し、許可されていればJavaScriptにレスポンスを渡す。許可されていなければブロックする(=CORSエラー)

⚠️ よくある誤解
CORSエラーが発生しても、サーバーへのリクエスト自体は届いています。ブラウザがレスポンスをJavaScriptに渡さないだけです。そのため、副作用を持つAPIへのリクエスト(POSTなど)は特に注意が必要です。


プリフライトリクエスト

「単純リクエスト」以外の場合、ブラウザは本リクエストを送る前にOPTIONSメソッドによる事前確認(プリフライト)を自動で行います。

プリフライトが不要な「単純リクエスト」の主な条件:

  • メソッドが GET / POST / HEAD のいずれか
  • Content-Type が application/x-www-form-urlencoded・multipart/form-data・text/plain のいずれか
  • カスタムリクエストヘッダーを含まない

プリフライトが必要な代表的なケース:

  • Content-Type: application/json を使うPOSTリクエスト
  • PUT / DELETE / PATCH メソッドを使うリクエスト
  • Authorization ヘッダーを含むリクエスト

プリフライトが発生する場合のフローは次のとおりです。

  1. ブラウザがOPTIONSメソッドでプリフライトリクエストを送信する
  2. サーバーが200 OKと許可情報(CORSヘッダー)を返す
  3. ブラウザが許可を確認し、本リクエスト(POST等)を送信する
  4. サーバーが本リクエストへのレスポンスを返す

💡 Access-Control-Max-Age によるキャッシュ
サーバーが Access-Control-Max-Age: 3600 を返すことで、ブラウザはその秒数の間、プリフライト結果をキャッシュし、毎回のOPTIONSリクエストを省略できます。パフォーマンス改善に有効です。


Vue + Spring Boot での実装例

以下の構成を前提に、フロントエンド・バックエンドそれぞれの設定例を示します。

役割技術オリジン
フロントエンドVue(Vite)https://frontend.com
バックエンドSpring Boothttps://api.backend.com

Vue側:開発環境のViteプロキシ設定

開発環境ではViteのプロキシ機能を使い、同一オリジンとして扱うことでCORSを回避できます。

// vite.config.ts
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      }
    }
  }
})

Spring Boot側:グローバルCORS設定(推奨)

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://frontend.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

Spring Security と併用する場合

🚨 注意:Spring Securityを導入している場合、WebMvcConfigurer の設定だけでは効かないことがあります。Security側にも必ずCORS設定を追加してください。

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
        .csrf(csrf -> csrf.disable());
    return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("https://frontend.com"));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(List.of("*"));
    config.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/api/**", config);
    return source;
}


CORSの主要ヘッダー一覧

レスポンスヘッダー(サーバーが返す)

ヘッダー名説明
Access-Control-Allow-Origin許可するオリジン(例: https://frontend.com または *)
Access-Control-Allow-Methods許可するHTTPメソッド(例: GET, POST, PUT, DELETE)
Access-Control-Allow-Headers許可するリクエストヘッダー(例: Content-Type, Authorization)
Access-Control-Allow-CredentialsCookieなどのCredentialを許可するか(true を指定)
Access-Control-Max-Ageプリフライト結果のキャッシュ時間(秒)
Access-Control-Expose-HeadersJavaScriptから読めるレスポンスヘッダーを追加指定

リクエストヘッダー(ブラウザが自動で付与)

ヘッダー名説明
Originリクエスト元のオリジン
Access-Control-Request-Methodプリフライト時に使用予定のメソッドを通知
Access-Control-Request-Headersプリフライト時に使用予定のカスタムヘッダーを通知

⚠️ ワイルドカード * の注意点
Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true と同時には使えません。CookieやAuthorizationヘッダーなど認証情報を扱う場合は、ワイルドカードではなく特定のオリジンを明示的に指定してください。


CORSエラーとは何か

CORSエラーとは、「ブラウザがサーバーからのレスポンスをJavaScriptに渡さずブロックすること」そのものを指します。JavaScriptの catch 内では詳細なエラーメッセージは確認できず、TypeError: Failed to fetch しか表示されません。

fetch('https://api.backend.com/users')
  .catch(err => {
    console.error(err)
    // → TypeError: Failed to fetch
  })

💡 CORSエラーの詳細なメッセージは ブラウザの開発者ツール(コンソールタブ) で確認できます。「blocked by CORS policy」という文言とともに、どのヘッダーが不足しているかが表示されるので、まずそちらを確認しましょう。


まとめ

テーマポイント
オリジンプロトコル・ホスト・ポートの3要素の組み合わせ。1つでも違えば別オリジン
CORSの仕組みサーバーがレスポンスヘッダーで許可オリジンを宣言。ブラウザが確認して許可・拒否を判断
プリフライト複雑なリクエストの前にOPTIONSで事前確認。Max-Ageでキャッシュ可能
Vue + Spring Boot開発はViteプロキシ、本番はSpring BootのCORS設定。Spring Security使用時は両方に設定が必要
CORSエラーブラウザがJSへのレスポンス渡しをブロックすること。詳細は開発者ツールで確認


参考リソース

コメント

タイトルとURLをコピーしました