0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ChatGPTを使って「メールOTP 2FA」をJavaでライブラリ化した話(Spring / Servlet 両対応)

Last updated at Posted at 2026-01-03

Webアプリのセキュリティを強化したくて、Javaで
「メールOTP(ワンタイムパスワード)」による2段階認証(2FA) を実装しました。

今回は単発の実装ではなく、

  • いろいろなアプリに横展できる
  • Webフレームワークに依存しすぎない
  • 将来的な拡張(TOTPやRedisなど)も見据えられる

という点を重視して、オープンソース的に再利用しやすい構成で作っています。

その設計・実装をかなりの部分 ChatGPT に相談しながら進めた ので、
「どう使ったか」「どこが助かったか」も含めてまとめます。

この記事でわかること

  • メールOTP 2FAを「使い回せるライブラリ」として設計する考え方
  • マルチモジュール構成(core / storage / mail / web / sample)の役割
  • Servlet版 / Spring版の両方に対応する設計
  • サンプルアプリ(Spring / Servlet)での動作確認方法
  • 実際にハマったエラーと解決(DataSource 周り)

なぜメールOTPの2FA?(TOTPと比較)

2FA には大きく分けて以下があります。

  • TOTP(Google Authenticator などの認証アプリ)
  • メールOTP(メールで6桁コードなどを送る)

TOTP はセキュリティ面では強い一方で、

  • 認証アプリのインストール
  • 初回登録(QRコード読み取り)

といったハードルがあります。

一方、メールOTPは

  • ユーザー体験がシンプル
  • 既存ユーザーにも導入しやすい

というメリットがあり、「まず2FAを導入したい」段階では現実解になりやすいです。

今回は 複数プロダクトに横展する共通部品 として、
まずは メールOTPをライブラリ化 しました。

プロジェクト構成:再利用最優先のマルチモジュール

今回作ったのは、単なるアプリ実装ではなく
email-otp-2fa という共通ライブラリ群です。

email-otp-2fa/
  pom.xml
  otp-core/              ← OTP生成/検証/状態遷移(純Java)
  otp-storage/           ← 永続化(現状は JDBC/H2 実装)
  otp-mail/              ← メール送信(Simple Java Mail / ログ出力)
  otp-web-servlet/       ← Servlet向け(Filter / Servlet)
  otp-web-spring/        ← Spring向け(AutoConfig / Filter / Controller)
  otp-spring-sample/     ← Spring Bootサンプル(H2)
  otp-servlet-sample/    ← Servletサンプル(Jettyで起動)

設計のポイント

  • otp-core は純Java

    • Spring / Servlet / DB に一切依存しない
  • Web層は「薄く」

    • 判定は core、Web はセッション制御だけ
  • 永続化・メール送信は差し替え前提

これにより、

  • Spring Boot
  • Servlet(Tomcat / Jetty など)

どちらにも 同じ思想・同じロジックで組み込み可能 になります。

ChatGPTをどう使ったか

ChatGPT には主に以下を相談しました。

  • モジュール分割(責務の切り方)
  • pom.xml(マルチモジュール構成)
  • OTPの状態遷移設計
  • Servlet版 / Spring版の設計を揃える
  • 起動エラーのログから原因を切り分ける

特に、

pom.xml を全部先に揃える

という進め方はかなり助かりました。
ここが崩れると、後で地獄を見ます。

OTP検証の中心:OtpVerifier(純ロジック)

この仕組みの中心は OtpVerifier です。

ここは

  • DBに依存しない
  • Webフレームワークに依存しない

純粋なビジネスロジック だけを持っています。

判定しているのは以下。

  • OTPが一致するか
  • 有効期限切れか
  • 試行回数オーバーか
  • 状態をどう遷移させるか

状態遷移

PENDING
 ├─ 正しいOTP → VERIFIED
 ├─ 間違い     → PENDING(attempts +1)
 ├─ 期限切れ   → EXPIRED
 └─ 回数超過   → LOCKED

Web層(Servlet / Spring)は
この結果に従って セッションを書き換えるだけ です。

Web層:Servlet版とSpring版を用意

実運用では Web 技術がプロジェクトごとに違うため、
2種類の Web 層を用意しました。

otp-web-servlet

  • Filter / Servlet ベース
  • Servlet コンテナ(Jetty / Tomcat など)で利用可能
  • サンプルは Jetty で起動

otp-web-spring

  • Spring Boot 用 AutoConfiguration
  • Filter + Controller で後付け可能
  • Spring Security なしでも動作

どちらも 同じセッションキー設計 なので、
ドキュメントや運用を揃えやすくしています。

動作確認:2つのサンプル

otp-spring-sample

  • Spring Boot
  • H2 Database
  • /login → /mfa/verify → /app
mvn -pl otp-spring-sample spring-boot:run

otp-servlet-sample

  • Servlet ベース
  • Jetty 起動
  • /login → OTP入力 → /app
mvn -pl otp-servlet-sample jetty:run

「まず end-to-end で動く最小」を作ったことで、
横展が一気に楽になりました。

ハマりポイント:DataSource 周り

エラー①

required a bean of type 'javax.sql.DataSource' that could not be found

spring-boot-starter-jdbc が無い。

エラー②

No supported DataSource type found

→ 手動で DataSourceBuilder を使っていたが、
クラスパスに DataSource 実装(HikariCP など)が無い。

解決策

  • DataSource の手動 Bean 定義を削除
  • Spring Boot の自動構成に任せる
  • spring-boot-starter-jdbc を追加

結果、H2 + JDBC が素直に立ち上がりました。

次にやりたいこと

  • OTP再送制限(クールタイム / レートリミット)
  • Spring Security 連携
  • Spring Boot Starter 化
  • TOTP 追加(同じ境界設計で差し替え)

まとめ:ChatGPTは「設計の筋」を保ったまま加速できる

今回のように、

  • ライブラリ化
  • 横展前提
  • Servlet / Spring 両対応

といった設計は、途中で整合性が崩れがちです。

ChatGPT は単なるコード生成というより、

  • 責務分割の整理
  • 状態遷移の明文化
  • 実装間の思想統一
  • ログからの原因特定

といった 設計の軸を保つ 役割で非常に効きました。

「2FAを自分のアプリ群へ横展したい」
「共通部品として整備したい」

そんな人の最初の一歩として、
メールOTPはかなり現実的だと思います。

ソースコード

こちらにまとめてあります 👇

https://github.com/livlog-llc/email-otp-2fa

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?