Twilio二要素認証の導入ノウハウ
AI(Gemini)と会話しながら成功に至るまでのノウハウと注意点を以下にまとめます。
1. Twilioの2要素認証利用方法
Twilio Verify APIを使用した二要素認証の基本的な流れは以下の通りです。
-
ユーザー認証: ユーザーがIDとパスワードでログインを試みます。
-
電話番号の取得: ログイン成功後、データベースからそのユーザーに紐づく電話番号を取得します。
-
認証コードの送信: 取得した電話番号と、Twilio Verifyで作成したService SIDを使用して、認証コードをSMSで送信します。この処理は
LoginServlet.java
のVerification.creator()
メソッドで行われます。 -
コードの検証: ユーザーが認証コードを入力し、
Verification.checker()
メソッドでそのコードが正しいか検証します。 -
ログイン完了: 検証が成功すれば、ログインが完了します。
2. 環境と前提条件
このアプリケーションを正しく動作させるための環境と前提条件は以下の通りです。
- Java環境: Java JDKがインストールされていること。
>java -version
openjdk version "24.0.2" 2025-07-15
OpenJDK Runtime Environment (build 24.0.2+12-54)
OpenJDK 64-Bit Server VM (build 24.0.2+12-54, mixed mode, sharing)
- Tomcat: Apache Tomcatがインストールされていること。
>bin\version.bat
Server version: Apache Tomcat/9.0.105
Server built: May 7 2025 18:36:02 UTC
Server number: 9.0.105.0
OS Name: Windows 10
OS Version: 10.0
Architecture: amd64
JVM Version: 24.0.2+12-54
JVM Vendor: Oracle Corporation
-
MySQL:
login_db
データベースにusers
テーブルが作成され、電話番号カラム(phone_number
)が追加されていること。
> mysql -u root -p
Enter password: *******
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 21
Server version: 8.0.31 MySQL Community Server - GPL
mysql> select * from users ;
+----+----------+-------------+---------------+
| id | username | password | phone_number |
+----+----------+-------------+---------------+
| 1 | admin | password123 | +819XXXXXXXXXX|
+----+----------+-------------+---------------+
1 row in set (0.04 sec)
- Twilioアカウント: Twilioアカウントと、Verify Serviceが作成されていること。
Geminiに「TwilioアカウントとVerify Serviceの設定方法」と聞く。
--------------回答(略)----------------------------
Twilioのウェブサイトにアクセスします。
https://www.twilio.com/
①アカウントを作成します。
・名前、メールアドレス、パスワードなどを入力します。
・電話番号の確認(SMSまたは音声通話)が必要になる場合があります。
②Twilioコンソールにログインします。
「Account SID」と「Auth Token」を控えます。
③Verify Serviceの作成
--------------回答(略)----------------------------
-
JARファイル:
WEB-INF/lib
に、twilio-10.0.0.jar
とその依存ライブラリ(Jackson, HttpComponentsなど)がすべて揃っていること。 -
環境変数: Tomcatの
setenv.bat
またはsetenv.sh
ファイルに、以下の3つの環境変数が正確に設定されていること。-
TWILIO_ACCOUNT_SID
-
TWILIO_AUTH_TOKEN
-
TWILIO_VERIFY_SERVICE_SID
-
3. アプリの構成
-
LoginServlet.java
とVerifyServlet.java
: ログイン処理と認証コードの検証を担当するサーブレットです。 -
web.xml
: サーブレットをTomcatに登録するためのデプロイメントディスクリプタファイルです。
web.xml
<web-app>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 新しく追加するVerifyServletの設定 -->
<servlet>
<servlet-name>VerifyServlet</servlet-name>
<servlet-class>VerifyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>VerifyServlet</servlet-name>
<url-pattern>/VerifyServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/LoginServlet</url-pattern>
</servlet-mapping>
<resource-ref>
<description>MySQLデータソース</description>
<res-ref-name>jdbc/MyDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
-
.class
ファイル:javac
コマンドでLoginServlet.java
とVerifyServlet.java
をコンパイルして生成されるファイルです。
javac -encoding UTF-8でコンパイルして生成
javac -encoding UTF-8 -cp "WEB-INF/lib/twilio-10.0.0.jar;WEB-INF/lib/jackson-annotations-2.12.7.jar;WEB-INF/lib/jackson-core-2.12.7.jar;WEB-INF/lib/jackson-databind-2.12.7.jar;WEB-INF/lib/jackson-datatype-jsr310-2.12.7.jar;WEB-INF/lib/java-jwt-3.19.2.jar;WEB-INF/lib/httpclient-4.5.13.jar;WEB-INF/lib/httpcore-4.5.13.jar;WEB-INF/lib/commons-logging-1.2.jar;WEB-INF/lib/commons-codec-1.15.jar;WEB-INF/lib/commons-io-2.7.jar;WEB-INF/lib/log4j-api-2.17.2.jar;WEB-INF/lib/log4j-core-2.17.2.jar;WEB-INF/lib/log4j-slf4j-impl-2.17.2.jar;WEB-INF/lib/slf4j-api-1.7.36.jar;C:\Users\fumin\Desktop\playwright2\apache-tomcat-9.0.105\lib\servlet-api.jar" -d WEB-INF/classes src/*.java
ディレクトリ構成
プロジェクトの標準的なディレクトリ構成は以下のようになります。
LOGINAPP
│ db.sql
│ index.jsp
│ menu.jsp
│ verify.jsp
│ welcome.jsp
│
├───src
│ LoginServlet.java
│ VerifyServlet.java
│
├───web
└───WEB-INF
│ web.xml
│
├───classes
│ LoginServlet.class
│ VerifyServlet.class
│
└───lib
commons-codec-1.5.jar
commons-io-2.7.jar
commons-logging-1.2.jar
httpclient-4.5.13.jar
httpcore-4.4.13.jar
jackson-annotations-2.12.7.jar
jackson-core-2.12.7.jar
jackson-databind-2.12.7.jar
jackson-datatype-jsr310-2.12.7.jar
java-jwt-3.19.2.jar
log4j-api-2.17.2.jar
log4j-core-2.17.2.jar
log4j-slf4j-impl-2.17.2.jar
slf4j-api-1.7.36.jar
twilio-10.0.0.jar
ソースのシーケンス(処理の流れ)
ユーザーがログインして二要素認証を行う際の、各ファイルの処理の流れは以下のようになります。
1.index.jsp
: ユーザー名とパスワードの入力フォーム。フォームのaction
はLoginServlet
に送信
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<title>ログイン</title>
</head>
<body>
<form action="LoginServlet" method="POST">
ユーザー名: <input type="text" name="username"><br>
パスワード: <input type="password" name="password"><br>
<input type="submit" value="ログイン">
</form>
</body>
</html>
2.LoginServlet.java
: ユーザーを認証し、成功すればTwilio Verify APIで認証コードを送信します。その後verify.jsp
にリダイレクトします。
import java.io.IOException;
import java.sql.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import com.twilio.Twilio;
import com.twilio.rest.verify.v2.service.Verification;
public class LoginServlet extends HttpServlet {
// Twilioの資格情報 (環境変数から取得することを推奨)
// Tomcatの環境変数として設定するか、別途設定ファイルから読み込むようにしてください。
// 例: Tomcatのbinディレクトリにあるsetenv.bat/shファイルに設定
// set TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// set TWILIO_AUTH_TOKEN=your_auth_token
// set TWILIO_VERIFY_SERVICE_SID=VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
private static final String TWILIO_ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");
private static final String TWILIO_AUTH_TOKEN = System.getenv("TWILIO_AUTH_TOKEN");
private static final String TWILIO_VERIFY_SERVICE_SID = System.getenv("TWILIO_VERIFY_SERVICE_SID");
@Override
public void init() throws ServletException {
// Servlet初期化時にTwilioクライアントを初期化
// 環境変数が正しく設定されているか確認
if (TWILIO_ACCOUNT_SID == null || TWILIO_AUTH_TOKEN == null || TWILIO_VERIFY_SERVICE_SID == null) {
System.err.println("ERROR: Twilio environment variables are not set. " +
"Please set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_VERIFY_SERVICE_SID.");
throw new ServletException("Twilio environment variables not set.");
}
Twilio.init(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);
System.out.println("Twilio initialized successfully in LoginServlet.");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
// ユーザーの電話番号をデータベースから取得する
String userPhoneNumber = getUserPhoneNumberFromDatabase(username);
if (authenticate(username, password) && userPhoneNumber != null && !userPhoneNumber.isEmpty()) {
HttpSession session = request.getSession();
session.setAttribute("username", username); // ログインユーザー名をセッションに保存
session.setAttribute("phoneNumberFor2FA", userPhoneNumber); // 二要素認証用に電話番号をセッションに保存
// Twilio Verify APIで認証コードを送信
try {
// ★ここを修正しました★
System.out.println("TWILIO_VERIFY_SERVICE_SID: " + TWILIO_VERIFY_SERVICE_SID + " for " + userPhoneNumber);
// creatorメソッドにService SID, 電話番号, チャネル("sms"または"call")の3つの引数を渡します。
Verification verification = Verification.creator(
TWILIO_VERIFY_SERVICE_SID,
userPhoneNumber,
"sms" // ここにチャネル ("sms" または "call") を追加
).create();
System.out.println("Verification SID: " + verification.getSid() + " for " + userPhoneNumber);
// 認証コード入力画面へリダイレクト
response.sendRedirect("verify.jsp");
} catch (Exception e) {
e.printStackTrace();
response.getWriter().println("<h1>二要素認証コードの送信に失敗しました。</h1><p>詳細: " + e.getMessage() + "</p>");
}
} else {
// 認証失敗または電話番号が見つからない場合
response.getWriter().println("<h1>Login ERROR</h1><p>ユーザー名またはパスワードが正しくないか、電話番号が登録されていません。</p>");
}
}
private boolean authenticate(String username, String password) {
// 既存の認証ロジック: データベースの users テーブルでユーザー名とパスワードをチェック
try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/login_db", "root", "rootXX1");
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username=? AND password=?")) {
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
return rs.next(); // レコードが見つかれば認証成功
} catch (SQLException e) {
e.printStackTrace();
System.err.println("データベース認証エラー: " + e.getMessage());
return false;
}
}
// データベースからユーザーの電話番号を取得する
private String getUserPhoneNumberFromDatabase(String username) {
String phoneNumber = null;
try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/login_db", "root", "root001");
PreparedStatement stmt = conn.prepareStatement("SELECT phone_number FROM users WHERE username=?")) {
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
phoneNumber = rs.getString("phone_number");
}
} catch (SQLException e) {
e.printStackTrace();
System.err.println("電話番号取得エラー: " + e.getMessage());
}
return phoneNumber;
}
}
3.verify.jsp
: 認証コードの入力フォーム。フォームのaction
はVerifyServlet
に送信されます。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
// セッションからユーザー名を取得 (表示用)
String username = (String) session.getAttribute("username");
if (username == null) {
// セッションがない場合はログインページへリダイレクト
response.sendRedirect("index.jsp");
return;
}
%>
<html>
<head>
<title>二要素認証</title>
</head>
<body>
<h1><%= username %> さん、認証コードを入力してください</h1>
<p>ご登録の電話番号に認証コードを送信しました。</p>
<form action="VerifyServlet" method="POST">
認証コード: <input type="text" name="verificationCode" required><br>
<input type="submit" value="認証">
</form>
<%
// エラーメッセージがあれば表示
String errorMessage = (String) request.getAttribute("errorMessage");
if (errorMessage != null) {
out.println("<p style='color: red;'>" + errorMessage + "</p>");
}
%>
<br>
<p><a href="index.jsp">ログインページに戻る</a></p>
</body>
</html>
4.VerifyServlet.java
: ユーザーが入力した認証コードをTwilio APIで検証します。成功すればwelcome.jsp
にリダイレクトします。
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import com.twilio.Twilio;
import com.twilio.rest.verify.v2.service.VerificationCheck;
public class VerifyServlet extends HttpServlet {
// Twilioの資格情報 (LoginServletと同様に環境変数から取得)
private static final String TWILIO_ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");
private static final String TWILIO_AUTH_TOKEN = System.getenv("TWILIO_AUTH_TOKEN");
private static final String TWILIO_VERIFY_SERVICE_SID = System.getenv("TWILIO_VERIFY_SERVICE_SID");
@Override
public void init() throws ServletException {
// Servlet初期化時にTwilioクライアントを初期化
if (TWILIO_ACCOUNT_SID == null || TWILIO_AUTH_TOKEN == null || TWILIO_VERIFY_SERVICE_SID == null) {
System.err.println("ERROR: Twilio environment variables are not set. " +
"Please set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_VERIFY_SERVICE_SID.");
throw new ServletException("Twilio environment variables not set.");
}
Twilio.init(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);
System.out.println("Twilio initialized successfully in VerifyServlet.");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(false); // 既存のセッションを取得
// セッションがない、または二要素認証用の電話番号が保存されていない場合はログインページへ
if (session == null || session.getAttribute("phoneNumberFor2FA") == null) {
response.sendRedirect("index.jsp");
return;
}
String userPhoneNumber = (String) session.getAttribute("phoneNumberFor2FA");
String verificationCode = request.getParameter("verificationCode");
if (verificationCode == null || verificationCode.isEmpty()) {
request.setAttribute("errorMessage", "認証コードを入力してください。");
request.getRequestDispatcher("verify.jsp").forward(request, response);
return;
}
// Twilio Verify APIで認証コードを検証
try {
// ★ここを修正しました★
// creatorにはService SIDのみを渡し、createメソッドでverificationCodeとuserPhoneNumberを渡します。
//VerificationCheck verificationCheck = VerificationCheck.creator(
// TWILIO_VERIFY_SERVICE_SID
//).create(verificationCode, userPhoneNumber);
// ★ここを修正しました★
// creator に Service SID を渡し、setCode と setTo をチェーンで呼び出し、最後に create() を実行します。
VerificationCheck verificationCheck = VerificationCheck.creator(TWILIO_VERIFY_SERVICE_SID)
.setCode(verificationCode)
.setTo(userPhoneNumber)
.create();
if (verificationCheck.getStatus().equals("approved")) {
// 認証成功!セッションから二要素認証用の電話番号を削除
// (認証成功後は、このセッション属性は不要になります)
session.removeAttribute("phoneNumberFor2FA");
response.sendRedirect("welcome.jsp"); // 認証成功後のページへ
} else {
// 認証失敗
request.setAttribute("errorMessage", "認証コードが正しくないか、期限切れです。");
request.getRequestDispatcher("verify.jsp").forward(request, response);
}
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("errorMessage", "認証の検証中にエラーが発生しました。詳細: " + e.getMessage());
request.getRequestDispatcher("verify.jsp").forward(request, response);
}
}
}
4. これまで苦労した事項と解決策
ミスした事項 | 原因と解決策 |
---|---|
コンパイルエラー |
javac コマンドのクラスパスに指定されたJARファイルのバージョンと、ソースコードのimport 文やメソッドが一致していなかった→ import 文をtwilio-10.0.0.jar に合わせて修正し、javac コマンドのクラスパスを正しく設定することで解決。
|
NoClassDefFoundError |
WARファイル作成時に必要なJARファイルがWEB-INF/lib に含まれていなかった。→ javac で指定したすべてのJARファイルをWEB-INF/lib に配置し、jar -cvf コマンドをプロジェクトルートで実行することで解決。
|
The requested resource was not found |
TWILIO_VERIFY_SERVICE_SID 、TWILIO_ACCOUNT_SID 、TWILIO_AUTH_TOKEN のいずれかが間違っているか、環境変数が正しくTomcatに読み込まれていなかった。→ TwilioコンソールでSIDとトークンを再確認し、setenv ファイルを修正後、Tomcatを再起動することで解決。
|
5. 結果
※twilioからショートメールで認証コードがきます。(上記Verify Serviceの作成参照)
これらの課題を一つずつGeminiに問い合わせし解決した結果、無事に
LoginServlet
とVerifyServlet
のコンパイルとデプロイに成功し、Twilio Verify APIを介した二要素認証のコード送信機能が実装できました。
このドキュメントが今後の開発作業の一助となれば幸いです。
6. 補足:会話した一例
ここでは、今回のTwilio Verify API導入作業に関するKaopiさんとGeminiとの会話の例をいくつかご紹介します。
-
Kaopiさん: 「Twilioの二要素認証ってどうやればいいの?」
-
Gemini: 「Twilio Verify APIを使います。まず、アカウントとサービスを作成して、
Account SID
、Auth Token
、Service SID
を取得しましょう。」 -
Kaopiさん: 「WARファイルを作成したら
NoClassDefFoundError
が発生するんだけど、どうして?」 -
Gemini: 「それはJARファイルの配置が原因の可能性があります。
WEB-INF/lib
に必要なすべてのJARファイルが揃っているか確認してください。」 -
Kaopiさん: 「Tomcatにデプロイしても動かない。
LoginServlet
のinit()
メソッドでエラーが出てる。」 -
Gemini: 「おそらく、Twilioの環境変数が正しく設定されていません。Tomcatの
setenv.sh
やsetenv.bat
を再確認して、サーバーを再起動してみましょう。」
7. 考慮点:Twilioのような外部サービスとHTTPS通信を行う際について
Twilioのような外部サービスとHTTPS通信を行う際、通常は特別な設定は不要ですが、場合によってはJavaの**keytool
**コマンドを使ってTomcatの信頼ストアに証明書をインポートする必要があります。
信頼ストアの確認と証明書のインポート
-
Tomcatの信頼ストア確認:
Tomcatがどの信頼ストアを使用しているか確認します。通常はJavaのデフォルトの信頼ストア ($JAVA_HOME/jre/lib/security/cacerts
) です。 -
Twilioの証明書のダウンロード:
ブラウザを使ってTwilioのAPIエンドポイント(https://api.twilio.com
)にアクセスし、証明書をエクスポートします。 -
keytool
での証明書インポート:
エクスポートした証明書を、keytool
コマンドを使ってJavaの信頼ストアにインポートします。これにより、TomcatがTwilioの証明書を信頼できるようになります。keytool -import -trustcacerts -alias twilio -file twilio.crt -keystore /path/to/your/cacerts
注意: デフォルトの
cacerts
ファイルはパスワード(通常changeit
)が必要です。
この手順は、HTTPS接続に関する問題が発生した場合のトラブルシューティングとして役立ちます。
◆雑談:達成する為の気持ちが大事
・諦めない心
・全集中:水の呼吸担当⇒くじけたら・・・ダダダンをみる
・ソース公開は検討中