Webアーキテクチャの進化: PHPの思い出とCORSの悪夢

📖 6min read

「あの頃はファイル一つで十分だったのに…」

学部時代に触っていたPHPは本当に便利だった。index.php という1つのファイルにHTMLタグを書き、その間で <?php ?> を開いてDB検索コードもそのまま書けた。フロントエンドとバックエンドの境界はなかった。変数を1つ宣言すればHTMLのどこでも使えたし、データ通信が原因でエラーになることもほとんどなかった。

だが実務で技術スタックをVue.js(フロント)とSpring Boot(バックエンド)に切り替えた瞬間、地獄が始まった。

「ローカルでSpringサーバーも立てたし、Vueの画面も立ち上がっているのに、どうしてデータが来ないんだ?」

Chromeの開発者ツールのコンソールは真っ赤なエラーメッセージで埋め尽くされていた。

Access to XMLHttpRequest at ... has been blocked by CORS policy

いったいCORSとは何者で、なぜ私のデータを止めるのか。そして、ファイル1つで済んでいたあの楽なやり方を捨て、なぜわざわざフロントとバックを分ける苦労をしているのか。

昔は一つ屋根の下で暮らしていたが、今は別々の家に住み、検問を通らなければならない。

料理を作るのは誰か?(SSR vs CSR)

この変化を理解するには、HTMLという「料理」を誰が作るのかを理解する必要がある。これがまさにSSR(Server Side Rendering)とCSR(Client Side Rendering)の違いだ。

1. SSR: 完成済みのお弁当(PHP, JSP)

2. CSR: ミールキット配送(React, Vue)

[Tip] CSRとSPAは同じ意味ではない

よく同じ意味で使われるが、厳密には別物だ。

私たちは普通、SPAを作るためにCSRを採用する。つまり、ページを1枚にしてチラつきをなくしたいから(SPA)、ブラウザが必要な部分を自分で描き直す方式(CSR)を選んでいるわけだ。

Vue.jsやReactを使う理由はユーザー体験だ。Webサイトではなくスマホアプリのように滑らかに動かすため、料理を作る主体をサーバーからブラウザへ移したのである。

招かれざる客を止める警備員、CORS

フロントエンド(Vue)とバックエンド(Spring)が分離されたことで、私たちは「2つの家」を持つことになった。

ここで問題が起きる。Webブラウザはセキュリティ上の理由から、「別のOriginから来たリソースを簡単に信用してはいけない」という基本原則を持っている。これがSOP(Same Origin Policy)だ。

考えてみてほしい。自分がNaverにログインしている状態で、ハッカーが作ったサイトからこっそりNaverサーバーに「この人の個人情報を出せ」とリクエストが送られたらどうなるか。ブラウザがそれを止めなければ、情報は簡単に抜き取られてしまう。

だからブラウザは、Origin、つまりドメインやポートが違えば、基本的にリクエストを止める。だが私たちは開発のために、あえてポートを分けた。自分のデータを自分で使いたいだけなのに、ブラウザという警備員が「出身ポートが違うだろう。立ち入り禁止だ」と止めてくる。この状況こそがCORS(Cross-Origin Resource Sharing)エラーの正体だ。

[Code Verification] 通行証を発行する

この問題の解決方法は意外と単純だ。サーバー、つまりバックエンドが、警備員であるブラウザに向かって「この人は自分が招待した客だから通してくれ」と通行証を書いてやればいい。

Spring Bootでは、その通行証を1つの設定ファイルで発行できる。

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 WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 1. すべてのAPIパスに対して
                .allowedOrigins("http://localhost:8080") // 2. このアドレスからのリクエストを許可する
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 3. このようなリクエストを許可する
                .allowCredentials(true); // 4. クッキーや認証情報も含められる
    }
}

分析:

ここで気をつけるべきなのは、面倒だからといって allowedOrigins("*") で全部に開放してはいけないということだ。それは家の玄関を全開にして「泥棒さん歓迎」と貼り紙を出すようなものだ。

実務アドバイス: 何をいつ使うべきか?

では、CSR、つまりVueやReactが常に正解なのだろうか。そうではない。実務では目的に応じて選ぶ必要がある。

締めくくり: 境界を越えて

PHP時代の「一つ屋根の下の大家族」は便利だったが、複雑になるほど管理が大変だった。今の「別居した二つの家族(フロント/バック)」は、通信やCORSのせいで面倒ではあるが、それぞれが自分の役割に集中できるようになった。

CORSエラーに出会っても、もうただ苛立つ必要はない。それはブラウザが私たちをいじめているのではなく、私たちの家、つまりサーバーを守るための「警備員の厳重な検問」だったのだとわかったからだ。

さて、これでフロントエンドとバックエンドの通路は開いた。データが行き来できるようになった。では、そのデータはサーバーのどこに保存されるのだろうか。昔はただクエリを投げて持ってくればよかったのに、SpringではJPAだのEntityだののせいで、SQLを直接書かせてくれないように見える。

次回は、「データベース設計の教科書(正規化)」が実務でどう足を引っ張るのか、そしてなぜJPAがSQLを隠そうとするのかを見ていこう。

コメントする