Java

【2026年版】Spring BootとReactを連携する方法|REST API・CORS・JWT認証まで実装解説

Spring Boot(Java)で作ったバックエンドと、Reactで作ったフロントエンドを連携させるには、「Spring Boot側でREST APIを公開し、ReactからHTTPで呼び出してJSONをやり取りする」という流れを押さえるのが基本です。本記事では、環境構築からAPIの実装、ReactでのAPI呼び出し、つまずきやすいCORSエラーの対処、そしてJWTを使った認証付きAPIの保護まで、動作する最新のコードとともに順を追って解説します。

まとめ:Spring BootとReact連携の要点

先に結論をまとめます。Spring BootとReactの連携は、役割分担を押さえたうえで、次の5ステップで構築できます。

  • 前提(役割分担):Spring Bootは「JSONを返すAPIサーバー」、Reactは「そのAPIを呼び出して画面に表示するクライアント」。両者はHTTP(REST API)で通信する。
  • 手順1:Spring BootでREST API(@RestController)を実装し、JSONを返す。
  • 手順2:ReactからfetchまたはaxiosでAPIを呼び出し、取得したデータをuseStateで表示する。
  • 手順3:オリジンが異なることで起きるCORSエラーを、開発時はViteのプロキシ、サーバー側はCORS設定で解消する。
  • 手順4:認証が必要なAPIには、ReactからAuthorizationヘッダーでトークン(JWT)を送る。
  • 手順5:Spring Security(SecurityFilterChain)でエンドポイントを保護する。

最大のつまずきポイントはCORSと認証まわりです。以降で、各手順を最新のコードとともに具体的に見ていきましょう。

Spring BootとReactの役割分担と全体像

連携を理解するうえで重要なのは、2つの技術の役割がはっきり分かれている点です。Spring BootはJavaベースのバックエンドフレームワークで、データベースとの連携やビジネスロジックを担い、その結果をREST APIとして公開します。ReactはフロントエンドのUIライブラリで、ユーザーが操作する画面を構築し、Spring Bootが公開したAPIを呼び出して受け取ったデータを描画します。

つまり両者は直接コードを共有するのではなく、「APIという窓口を通じてJSON形式のデータをやり取りする」関係です。バックエンドとフロントエンドを分離するこの構成は、SPA(シングルページアプリケーション)開発で広く採用されており、片方だけを差し替えやすい、スマホアプリなど他のクライアントからも同じAPIを使えるといったメリットがあります。

開発環境の準備(Spring InitializrとVite)

まずはバックエンドとフロントエンドのプロジェクトをそれぞれ用意します。

バックエンド:Spring Initializrでプロジェクトを作成

Spring公式のSpring Initializr(start.spring.io)でプロジェクトの雛形を生成します。執筆時点の最新はSpring Boot 4.1系(2026年6月リリース)で、4.0系も現役です。3.5系もしばらくは使えますが、2026年6月末にOSSサポートが終了予定のため、新規開発では4系を選ぶのが無難です。Spring Boot 3.0以降はJava 17が最小要件のため、Java 17以上を選択してください。Spring Boot 4で追加・変更された点はSpring Boot 4の新機能・変更点を徹底解説にまとめています。依存関係には最低限「Spring Web」を追加すればREST APIを実装できます。Spring Bootで使う主要なアノテーションについては、Spring Bootの主要なアノテーション一覧と基本的な役割も参考になります。

フロントエンド:ViteでReactプロジェクトを作成

Reactの環境構築には、高速なビルドツールであるViteが現在の標準です。ターミナルで以下を実行し、Reactプロジェクトを作成します。なお、Viteの開発サーバーの既定ポートは5173です(後述のCORS設定で使います)。

npm create vite@latest react-app -- --template react
cd react-app
npm install
npm run dev

Viteのセットアップをより詳しく知りたい場合は、Viteを使ったReactプロジェクトのセットアップ方法を徹底解説を参照してください。

手順1:Spring BootでREST APIを実装する

まずはバックエンド側に、JSONを返すシンプルなAPIを実装します。@RestControllerを付けたクラスのメソッドで返したオブジェクトは、Spring Bootが自動的にJSONへ変換してレスポンスします。ここでは名前を受け取って挨拶メッセージを返すエンドポイントを作ります。

// GreetingController.java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/api")
public class GreetingController {
 
    // レスポンス用のレコード(JSONに変換される)
    public record GreetingResponse(String message) {}
 
    @GetMapping("/greet")
    public GreetingResponse greet(@RequestParam String name) {
        return new GreetingResponse("Hello, " + name + "!");
    }
}

このAPIを起動し、ブラウザやcurlで http://localhost:8080/api/greet?name=React にアクセスすると、{"message":"Hello, React!"} というJSONが返ります。フロントエンドの実装に進む前に、APIが期待どおりに動くかを必ず確認しておきましょう。

手順2:ReactからAPIを呼び出す(fetch / axios)

次に、React側からこのAPIを呼び出します。コンポーネントの初回表示時にAPIを叩き、結果をuseStateで保持して画面に表示します。ここで重要なのが、useState配列の分割代入で受け取るという点です(const [値, セット関数] = useState(初期値))。標準のfetchを使う例は次のとおりです。

// App.jsx
import { useEffect, useState } from "react";
 
function App() {
  const [greeting, setGreeting] = useState("");
 
  useEffect(() => {
    fetch("http://localhost:8080/api/greet?name=React")
      .then((res) => {
        if (!res.ok) throw new Error("HTTP " + res.status);
        return res.json();
      })
      .then((data) => setGreeting(data.message))
      .catch((err) => console.error("取得に失敗しました:", err));
  }, []); // 空配列で初回マウント時のみ実行
 
  return <h1>{greeting}</h1>;
}
 
export default App;

useEffectの第2引数に空配列[]を渡すことで、API呼び出しがコンポーネントの初回マウント時に一度だけ実行されます。ここを省略すると再レンダリングのたびにAPIを呼び続けてしまうため注意してください。

HTTPクライアントライブラリのaxiosを使うと、JSONのパースを自動で行ってくれるなど記述がやや簡潔になります。同じ処理をaxiosで書くと次のようになります。

// axiosを使う場合
import axios from "axios";
 
axios
  .get("http://localhost:8080/api/greet", { params: { name: "React" } })
  .then((res) => setGreeting(res.data.message))
  .catch((err) => console.error(err));

fetchとaxiosの比較

どちらを使ってもかまいません。標準搭載のfetchは追加インストール不要で手軽、axiosはJSONの自動変換や共通ヘッダーの一元管理ができる点が便利です。簡単な比較を示します。

項目 fetch axios
導入 標準搭載 要インストール
JSON変換 手動(.json()) 自動
エラー判定 res.okで確認 例外で捕捉
共通ヘッダー 都度指定 インスタンスで共有

小規模なら標準のfetch、リクエストが増え共通設定をまとめたいならaxios、という使い分けが目安です。

手順3:CORSエラーへの対処

ReactとSpring Bootを別々のポート(例:Reactが5173、Spring Bootが8080)で動かすと、オリジンが異なるため、ブラウザの同一オリジンポリシーによってAPIリクエストがブロックされます。これがいわゆるCORSエラーです。コンソールに「Access-Control-Allow-Origin ヘッダーが無い」といったメッセージが出るのが典型です。背景の仕組みはクロスオリジンリソース共有(CORS)とは何か?その基本概念と重要性を解説Same-Origin Policy(同一オリジンポリシー)の基本的な仕組みと役割で詳しく解説しています。

方法A:開発時はViteのプロキシを使う

開発中であれば、Vite側でAPIへのリクエストをプロキシする方法が手軽です。vite.config.jsに以下を追記すると、/api宛てのリクエストがSpring Boot(8080)へ転送され、ブラウザから見たオリジンが同一になるためCORSが発生しません。

// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
 
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      "/api": "http://localhost:8080",
    },
  },
});

方法B:サーバー側でCORSを許可する

本番環境などフロントとバックを別ドメインで運用する場合は、Spring Boot側で許可するオリジンを明示します。WebMvcConfigurerを実装してグローバルに設定する例です。

// 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("/api/**")
                .allowedOrigins("http://localhost:5173") // Vite開発サーバ/本番は実際のドメイン
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

特定のコントローラーだけ許可したい場合は、クラスやメソッドに@CrossOrigin(origins = "http://localhost:5173")を付ける方法もあります。なおallowedOrigins"*"(ワイルドカード)を指定すると、allowCredentials(true)と併用できない点に注意してください。どうしてもパターンで許可したい場合はallowedOriginPatterns("*")を使えば資格情報付きでも併用できますが、本番では許可するオリジンを必ず具体的に限定するのが安全です。

手順4:認証付きAPIをReactから呼び出す(JWT)

ログインが必要なAPIでは、リクエストに認証情報を添えます。一般的なのがJWT(JSON Web Token)を使う方式で、ログイン後にサーバーから受け取ったトークンを、以降のリクエストのAuthorizationヘッダーにBearer形式で付与します。

// 認証付きAPIの呼び出し例
const token = getToken(); // 保存場所からトークンを取得(後述)
 
fetch("http://localhost:8080/api/secure/profile", {
  headers: { Authorization: `Bearer ${token}` },
})
  .then((res) => res.json())
  .then((data) => setProfile(data))
  .catch((err) => console.error(err));

トークンはユーザーになりすますための鍵になり得るため、保存と送信には注意が必要です。フロントエンドに保存したトークンは攻撃者から読み取られるリスクがあるため、漏洩時の被害を抑える設計が重要です。主なポイントは次のとおりです。

対策 狙い
短い有効期間 漏洩時の悪用可能な時間を短くする
HttpOnlyクッキー JSから読めない場所に保存しXSS被害を軽減
HTTPS通信 通信経路上での盗聴を防ぐ
ハードコード禁止 ソースコードへの直書きを避ける

特に機密性の高いトークンは、JavaScriptから読めないHttpOnlyクッキーに保存するのが安全側の選択です。localStorageはXSS(クロスサイトスクリプティング)で盗まれる恐れがあるため、機密トークンの保存先としては推奨されません。

手順5:Spring SecurityでAPIを保護する(SecurityFilterChain)

Spring Bootで作ったAPIは、初期状態ではアクセス制御がかかっていません。Spring Securityを導入してエンドポイントを保護します。ここで重要な注意点として、かつて使われていたWebSecurityConfigurerAdapterを継承する書き方はSpring Security 6(Spring Boot 3.0以降)で削除されました。現在はSecurityFilterChainのBeanを定義する「コンポーネントベース」の書き方が正解です。

// SecurityConfig.java(Spring Security 6 / 7)
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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
@Configuration
@EnableWebSecurity
public class SecurityConfig {
 
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .cors(Customizer.withDefaults())
            .csrf(AbstractHttpConfigurer::disable) // ステートレスなJWT APIではCSRFを無効化
            .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // セッションを作らない(JWTで都度認証)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/open/**").permitAll()      // 公開エンドポイント
                .requestMatchers("/api/secure/**").authenticated() // 認証が必要
                .anyRequest().authenticated()
            )
            .addFilterBefore(new JwtAuthenticationFilter(),
                    UsernamePasswordAuthenticationFilter.class);   // JWT検証フィルタ
        return http.build();
    }
}

ポイントは、旧来のauthorizeRequests()antMatchers()authorizeHttpRequests()requestMatchers()に置き換わっている点です。JWTの検証はJwtAuthenticationFilter(実装は別途必要)の中で行い、有効なトークンを持つリクエストだけを通過させます。Spring Securityの仕組み全体はSpring Securityとは何か?その基本と重要性を解説で整理しています。

開発時と本番でのデプロイ構成(補足)

開発中はReact(5173)とSpring Boot(8080)を別ポートで起動し、プロキシやCORS設定で連携させるのが一般的です。一方、本番では大きく2つの構成があります。1つはReactをビルドした静的ファイルをSpring Bootに同梱して1つのサーバーから配信する方法、もう1つはフロントとバックを別々にホスティングしてCORSで連携する方法です。

Reactのビルド成果物をSpring Bootへ同梱する

Spring Bootは既定で、クラスパス上の/static(ほかに/public/resources/META-INF/resources)に置かれた静的ファイルを配信します。その際まずindex.htmlを探すため、Viteの出力先をsrc/main/resources/staticに向けるだけで同梱構成が成立します。Viteのbuild.outDirは既定がdistなので、次のように上書きします。フロントとバックを別ディレクトリに置く構成の例です。

// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  build: {
    outDir: "../backend/src/main/resources/static",
    emptyOutDir: true,
  },
});

emptyOutDirを明示しているのは、出力先がViteのプロジェクトルート外にあると「重要なファイルを消す事故を避けるため」の警告が出るからです。この構成ならnpm run buildのあとに./mvnw packageを実行するだけで、1つのjarにフロントエンドとバックエンドが収まります。同一オリジンから配信されるので、手順3で設定したCORSも本番では不要になります。

ただし1点だけ注意が必要です。静的コンテンツの配信は/**にマップされるため、React Routerのクライアントルーティングで/users/1のようなURLをブラウザに直接入力すると、対応するファイルが存在せずNoResourceFoundExceptionになります。SPAとして成立させるには、APIパス以外のリクエストをindex.htmlへフォールバックさせる設定を別途入れてください。

よくある質問(FAQ)

Q. Spring BootとReactは同じプロジェクトにまとめるべきですか?

開発中はバックエンドとフロントエンドを別ディレクトリ・別ポートで動かすのが一般的です。本番ではReactのビルド成果物をSpring Bootに同梱して1サーバーで配信することも、別々にホスティングすることもできます。要件に合わせて選びましょう。

Q. CORSエラーはReact側とSpring Boot側のどちらで直すのですか?

CORSはブラウザのセキュリティ機構のため、最終的な許可はサーバー(Spring Boot)側でオリジンを許可して解決します。開発中に限ってはViteのプロキシでオリジンを揃え、CORS自体を発生させない回避策も有効です。

Q. WebSecurityConfigurerAdapterが使えないのはなぜですか?

このクラスはSpring Security 5.7で非推奨となり、Spring Security 6(Spring Boot 3.0以降)で削除されました。現在はSecurityFilterChainのBeanを定義する方式に置き換わっています。古い記事のコードをそのまま使うとコンパイルエラーになるため注意してください。

Q. トークンはlocalStorageに保存してよいですか?

機密性の高いトークンの保存先としては推奨されません。localStorageはJavaScriptから読めるためXSSで盗まれる恐れがあります。HttpOnlyクッキーに保存し、有効期間を短く保つのが安全です。

関連記事

資料請求

RELATED POSTS 関連記事