Spring Boot 3 & PostgreSQLによるREST API構築:単なる「動くコード」から本番環境レベルのシステムへ

Development tutorial - IT technology blog
Development tutorial - IT technology blog

現実の課題:APIは「動く」だけでは不十分

キャリアの初期、私は意気揚々とFrontendチームにSpring Bootで作ったAPIを納品したことがありました。当時は、正しいJSONさえ返せれば十分だと思っていました。しかし、結果は散々なものでした。わずか1週間後、レスポンスに機密情報を含めていたために1,000件以上のユーザーレコードが流出。さらに、権限管理(Authorization)が不十分だったため、リンクさえ知っていれば誰でも他人の投稿を削除できる状態になっていました。

多くのジュニアエンジニアが今でも同じ間違いを繰り返しています。個人のPCではスムーズに動くコードも、セキュリティ監査やユーザー数の増加に直面すると、すぐに「崩壊」してしまいます。実務レベルのシステムでは、大学の課題などとは比較にならないほど、カプセル化とセキュリティに対する厳格さが求められます。

本番環境への導入が拒否される3つの「致命的な欠陥」

Fintechプロジェクトで5万行以上のコードベースのリファクタリングに参加した際、私は最も一般的な3つのミスに気づきました:

  • おろそかなセキュリティ管理: 未だにBasic認証を使用したり、パスワードをプレーンテキストで保存したりしているケース。これはハッカーにとって絶好の標的です。
  • Entityの乱用: データベースのオブジェクトをそのままAPIのレスポンスとして返すのは重大なミスです。パスワードのハッシュ値や不要な内部IDを意図せず公開してしまいます。
  • 設定のハードコーディング: JWTのシークレットキーなどをコード内に直接記述すると、キーを変更するだけでプロジェクト全体を再ビルドしなければならなくなります。

アーキテクチャの選択:なぜJWTなのか?

認証の処理において、通常はSessionかTokenのどちらかを選択します。従来のSessionベースは、3〜4台のサーバーでロードバランシングを行う際にスケールさせるのが困難です。一方、Basic認証はすべてのリクエストにパスワードを付与するため、安全性が極めて低いです。

JWT (JSON Web Token) は、REST APIのゴールデンスタンダードとして定着しました。システムをステートレス(状態を持たない)に保つことができ、Webとモバイルアプリの両方に簡単に統合できます。以下に、PostgreSQLと多層防御を組み合わせた標準的なプロジェクトの構築方法を説明します。

Hướng dẫn triển khai chi tiết

1. PostgreSQLの初期化と適切な設定

まずは Spring Initializr にアクセスしましょう。*Spring Web, Spring Data JPA, PostgreSQL Driver, Spring Security, Lombok* のライブラリを選択します。

古い .properties ファイルではなく、階層構造が明確で読みやすい application.yml の使用を推奨します。DB接続を次のように設定します:

spring:
  datasource:
    url: jdbc:postgresql://${DB_HOST:localhost}:5432/${DB_NAME:mydb}
    username: ${DB_USER:postgres}
    password: ${DB_PASS:password}
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: false

重要な注意点: このファイルに実際のパスワードを直接書き込まないでください。${VAR:default} という構文を使うことで、DockerやKubernetesでデプロイする際に環境変数を柔軟に渡せるようになります。

2. モデルの設計と権限管理 (RBAC)

ロールベースのアクセス制御(Role-based Access Control)は必須です。UserRole を多対多(Many-to-Many)の関係で分離する必要があります。このアプローチにより、コードを修正することなくユーザーをADMINに昇格させることが容易になります。

@Entity
@Table(name = "users")
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true)
    private String email;
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    private Set<Role> roles = new HashSet<>();
}

3. Spring Security 6の設定:最大の難所

Spring Security 6では、従来の WebSecurityConfigurerAdapter を継承する方法が廃止されました。現在は関数型の設定(functional configuration)に移行しています。ここがAPIの「検問所」を設定する場所です。

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/v1/auth/**").permitAll()
            .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
        )
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        )
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

4. JWTサービスの管理

Tokenの生成には jjwt ライブラリを使用します。ヒント:Access Tokenの有効期限(Expiration)は15分などの短時間に設定しましょう。その上でRefresh Tokenを組み合わせることで、セキュリティを確保しつつ、ユーザー体験を損なわないようにします。

Dockerによるパッケージング:本番環境への準備

「自分のマシンでは動くのに、サーバーでは動かない」というミスを防ぐため、アプリケーションを常にDockerでパッケージングします。これにより、開発環境と本番環境を同一に保つことができます。

プロジェクト用の最小限의 Dockerfile

FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY target/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

docker-compose.yml と組み合わせれば、コマンド一つでアプリとデータベースの両方を起動できます。インフラ管理が格段に楽になります。

実務からのアドバイス

私はかつて、古いシステムのXML設定ファイルのミス一つで、バグ修正に一晩中費やしたことがあります。最大の教訓は、「機械のためだけでなく、後から読む人のためにコードを書く」ということです。透明性の高い設定を持つSpring Boot 3を採用することで、メンテナンスチームは毎月数十時間の作業時間を節約できます。

ログインや登録のフローについては、必ずインテグレーションテスト(Integration Test)を書いてください。優れたテストがあれば、金曜日の午後にデプロイボタンを押しても、真夜中に上司から電話がかかってくる心配はありません。これらのステップをマスターすれば、プロフェッショナルなバックエンドシステムを構築する準備は万全です。

Share: