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はかなり現実的だと思います。
ソースコード
こちらにまとめてあります 👇