第 13 章.Tomcat を 用 いたユーザ 認 証 学 習 のねらい 1 データベースに 登 録 されたユーザのみにアクセスを 許 可 するユーザ 認 証 の 仕 組 みを Tomcat の 機 能 を 用 いて 学 習 する < 前 回 の 復 習 > 講 義 で 示 された 基 礎 課 題 13-1 に 解 答 して 下 さい 13-1.はじめに 本 学 の 情 報 ポータルなど 個 人 情 報 を 管 理 しているページは セキュリティのため 本 人 以 外 がアクセスできないように ユーザ ID とパスワードでアクセスを 管 理 しています こ の 仕 組 みをユーザ 認 証 と 言 います 本 章 では 以 下 の 13-2~13-4 節 の 説 明 にしたがって Tomcat が 用 意 している 機 能 を 用 いてユーザ 認 証 機 能 がついたページを 作 成 しましょう 13-2. 閲 覧 ページ(サーブレット)の 作 成 まず 基 になる 閲 覧 ページを 作 成 します 作 成 するのは 次 のようなページです Page1Servlet.java というサーブレットに 接 続 すると 次 のように 表 示 される Page2Servlet.java というサーブレットに 接 続 すると 次 のように 表 示 される そして 両 者 はリンクで 結 びついている ユーザ 認 証 は 後 に 回 して この 2 つのサーブレットを 次 のように 作 成 してください 185
1 Tomcat プロジェクトを Auth という 名 前 で 新 規 作 成 します その 中 にパッケージ 名 auth クラス 名 Page1Servlet.java というクラスを 作 成 してください 続 いて 同 じパッケージ 内 に Page2Servlet.java というクラスを 作 成 し ます 2 Page1Servlet.java を 次 のように 記 述 してください この 時 点 では 新 しい 内 容 は 含 まれていないので 理 解 に 問 題 はないと 思 います package auth; <Page1Servlet.java> import java.io.ioexception; import java.io.printwriter; import javax.servlet.servletexception; import javax.servlet.http.httpservlet; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; public class Page1Servlet extends HttpServlet{ public void doget(httpservletrequest request, HttpServletResponse response) throws ServletException, IOException { request.setcharacterencoding("windows-31j"); response.setcontenttype("text/html;charset=windows-31j"); PrintWriter out=response.getwriter(); out.println("<html>"); out.println("<head>"); out.println("<title>ユーザー 認 証 のテスト</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>ようこそページ 1 へ</p>"); out.println("<p><a href= "/Auth/Page2Servlet "> ページ 2 へ</a></p>"); out.println("</body>"); を で 囲 まれた 文 字 列 中 に 指 out.println("</html>"); 定 する 場 合 は と 指 定 します 3 同 様 に Page2Servlet.java を 記 述 します 次 ページの 下 線 部 のようにページ 番 号 1 と2が 入 れ 替 わるだけです 186
package auth; import java.io.ioexception; import java.io.printwriter; import javax.servlet.servletexception; import javax.servlet.http.httpservlet; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; <Page2Servlet.java> public class Page2Servlet extends HttpServlet{ public void doget(httpservletrequest request, HttpServletResponse response) throws ServletException, IOException { request.setcharacterencoding("windows-31j"); response.setcontenttype("text/html;charset=windows-31j"); PrintWriter out=response.getwriter(); out.println("<html>"); out.println("<head>"); out.println("<title>ユーザー 認 証 のテスト</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>ようこそページ 2 へ</p>"); out.println("<p><a href= "/Auth/Page1Servlet "> ページ 1 へ</a></p>"); out.println("</body>"); out.println("</html>"); 4 上 の2つのサーブレットの URL を 登 録 するため web.xml を 次 ページのように 記 述 し ます WEB-INF 内 に 新 規 作 成 187
<web-app> <web.xml> <servlet> <servlet-name>page1servlet</servlet-name> <servlet-class>auth.page1servlet</servlet-class> </servlet> <servlet> <servlet-name>page2servlet</servlet-name> <servlet-class>auth.page2servlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>page1servlet</servlet-name> <url-pattern>/page1servlet</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>page2servlet</servlet-name> <url-pattern>/page2servlet</url-pattern> </servlet-mapping> </web-app> 応 用 課 題 13-A 作 成 したら 動 作 を 確 認 してください その 上 で 閲 覧 対 象 となる 二 つのページを 作 成 し ました き 記 述 して 提 出 してください 13-3.ユーザ 情 報 データベースの 作 成 次 に ユーザ 認 証 を 行 う 際 に 参 照 する ユーザ 名 とパスワードを 保 管 したデータベース を 作 成 します 作 成 するテーブルは 次 の user_table と role_table の2つです <user_table> user pass Name user:ユーザ 名 S_Kaneda pass1 金 田 正 太 郎 pass:パスワード N_Date pass2 伊 達 直 人 Name:ユーザ 氏 名 <role_table> user role user:ユーザ 名 S_Kaneda user1 role:ユーザの 承 認 レベル 例 えば 一 般 ユーザや 管 理 N_Date user1 者 などを 区 別 する 際 に 用 いる ここでは 同 一 のロールにしてある(role 名 は 任 意 ) 188
上 の 二 つのテーブルを 次 の 手 順 にしたがって 作 成 してください 1 MySQL を 起 動 し 下 のようにデータベース auth を 作 成 します( 認 証 は 英 語 で authentication というので そこから 命 名 ) 下 線 部 がコマンド 入 力 部 分 2 操 作 対 象 を auth にします 3 次 のように user_table を 作 成 し 2 名 分 のデータを 登 録 します user_table の 作 成 データの 追 加 1 データの 追 加 2 4 上 の 作 成 後 テーブルの 全 データを 取 り 出 すと 次 のように 表 示 されるはずです 5 続 いて 同 じ 要 領 で テーブル role_table を 作 成 し 2 名 分 のデータを 登 録 します role_table の 作 成 189
データの 追 加 6 上 の 作 成 後 テーブルの 全 データを 取 り 出 すと 次 のように 表 示 されるはずです 応 用 課 題 13-B 上 の4と6の 表 示 画 面 を 確 認 したら データベース auth の 中 に user_table と role_table を 作 成 しました と 記 述 して 提 出 してください 13-4.レルム(Realm)の 指 定 -JDBC レルムの 指 定 本 節 ではレルム(Realm)を 指 定 します レルムとは 聞 き 慣 れない 用 語 だと 思 いますが ユーザ 認 証 を 行 う 際 に 必 要 となるユーザ ID やパスワードの 管 理 の 仕 方 を 指 します Tomcat ではデフォルトで UserDatabase レルムというレルムが 設 定 されています これは ユーザ ID パスワード ロールを tomcat-users.xml というファイルに 記 述 し それを 参 照 す ることで 認 証 を 行 う 方 式 です これは 簡 便 で 便 利 な 反 面 ユーザ 数 が 増 えた 場 合 には 実 用 的 ではありません そこで ここでは ユーザ ID パスワード ロールをデータベース に 格 納 し JDBC 経 由 で 認 証 を 行 う JDBC レルムを 用 いることにします つまり 前 節 で 作 成 したデータベースを 認 証 に 用 いる 訳 です 以 下 の 手 順 にしたがって JDBC レルムを 用 いる ための 設 定 を 行 ってください 1 Tomcat のルートディレクトリ(C:\Program Files\Apache Software Foundation\Tomcat 8.0)にある conf というフォルダを 開 いてください この 中 に server.xml があ ります これを 編 集 するので 秀 丸 エディタなど 適 当 なエディタを 使 って 開 いてくだ さい 2 130 行 目 付 近 にある <Realm classname= という 部 分 は UserDatabase レル ムの 設 定 を 行 っているところです 今 は これを 使 わず JDBC レルムを 使 用 するので 190
この 部 分 を 下 のようにコメントにしてください そして その 下 に 点 線 枠 で 示 した JDBC レルム 設 定 部 分 を 新 たに 記 述 します <server.xml> これをつけてコメントにする <!-- <Realm classname="org.apache.catalina.realm.userdatabaserealm" resourcename="userdatabase"/> --> JDBC レルム 設 定 部 分 <Realm classname="org.apache.catalina.realm.jdbcrealm" drivername="com.mysql.jdbc.driver" MySQL のパスワード connectionurl="jdbc:mysql://localhost/auth" を 各 自 記 述 すること connectionname="root" connectionpassword="*******" usertable="user_table" usernamecol="user" usercredcol="pass" userroletable="role_table" rolenamecol="role" /> 解 説 JDBC レルムタグでは 次 の 要 素 を 指 定 しています 1. classname:このレルムを 動 作 させるために Tomcat が 用 意 しているクラス 名 2. drivername:jdbc ドライバ 名 MySQL の 場 合 は"com.mysql.jdbc.Driver" 3. connectionurl: 接 続 するデータベースの URL 最 後 の auth がデータベース 名 4. connectionname connectionpassword: 当 該 データベースに 接 続 できるユーザ 名 とパスワード ここではユーザ 名 として root を 用 いる ただし パスワードは 各 自 のものを 指 定 すること 5. usertable:ユーザ 名 とパスワードが 格 納 されているテーブル 名 6. usernamecol: 当 該 テーブルに 於 いて ユーザ 名 が 入 っているカラム 名 ( 列 名 ) 7. usercredcol: 当 該 テーブルに 於 いて パスワードが 入 っているカラム 名 ( 列 名 ) 8. userroletable:ユーザ 名 とロール 名 が 格 納 されているテーブル 名 9. rolenamecol: 当 該 テーブルに 於 いて ロール 名 が 入 っているカラム 名 ( 列 名 ) 3 JDBC レルムを 使 用 する 場 合 使 用 するデータベースの JDBC ドライバを 所 定 の 場 所 にコピーしておく 必 要 があります Tomcat のルートディレクトリ(C:\Program Files\Apache Software Foundation\Tomcat 8.0)にある lib というフォルダ 内 に MySQL の JDBC ドライバ mysql-connector-java-5.1.37-bin.jar をコピーしてくださ い JDBC ドライバは 10-3 節 (p.149)で 確 認 したはずです 191
応 用 課 題 13-C 上 の 処 理 を 行 ったら server.xml に JDBC レルムを 使 用 する 設 定 を 行 いました と 記 述 して 提 出 してください 13-5. 認 証 方 法 の 指 定 -FORM 認 証 の 指 定 JavaEE では 次 の 4 種 類 の 認 証 方 法 が 用 意 されています 1. BASIC 認 証 :HTTP の BASIC 認 証 を 使 ってユーザ 名 とパスワードを 照 合 する 方 式 パスワードもそのままサーバに 送 信 される ログインは HTTP で 用 意 されているダイ アログボックスを 用 いる 2. DIGEST 認 証 : 上 と 同 様 だが パスワードを 簡 易 的 に 暗 号 化 してサーバに 送 信 する 点 が 異 なる 3. FORM 認 証 :BASIC 認 証 との 違 いは ユーザ 名 やパスワード 入 力 時 に HTTP で 用 意 されているダイアログボックスではなく 独 自 のログイン 画 面 を 用 いる 点 4. CLIENT-CERT 認 証 :クライアント 証 明 書 を 使 ってユーザ 認 証 を 行 う 最 も 安 全 な 認 証 方 式 この 方 式 では SSL (Secure Sockets Layer) プロトコルを 使 用 して 認 証 を 行 う 必 要 がある ここでは 標 準 的 に 用 いられている FORM 認 証 を 用 います 次 の 手 順 にしたがって FORM 認 証 の 設 定 を 行 ってください 1 web.xml に 次 ページのように<security-constraint> <login-config>そして <security-role>の3つのタグを 追 加 してください 2 続 いて ログイン 時 に 表 示 されるページ Login.html を 下 のように Auth プロジェ クトのルートディレクトリに 作 成 し p.194 の 様 に 記 述 してください 3 最 後 に ログイン 失 敗 時 に 表 示 されるページ Error.html を Auth プロジェクト のルートディレクトリ 内 に 作 成 し p.194 の 様 に 記 述 してください 192
<web-app> <web.xml> <security-constraint> <web-resource-collection> <web-resource-name>form Auth</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>user1</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>form</auth-method> <form-login-config> <form-login-page>/login.html</form-login-page> <form-error-page>/error.html</form-error-page> </form-login-config> </login-config> アクセス 制 限 をする Web リソースに 名 前 をつける( 名 前 は 任 意 ) セキュリティの 対 象 となる URL:ここで は Web アプリケーション 内 の 全 ページ アクセスを 許 可 するロール 名 認 証 方 法 を FORM 認 証 に 指 定 ログイン 用 ページの 指 定 ログイン 失 敗 時 のページの 指 定 <security-role> <role-name>user1</role-name> </security-role> ( 使 用 する)ロール 名 の 定 義 </web-app> 193
<html lang="ja"> <head> <title>ログイン 画 面 </title> </head> <body> <Login.html> <h1>ユーザ 認 証 画 面 </h1> <p>ユーザ 名 とパスワードを 入 力 して[ 送 信 ]ボタンをクリックしてください </p> <form method="post" action="j_security_check" name="loginform"> ユーザ 名 <input type="text" name="j_username" ><br> パスワード <input type="password" name="j_password" ><br> <input type="submit" value=" 送 信 "> <input type="reset" value="リセット"> </form> アクション 名 :j_security_check </body> </html> ユーザ 名 :j_username パスワード:j_password の 各 名 称 は 固 定 されている <html lang="ja"> <head> <title>ログインエラー 画 面 </title> <Error.html> </head> <body> <h1>ログインエラー</h1> <p>ログインに 失 敗 しました ユーザ 名 あるいはパスワードが 正 しくありません </p> </body> </html> 応 用 課 題 13-D 作 成 したら 次 の 動 作 を 確 認 してください 1 http://localhost:8080/auth/page1servlet に 接 続 してください すると 次 ページ のログイン 画 面 が 現 れます ここで 13-3 節 で 作 成 したデータベースに 登 録 されてい るユーザ 名 とパスワードを 入 力 してください 194
2 すると 下 のようにページ 1 にアクセスできます 3 上 の 状 態 で ページ 2 へ をクリックすると ページ 2 へジャンプします このよう に いったんユーザ 認 証 をクリアすると 当 該 Web アプリケーション 内 のページに( 認 証 なしで) 移 動 することができます 4 一 方 1のログイン 画 面 で 不 正 なユーザ 名 あるいはパスワードを 入 力 すると 次 のよ うにログインエラー 画 面 に 移 動 します 上 の 動 作 を 確 認 したら ユーザ 認 証 機 能 が 動 作 することを 確 認 しました と 記 述 して 提 出 してください 195
13-6.ユーザ 認 証 の 改 良 応 用 課 題 13-E -ログアウト 処 理 の 追 加 FORM 認 証 を 用 いた 場 合 いったん 認 証 が 通 ると セッションが 有 効 な 間 (つまりブラ ウザを 閉 じるまで)はログインした 状 態 が 続 きます そこで 任 意 のタイミングでログイ ン 状 態 を 終 了 できるよう ログアウトの 処 理 を 付 加 してみましょう 次 の 手 順 にしたがっ て ログアウトの 処 理 を 加 えてください 1 ログアウト 時 に 表 示 されるページ Logut.jsp を Auth プロジェクトのルートディレクトリに 作 成 し 下 のように 記 述 してください 下 線 部 の invalidate()メソッドはセッション を 無 効 にするメソッドです つまり この 命 令 で セッションは 終 了 し したがってログイン 状 態 も 終 了 することになります <Logout.jsp> <%@ page contenttype="text/html; charset=windows-31j" %> <html> <body> ログアウトしました <% session.invalidate(); %> </body> </html> 2 次 に Page1Servlet.java および Page2Servlet.java を 次 ページのように 修 正 し てください 下 線 部 が 追 加 部 分 です 追 加 したのは 上 の Logout.jsp へのリンクを つけた 部 分 です 196
package auth; <Page1Servlet.java> public class Page1Servlet extends HttpServlet{ public void doget(httpservletrequest request, HttpServletResponse response) throws ServletException, IOException { request.setcharacterencoding("windows-31j"); response.setcontenttype("text/html;charset=windows-31j"); out.println("<p><a href= "/Auth/Page2Servlet "> ページ 2 へ</a></p>"); out.println("<p><a href= "/Auth/Logout.jsp "> ログアウト</a></p>"); out.println("</body>"); は 使 用 フォントによっては と 表 示 されます out.println("</html>"); package auth; <Page2Servlet.java> public class Page2Servlet extends HttpServlet{ public void doget(httpservletrequest request, HttpServletResponse response) throws ServletException, IOException { request.setcharacterencoding("windows-31j"); response.setcontenttype("text/html;charset=windows-31j"); out.println("<p><a href= "/Auth/Page1Servlet "> ページ 1 へ</a></p>"); out.println("<p><a href= "/Auth/Logout.jsp "> ログアウト</a></p>"); out.println("</body>"); out.println("</html>"); 197
作 成 したら 次 のように 動 作 を 確 認 してください 1 Page1Servlet へ 接 続 してください 認 証 を 経 た 後 今 度 は 次 のように ログアウト ページのリンクが 表 示 されます 2 このログアウトへのリンクをクリックすると 次 のように Lgout.jsp へジャンプし ます つまりログアウトします 3 再 び Page1Servlet に 接 続 しようとすると ログイン 画 面 が 現 れ ユーザ 認 証 が 必 要 なことを 確 認 してください 上 の 動 作 を 確 認 したら ログアウト 処 理 を 確 認 しました と 記 述 して 提 出 してください 応 用 課 題 13-F - 認 証 情 報 の 取 得 例 えばユーザ 認 証 画 面 で ユーザ 名 として S_Kaneda を 入 力 した 場 合 接 続 先 の Page1Servlet で 次 のようにユーザ 名 が 表 示 されるように 改 良 してみましょう 次 ページの 様 に Page1Servlet.java および Page2Servlet.java を 修 正 してください 下 線 部 が 修 正 箇 所 です 198
package auth; <Page1Servlet.java> public class Page1Servlet extends HttpServlet{ public void doget(httpservletrequest request, HttpServletResponse response) throws ServletException, IOException { request.setcharacterencoding("windows-31j"); response.setcontenttype("text/html;charset=windows-31j"); PrintWriter out=response.getwriter(); String userid=request.getremoteuser(); getremoteuser()メソッドでユーザ 名 を 取 out.println("<html>"); 得 できる out.println("<head>"); out.println("<title>ユーザー 認 証 のテスト</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>ようこそ"+userid+"さん ページ 1 へ</p>"); package auth; <Page2Servlet.java> public class Page2Servlet extends HttpServlet{ public void doget(httpservletrequest request, HttpServletResponse response) throws ServletException, IOException { request.setcharacterencoding("windows-31j"); response.setcontenttype("text/html;charset=windows-31j"); PrintWriter out=response.getwriter(); String userid=request.getremoteuser(); out.println("<html>"); out.println("<head>"); out.println("<title>ユーザー 認 証 のテスト</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>ようこそ"+userid+"さん ページ 2 へ</p>"); 199
作 成 したら Page1Servlet に 接 続 し ログイン 画 面 で 入 力 したユーザ 名 が p.198 のように 表 示 されることを 確 認 してください 確 認 したら request.getremoteuser()によっ てログイン 画 面 で 入 力 したユーザ 名 を 取 得 できることを 確 認 しました と 記 述 して 提 出 し てください 応 用 課 題 13-G - 認 証 情 報 の 取 得 ( 改 良 ) 応 用 課 題 13-F を 改 良 して 今 度 は 認 証 後 に 入 力 したユーザ 名 の 氏 名 が 表 示 されるよ うに 改 良 してみましょう どのようにすれば 良 いか 分 かるでしょうか? 13-3 節 で 作 成 したテーブル user_table には Name というカラム( 列 )があり そこ にユーザの 氏 名 が 入 っていました そこで 認 証 後 にこのテーブルを 検 索 して 該 当 するユ ーザ 名 のレコード( 行 )の Name カラムの 値 を 取 り 出 せば 良 いことが 分 かります これを プログラミングしましょう 次 の 手 順 にしたがって 作 成 してください 1 サーブレットからデータベースへ 接 続 するので 11-2 節 で 作 成 したのと 同 様 な DBManager.java を 作 成 し 次 ページのように 記 述 します(DBManager.java をコ ピーして 修 正 しても 結 構 です) 2 次 に p.202 のように Page1Servlet.java を 修 正 します プログラムの 形 式 は 12-1 節 と 同 様 です また Page2Servlet.java についても 同 様 に 修 正 して 下 さい 200
package auth; <DBManager.java> import java.sql.connection; import java.sql.drivermanager; public class DBManager { public static Connection getconnection() { try{ Class.forName("com.mysql.jdbc.Driver"); Connection con=drivermanager.getconnection( "jdbc:mysql://localhost/auth","root","*******"); return con; catch (Exception e) { ユーザ 名 パスワード throw new IllegalStateException(e); MySQL に 接 続 する 際 のユーザ 名 とパスワ ードは 各 自 の 値 を 用 いる 事 作 成 後 動 作 を 確 認 して 下 さい 2 名 のユーザ( 金 田 正 太 郎 伊 達 直 人 )について p.200 のようにユーザ 名 に 対 応 する 氏 名 が 表 示 されることを 確 認 したら user_table からユーザ 名 に 対 応 する 氏 名 を 取 り 出 しそれを 表 示 する 事 ができました と 記 述 して 提 出 してくださ い 201
package auth; import java.sql.connection; import java.sql.resultset; import java.sql.sqlexception; import java.sql.statement; <Page1Servlet.java> public class Page1Servlet extends HttpServlet{ public void doget(httpservletrequest request, HttpServletResponse response) throws ServletException, IOException { request.setcharacterencoding("windows-31j"); response.setcontenttype("text/html;charset=windows-31j"); PrintWriter out=response.getwriter(); String userid=request.getremoteuser(); String sql="select * from user_table where user='"+userid+"'"; Connection con=null; Statement smt=null; try{ con=dbmanager.getconnection(); smt=con.createstatement(); ResultSet rs=smt.executequery(sql); rs.next(); String Name=rs.getString("Name"); out.println("<html>"); out.println("<head>"); out.println("<title>ユーザー 認 証 のテスト</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>ようこそ"+name+"さん ページ 1 へ</p>"); out.println("</html>"); catch(sqlexception e) { throw new ServletException(e); finally { if(smt!=null) {try{smt.close(); catch(sqlexception ignore) { if(con!=null) {try{con.close(); catch(sqlexception ignore) { 202