HTTPセッション管理と認証方式の基礎概念

Webアプリケーション開発において、HTTPのステートレスな性質を補完し、ユーザーの状態(ログイン状態など)を維持する仕組みは不可欠である。本稿では、セッション管理の基本用語、認証方式の比較、SPAにおける実装方針、およびSpring Securityにおける内部挙動について整理する。


1. 基本用語の定義

まず、セッション管理を構成する主要な要素について定義する。


2. 認証・セッション管理のパターン

現代のWeb開発において、認証状態の管理は大きく分けて「ステートフル」と「ステートレス」の2パターンが存在する。

ステートフル認証(サーバーサイドセッション)

従来から利用されている標準的な方式である。

ステートレス認証(トークンベース)

マイクロサービスやモバイルアプリ連携の文脈で普及した方式である。

補足:不透明トークン(Opaque Token)とPASETO

JWT以外にも以下のトークン形式が利用される。


3. SPAにおけるセッション管理

Single Page Application (SPA) においても、セキュリティの観点からクッキーを用いたセッション方式(ステートフル)が再評価されている。

JWTを LocalStorage に保存する場合、JavaScriptからのアクセスが容易であるため、XSS(クロスサイトスクリプティング)脆弱性が存在した際にトークンが奪取されるリスクがある。

対して、セッションIDを HttpOnly 属性が付与されたクッキーで管理する場合、JavaScriptからのアクセスは遮断される。これにより、XSS攻撃を受けたとしてもセッションID自体の流出を防ぐことが可能となる。

したがって、フロントエンドとバックエンドが同一ドメイン(またはサブドメイン)で運用可能な場合、SPAであってもクッキーベースのセッション管理を採用することが、セキュリティ上堅牢な選択肢となり得る。


4. Spring Securityにおける挙動

フレームワークを利用した場合、これらのセッション管理や再認証のフローは内部的に処理される。Spring Securityを例にとると、開発者が明示的な分岐を書かずとも未認証ユーザーが弾かれるのは、「フィルターチェーン」の仕組みによるものである。

主なフィルターの役割

リクエストはコントローラーに到達する前に、以下のフィルター群によって処理される。

  1. SecurityContextPersistenceFilter

    リクエストに含まれるクッキー(JSESSIONID)を確認し、サーバー上のセッション情報と紐付ける。

  2. FilterSecurityInterceptor

    アクセス制御のルール(例: .anyRequest().authenticated())と照合し、許可されていない場合は例外を送出する。

  3. ExceptionTranslationFilter

    送出された例外を捕捉し、適切なレスポンス(401 Unauthorized や ログイン画面へのリダイレクト)をクライアントへ返却する。

開発者はこの仕組みにより、ビジネスロジック内で認証チェックの記述を省略できる。


5. 実装サンプル (Java / Spring Security)

最後に、Spring Securityを用いた基本的なセッション管理の実装例を示す。

初回はベーシック認証を行い、成功後はクッキー(JSESSIONID)によってセッションを維持する構成である。

依存関係 (pom.xml)

XML

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

セキュリティ設定 (SecurityConfig.java)

Java

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.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 全てのリクエストに認証を要求
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            // ベーシック認証を有効化
            .httpBasic(Customizer.withDefaults())
            // セッション作成ポリシーの設定(必要に応じて作成)
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            );

        return http.build();
    }
}

コントローラー (HelloController.java)

Java

import jakarta.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(Authentication authentication, HttpSession session) {
        return String.format("User: %s\nSession ID: %s", 
                authentication.getName(), 
                session.getId());
    }
}

設定ファイル (application.properties)

Properties

spring.security.user.name=admin
spring.security.user.password=password123