1
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?

Geminiと会話してTwilio二要素認証の導入してみた

Last updated at Posted at 2025-08-09

Twilio二要素認証の導入ノウハウ

AI(Gemini)と会話しながら成功に至るまでのノウハウと注意点を以下にまとめます。


1. Twilioの2要素認証利用方法

Twilio Verify APIを使用した二要素認証の基本的な流れは以下の通りです。

  1. ユーザー認証: ユーザーがIDとパスワードでログインを試みます。

  2. 電話番号の取得: ログイン成功後、データベースからそのユーザーに紐づく電話番号を取得します。

  3. 認証コードの送信: 取得した電話番号と、Twilio Verifyで作成したService SIDを使用して、認証コードをSMSで送信します。この処理はLoginServlet.javaVerification.creator()メソッドで行われます。

  4. コードの検証: ユーザーが認証コードを入力し、Verification.checker()メソッドでそのコードが正しいか検証します。

  5. ログイン完了: 検証が成功すれば、ログインが完了します。


2. 環境と前提条件

このアプリケーションを正しく動作させるための環境と前提条件は以下の通りです。

  • Java環境: Java JDKがインストールされていること。
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がインストールされていること。
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
> 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の作成
q.tw.JPG
--------------回答(略)----------------------------

  • 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.javaVerifyServlet.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.javaVerifyServlet.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.war: 上記のファイル群をまとめたWebアプリケーションアーカイブファイルです。Tomcatのwebappsディレクトリに配置してデプロイします。
    tomct.JPG

ディレクトリ構成

プロジェクトの標準的なディレクトリ構成は以下のようになります。

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: ユーザー名とパスワードの入力フォーム。フォームのactionLoginServletに送信

index.jsp
<%@ 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にリダイレクトします。

LoginServlet.java(Twilio 2FA対応版)
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: 認証コードの入力フォーム。フォームのactionVerifyServletに送信されます。

verify.jsp
<%@ 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にリダイレクトします。

VerifyServlet.java (Twilio 2FA検証版)
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_SIDTWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKENのいずれかが間違っているか、環境変数が正しくTomcatに読み込まれていなかった。→ TwilioコンソールでSIDとトークンを再確認し、setenvファイルを修正後、Tomcatを再起動することで解決。

5. 結果

q1.tw.JPG
q2.tw.JPG
※twilioからショートメールで認証コードがきます。(上記Verify Serviceの作成参照)
q3.tw.JPG
q4.tw.JPG

これらの課題を一つずつGeminiに問い合わせし解決した結果、無事にLoginServletVerifyServletのコンパイルとデプロイに成功し、Twilio Verify APIを介した二要素認証のコード送信機能が実装できました。

このドキュメントが今後の開発作業の一助となれば幸いです。

6. 補足:会話した一例

ここでは、今回のTwilio Verify API導入作業に関するKaopiさんとGeminiとの会話の例をいくつかご紹介します。

  • Kaopiさん: 「Twilioの二要素認証ってどうやればいいの?」

  • Gemini: 「Twilio Verify APIを使います。まず、アカウントとサービスを作成して、Account SIDAuth TokenService SIDを取得しましょう。」

  • Kaopiさん: 「WARファイルを作成したらNoClassDefFoundErrorが発生するんだけど、どうして?」

  • Gemini: 「それはJARファイルの配置が原因の可能性があります。WEB-INF/libに必要なすべてのJARファイルが揃っているか確認してください。」

  • Kaopiさん: 「Tomcatにデプロイしても動かない。LoginServletinit()メソッドでエラーが出てる。」

  • Gemini: 「おそらく、Twilioの環境変数が正しく設定されていません。Tomcatのsetenv.shsetenv.batを再確認して、サーバーを再起動してみましょう。」


7. 考慮点:Twilioのような外部サービスとHTTPS通信を行う際について

Twilioのような外部サービスとHTTPS通信を行う際、通常は特別な設定は不要ですが、場合によってはJavaの**keytool**コマンドを使ってTomcatの信頼ストアに証明書をインポートする必要があります。

信頼ストアの確認と証明書のインポート

  1. Tomcatの信頼ストア確認:
    Tomcatがどの信頼ストアを使用しているか確認します。通常はJavaのデフォルトの信頼ストア ($JAVA_HOME/jre/lib/security/cacerts) です。

  2. Twilioの証明書のダウンロード:
    ブラウザを使ってTwilioのAPIエンドポイント(https://api.twilio.com)にアクセスし、証明書をエクスポートします。

  3. keytoolでの証明書インポート:
    エクスポートした証明書を、keytoolコマンドを使ってJavaの信頼ストアにインポートします。これにより、TomcatがTwilioの証明書を信頼できるようになります。

    keytool -import -trustcacerts -alias twilio -file twilio.crt -keystore /path/to/your/cacerts
    

    注意: デフォルトのcacertsファイルはパスワード(通常changeit)が必要です。

この手順は、HTTPS接続に関する問題が発生した場合のトラブルシューティングとして役立ちます。


◆雑談:達成する為の気持ちが大事
・諦めない心
・全集中:水の呼吸担当⇒くじけたら・・・ダダダンをみる
・ソース公開は検討中


1
0
0

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
1
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?