|

Spring Boot API開発で理解しておきたいCORS / CSRF / セッションレス設計

この記事はSpring BootでAPIを開発しており、CORS、CSRF、セッション管理をどのように設定すべきか迷っている人向けです。「とにかく全部無効にすればよいのか?」という疑問に対し、それぞれの役割を整理し、設計意図を理解できるように解説します。

CORSとは

CORS(Cross‑Origin Resource Sharing)はブラウザが別オリジンのリソースをJavaScriptに渡してよいかを決める仕組みであり、APIの認証・許可ではありません。ReactやAngularなどのSPAからバックエンドAPI(異なるポートやドメイン)を呼び出すとき、ブラウザはまずリクエストヘッダーのOriginを見て、レスポンスのAccess‑Control‑Allow‑Originなどのヘッダーで許可されているか確認します。ここで拒否されるとアプリケーションコードまでレスポンスが届きません。

プリフライトリクエスト

POSTやカスタムヘッダーを含むリクエストでは、ブラウザは本番リクエストの前にOPTIONSメソッドによるプリフライト(事前確認)リクエストを送ります。このリクエストにはCookieが含まれないため、CORS処理がSpring Securityの前に起動しないと「未認証」として拒否されてしまいます。cors()をSecurityFilterChainの前段で有効にし、CorsConfigurationSourceCorsWebFilterを使って正しく設定する必要があります。

以下はServletアプリケーションでのCORS設定例です。事前に許可するオリジン、メソッド、ヘッダーを明示し、プリフライトで拒否しないようにします。

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("https://example.com", "http://localhost:3000"));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
    // Cookieを使わない場合はAllowCredentialsをfalseにする
    config.setAllowCredentials(false);
    config.setMaxAge(3600L);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        .csrf(csrf -> csrf.disable())
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
        .build();
}

Spring WebFluxアプリケーションではCorsWebFilterをBean登録して同様の設定を行います。このフィルターはセキュリティより先に実行されるため、プリフライトの失敗を防げます。

CSRFとは

CSRF(Cross‑Site Request Forgery)は、ログイン中のユーザーのブラウザに保存された認証情報(主にCookie)を悪用し、ユーザーの意図しないリクエストを送らせる攻撃です。共有の例として、銀行の送金フォームに対し悪意あるサイトが自動でPOSTリクエストを送るケースが挙げられます。サーバー側はブラウザが自動送信するCookieしか確認できないため、本人からの正規リクエストと区別がつきません。

CSRFが成立しやすい条件は次のとおりです:

  • ユーザーが対象サイトにログイン済みで、認証情報がCookieとして自動送信される。
  • リクエストが状態を変更する(POST/PUT/DELETEなど)。
  • サーバー側に追加の検証がない。

CSRFトークンとSPAサポート

フォームベースのアプリケーションでは、Spring SecurityはCSRFトークンを自動的に生成し、フォームに埋め込むためCSRF攻撃を防げます。SPAやREST APIではJSONをやり取りするため、トークンをヘッダーに載せて送信する実装が一般的です。CookieCsrfTokenRepositoryを設定すると、CSRFトークンをCookie(名前はXSRF‑TOKEN)に保存し、ブラウザがそれを読み受ってX‑XSRF‑TOKENヘッダーにセットすることができます。

Spring Security 6ではSPA向けにcsrf.spa()が追加されました。CsrfConfigurer#ignoringRequestMatchersより適切なプリセットで、ログインやログアウト直後にCSRFトークンを更新し、BREACH攻撃に対する純第的な安全対策も組み込まれています。SPA構成でCookieを使う場合は、このcsrf.spa()を利用すると安全かつ実装が簡単です。

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
        .cors(Customizer.withDefaults())
        .csrf(csrf -> csrf
            // SPA用のCSRF設定を利用
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .ignoringRequestMatchers("/api/public/**")
        )
        .authorizeHttpRequests(auth -> auth
            .requestMatchers(HttpMethod.GET, "/api/public/**").permitAll()
            .anyRequest().authenticated())
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
        .build();
}

Cookieにトークンを保存する場合はHttpOnly属性を付けない設定(withHttpOnlyFalse())に注意してください。JavaScriptからトークンを読み出せる反面、XSS対策がより重要になります。

セッションレス設計

「セッションレス」とはサーバー側でHTTPセッション(HttpSession)を使わずにユーザーの認証状態を管理する設計を指します。トークンベース認証(JWTやOAuth2アクセストークンなど)では各リクエストに認証トークンを付与して認証するため、サーバーはセッションを保持する必要がありません。

Spring SecurityではsessionManagementSessionCreationPolicy.STATELESSを指定すると、認証情報をHTTPセッションに保存しなくなります。内部的にはNullSecurityContextRepositoryが使われ、リクエストごとにSecurityContextを作成しトークンから認証を行います。またSpring Security 6ではSecurityContextHolderFilterにより、セキュリティコンテキストの残しかがフィルターレベルに移動し、必要な場合は明示的に保存する設計になっています。

ただし、セッションレスでもブラウザがCookieを自動送信していればCSRFリスクは残ります。認証トークンをCookieに保存する場合は、SameSite=LaxStrictを検討し、CSRF保護を無効化しないようにしましょう。

設計判断のポイント

CORS、CSRF、セッションレスは別のレイヤーの仕組みですが、実際の設計では以下の见点を抱えると判断しやすくなります。

  • 認証情報の保存場所 – Cookie、セッション、JWTなど。
  • 認証情報の送信方法 – ブラウザが自動送信するか(Cookie)、JavaScriptで明示的に送るか(Authorizationヘッダー)。
  • APIの利用元 – 同一オリジンか別オリジンか。
  • ブラウザを経由するか – モバイルアプリやサーバー同士の通信ではCORSは不要。
  • ユーザーインタフェース – SPAなのかサーバーサイドレンダリングなのか。

よくある構成と設定

構成CORSCSRFセッション
SPA + API + JWT(Authorizationヘッダー)必要。
特定Originのみ許可。
無効化しやすい。
csrf.spa()不要。
STATELESS
SPA + API + Cookie認証必要。
allowCredentialsをtrueにし、オリジンを明示。
必須。
CookieCsrfTokenRepositorycsrf.spa()を利用。
セッションありまたはトークン保存Cookie。
サーバーサイドHTML + セッションログイン基本不要(同一オリジン)。必須。
フォームにCSRFトークンを埋め込む。
IF_REQUIRED(デフォルト)
モバイルアプリや内部サーバー不要(ブラウザを介さない)。認証情報を手動付与するため不要。STATELESSが多い。

とCORSはブラウザによるオリジン制御、CSRFは自動送信さる認証情報の悪用防止、セッションレスはサーバが認証状態を保持しない設計と、それぞれ別物です。セッションレスだからCSRF対策が不要という話ではなく、認証情報をCookieに保存するかどうかで判断する必要があります。また、プリフライトリクエストを考慮したCORS設定や、SPA向けのCSRFトークン運用といった最新の機能も抱えておくと安心です。

Spring Boot APIのセキュリティ設定はコピペで済ませるのではなく、どの設定がどの脈引に対応しているのかを理解した上で選択しましょう。

是非フォローしてください

最新の情報をお伝えします

類似投稿