第 13 回 サーブレット、JSP

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

13-1. 同期

アクセスカウンタを考えます。 アクセスカウンタは値を保持しており、その値を読み込んだ後、1を加算し再 び値を記憶します。

カウンタ処理

  1. カウンタ値の読み込み(→0)
  2. カウンタ値の加算(0+1)
  3. カウンタ値の書き込み(←1)

これに対して同一のページを複数のブラウザが同時にアクセスすることがあり ます。 ブラウザ A, B がカウンタのページをほぼ同時にアクセスすることを考えます。 すると、次のような処理が発生することがあります。

理想的なアクセス

サーバのカウンタ値ブラウザAブラウザB
0カウンタ値の読み込み(→0)
0 カウンタ値の加算(0+1)
1 カウンタ値の書き込み(←1)
1 カウンタ値の読み込み(→1)
1 カウンタ値の加算(1+1)
2 カウンタ値の書き込み(←2)

二つのブラウザが同時にアクセスしたとき

サーバのカウンタ値ブラウザAブラウザB
0カウンタ値の読み込み(→0)
0 カウンタ値の読み込み(→0)
0 カウンタ値の加算(0+1)
0 カウンタ値の加算(0+1)
1 カウンタ値の書き込み(←1)
1 カウンタ値の書き込み(←1)

これを制御するには、複数のプロセスが同時にカウンタをアクセスしないよう にする必要があります。 単一のプロセスだけが操作するように制御を行うことを排他制御 と呼びます。 排他制御のために用いられる仕組みにセマフォがあります。 これは資源管理関数 P, V を用意します。 P が成功したら、処理をし、 V を実行して処理を終了するものです。 初期状態では P は一つのプロセスしか実行できず、また、 P を実行したプロ セスが V を実行したら、再び一つのプロセスだけが P を実行できるようにな ります。 これを導入すると上記のカウンターの処理は次のようになります。

セマフォを使ったカウンタ処理

  1. P 操作が成功するまで間隔を空けて繰り返す
  2. カウンタ値の読み込み(→0)
  3. カウンタ値の加算(0+1)
  4. カウンタ値の書き込み(←1)
  5. V 操作

二つのブラウザが同時にアクセスしたとき

サーバのカウンタ値ブラウザAブラウザB
0 P操作成功
0 P操作失敗
0カウンタ値の読み込み(→0)
0 P操作失敗
0 カウンタ値の加算(0+1)
0 P操作失敗
1 カウンタ値の書き込み(←1)
1 P操作失敗
1 V操作
1 P操作成功
1 カウンタ値の読み込み(→1)
1 カウンタ値の加算(1+1)
2 カウンタ値の書き込み(←2)
2 V操作

このように、排他制御を行うアルゴリズムはありますが、排他制御はカウンター のためだけではなく、データベースの更新など様々な場面で必要になります。 そのため、プログラミング言語や OS などでこのセマフォアの仕組みは導入さ れています。 Java では排他制御が必要な時に synchronized を使用します。 これは引数に何らかのオブジェクトか配列を取り、続いてブロックを書きます。 そして、オブジェクトがロックされてないときに、ロックしてからブロックを 実行して、実行を終えてからロックを解除します。

例13-1

上記の概念的なカウンタ処理を Java 風に書くと次のようになります。


private Object semaphore = new Object(); 
private int counter = 0;
public void increment(){
    synchronized(semaphore){ // P 操作
       counter++;
    } // V 操作
}

しかし、 Java ではさらに便利な表現として、メソッドの修飾子として synchronized を使用できます。

例13-2

上記の概念的なカウンタ処理を Java 風に書くと次のようになります。


private int counter = 0;
public synchronized void increment(){
       counter++;
}

なお、あらゆるメソッドで上記のような synchronized を使用すると多重書き 込みを避けることはできますが、あらゆる場面でロックがかかりプログラムの 効率が落ちます。 そのため、どの場面で排他制御が必要か吟味する必要があります。 また、複数の資源の排他制御に関しては、二つのプロセスが双方で資源を取り 合って、互いに相手の資源の開放を永遠に待ってしまう デッドロック という重大なバグが発生する場合があります。 今回はデッドロックに関してまでは説明はしませんが、様々な定石が開発され てますので、もしデッドロックの問題を抱えたら、デッドロックをキーワード に文献等を調査して下さい。

例13-3

Thread を使用して、 synchronized の効用を確かめます。 UnsyncCounter は synchronized なし、 SyncCounter は synchronized あり でカウンター変数 counter にアクセスします。 Tester は max1 個並列に動作し、max2 回 increase を行います。 その結果、 SyncCounter の出力は常に max1 × max2 になりますが、UnsyncCounter はそ れより少ない値になってしまいます。


interface Counter {
	void increase();
}
abstract class AbstractCounter implements Counter{
	protected int counter=0;
	@Override
	public String toString(){
		return String.valueOf(counter);
	}
}
class UnsyncCounter extends AbstractCounter {
	@Override
	public void increase(){
		counter++;
	}
}
class SyncCounter extends AbstractCounter {
	@Override
	public synchronized void increase(){
		counter++;
	}
}
class Tester extends Thread {
	private Counter[] counters;
	private int max;
	public Tester(Counter[] counters, int max){
		this.counters = counters;
		this.max = max;
	}
	public void run(){
		for(int i=0; i<max; i++){
			for(Counter c: counters){
				c.increase();
			}
		}
	}
}
class Main {
	private static final int max1 = 1000;
	private static final int max2 = 1000;
	public static void main(String[] arg){
		Counter[] counters = new Counter[]{
				new UnsyncCounter(),
				new SyncCounter()
				};
		Tester[] testers = new Tester[max1];
		for(int i=0; i<testers.length; i++){
			testers[i]= new Tester(counters,max2);
			testers[i].start();
		}
		for(Thread t : testers){
			try {
				t.join();
			} catch (InterruptedException e) {}
		}
		for(Counter c : counters){
			System.out.println(c);
		}
	}

}
出力例
999891
1000000

(実行毎に異なり、またコンピュータの構成によっても変化の差は異なる)

13-2. サーブレット

Web アプリケーションと CGI

Web のサービスは単なる静的なデータの提供に止まりません。 動的なデータサービスを行うため、ブラウザからのデータ入力に対して、サー バ側のアプリケーションプログラムが情報処理を行い、結果を表示する仕組み が必要です。 そのため、古くは CGI(Common Gateway Interface) という技術がありました。 これは、ブラウザからのデータをWeb サーバが受け付け、指定されたアプリケー ションプログラムの標準入力に与えるようにする仕組みです。 アプリケーションプログラムは標準入力から受け取った入力を解釈し、情報処 理を行った後、結果を HTML に変換し、標準出力に出力します。 すると、 Web サーバは得られた出力をブラウザに送ります。

CGI は任意のプログラミング言語で作成でき、また標準入力や標準出力と言っ た使いこなされた入出力方式を使用するため、取っ付きやすく開発がしやすい と言う特徴がありました。 そのため、入出力処理や文字列処理が得意で開発が容易なスクリプト言語であ る Perl 言語が多用され、多くの CGI プログラムが作られました (グループスタディ I の受け付けシステムは Perl で作られた CGI を使用し ています)。

しかし、 CGI は Web サーバと別にアプリケーションプログラムが起動されま す。 これは、ブラウザが送ったデータごとに処理が行われるため、 Perl の処理系が起動し、 Perl のスク リプトが読み込まれ実行されます。 そのため、アクセスが集中すると、大量の Perl の処理系が起動することにな るなど、メモリや起動時の処理の効率が悪くなります。

さらに、通常の Web アプリケーションは、 「ログインした後で買い物をする」など、 一連の処理が多くの画面の集合体 によって成り立っています。 そのため、この処理の一貫性を管理する必要があります。 これをセッション管理と言います。 しかし、この CGI の仕組みだと、毎回独立して処理系が起動し、毎回処理を 終えます。 そのため、効率が悪いだけではなく、画面間のデータの受け渡しをサーバ側だ けではサポートできません。 セッション管理を行うためにはブラウザ側、サーバ側でセッションを管理する 情報をやりとりする必要があります。 そのため、データ出力を HTML に変換する際に引き渡し情 報を hidden 属性の input 要素として埋め込んだり、ブラウザ側 のCookie と呼ばれる記憶領域に情報を書き込んだりするような処 理をアプリケーション側で記述する必要がありました。

サーブレット

このように、 Web のアプリケーションサーバを構築するのに、CGI は様々な 問題を抱えていました。 そこで、プログラミング言語を固定し、Web アプリケーションに必要な機能を 追加したような Web アプリケーション専用の Web サーバが開発されました。 一つは Microsoft の IIS というサーバです。 一方、Java 言語に限定した Web アプリケーション仕様が J2EE(Java 2 Enterprise Edition)です。 これには Servlet と JSP(Java Server Pages) という規格が含まれて います。 ここではこの J2EE に準拠したフリーソフトのサーバ Tomcat サーバについて 説明します。 Tomcat 6.0 は Servlet 2.5 と JSP 2.1 に対応しています。 Servlet は Web のアクセスにより Java プログラムを動作させるもの、一方、 JSP は HTML ドキュメントに Java プログラムを埋め込むものです。

J2EE のうち、 Servlet 2.5 に関するドキュメントは http://jcp.org/aboutJava/communityprocess/mrel/jsr154/ にあります。

Tomcat サーバで Servlet プログラムを作成するには、 javax.servlet.http.HttpServlet クラスのサブクラスを作って、 doGet メソッ ドをオーバライドします。 すると、ブラウザからのアクセスに応答できます。 一方、ブラウザからのデータは特定のオブジェクトとして与えられます。 なお、ブラウザへの出力は特定の出力データストリームオブジェクトに print メソッドで送ります。 さらに、同一ブラウザが複数のページを順にアクセスするとき、セッショ ン という概念でデータを関連付けることができます。

なお、作成したプログラム(クラスファイル)と URI を対応付けるため、 web.xml というファイルを記述する必要があります。

基本操作

http://localhost:8080/xxx/yyy/zzz をアクセスすると Hello という名前 のクラスが動くというアプリケーションを作ります。 これには、最初の xxx はパッケージ名、その後の yyy/zzz はアノテーション @WebServlet("/yyy/zzz") あるいは @WebServlet(value={"/yyy/zzz"}) で指定します。 実行されるのはそのアノテーションが指定されたクラスです。

  1. 「File」→「new」→「Web」→「Dynamic Web Project」を選択する
  2. プロジェクト名を xxx に指定する
  3. すると下記のようにプロジェクトができます。
    JAX-WS Web Services
    Deployment Descriptor: xxx
    Java Resources: src
    JavaScript Resources
    build
    WebContent
    

    Contents of the Project
  4. Java Resources: src にパッケージを作り(この例では hello)、Hello クラス を追加し、下記を記入します。

    
    package hello;
    import java.io.*;
    import javax.servlet.*;
    import javax.servlet.http.*;
    @WebServlet("/yyy/zzz")
    public class Hello extends HttpServlet {
      private static final long serialVersionUID = 1L;
      public void doGet(HttpServletRequest request,
                        HttpServletResponse response)
                        throws IOException, ServletException{
        response.setContentType("text/html;charset=shift_jis");
        final PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">");
        out.println("<html lang=\"ja\">");
        out.println("<head>");
        out.println("<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">");
        out.println("<title>こんにちは</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<p>こんにちは サーブレット</p>");
        out.println("</body>");
        out.println("</html>");
      }
    }
    

Servlet 3.0 からは従来の web.xml ファイルの編集からは開放されました。 @WebServlet アノテーションの引数で関連付ける URL を指定します。

データの受信

HTML で form 要素を使い、データをサーヴレットに送信します。 これを受信するには、 doGet あるいは doPost メソッドをオーバライドする 際に、引数の HttpServletRequest 型の変数を使用します。 引数に getParameter メソッドでデータ名を引数に与えることで値を取り出す ことができます。

例13-4

senddata.html

Eclipse では WebContent フォルダ内に入れます。


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" 
 "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Send Data</title>
</head>
<body>
<form method="post" action="receiver">
<p>送信データを入力してください <input name="data" type="text" /></p>
<p><input name="送信" type="submit" /></p>
</form>
</body>
</html>
ReceiveData.java

Eclipse では Java Resources:src 内に testPackage パッケージを作り、そ の中に入れます。


package testPackage;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/receiver")
public class ReceiveData extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private void printHeader(PrintWriter out, String title){
		out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">");
		out.println("<html lang=\"ja\">");
		out.println("<head>");
		out.println("<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">");
		out.println("<title>"+title+"</title>");
		out.println("</head>");
	}
	@Override
	public void doPost(HttpServletRequest request,
			HttpServletResponse response)
	throws ServletException, IOException {
		response.setContentType("text/html;charset=utf-8");
		final PrintWriter out = response.getWriter();
		printHeader(out,"Result");
		out.println("<body>");
		request.setCharacterEncoding("utf-8");
		out.println("<p>受信したデータは「"+request.getParameter("data")+"」</p>");
		out.println("</body>");
		out.println("</html>");		
	}
}

セッション管理

サーヴレットでセッションを関連付けるには、 HttpServletRequest 引数に対 して、 getSession() メソッドで javax.servlet.http.HttpServlet 型のオブ ジェクトを取り出します。 このオブジェクトには次のメソッドがあります。

boolean isNew()
新規のセッションなら true を返します。
void setAttribute(String key, Object obj)
セッション間で渡すオブジェクトを登録します。
Object getAttribute(String key)
登録したセッション間で渡すオブジェクトを登録します。

13-3. JSP

JSP とは

Java Server Page(JSP) は HTML ドキュメントに Java コードを埋め込む技術です。 JSP2.1の API のマニュアルは http://jcp.org/aboutJava/communityprocess/final/jsr245/ からダウンロードできます。

Web アプリケーションにおいて、ドキュメントとプログラムの主従関係は ドキュメントにデータが埋め込まれるため、 HTML を print メソッドで出力 するより、 HTML の特殊構文に Java のプログラムが埋め込まれる方が利便性 が高くなります。 そのため、 JSP では <% Java プログラム %> の構文で Java のコードを埋め込みます。 すると、 Tomcat は JSP ファイルをサーブレットに変換し、さらにコンパイ ルした後実行します。 二度目からのアクセスについては、コンパイルしたものを使うので高速です。

なお、 JSP の基本構文には<% Java プログラム %> の他 に変数の内容を表示する <%= 変数名 %> と、初期設定を 行う<%@ 設定項目 %>と メンバを定義する <%! メンバ定義 %> があります。

基本構文


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="ja">
<head>
<meta http-equiv="content-type"
      content="text/html; charset=shift_jis" >
<%@ page language="java" contentType="text/html; charset=shift_jis" %>
<title>こんにちは</title>
</head>
<body>
<h1>こんにちは</h1>
<p>
<% final String hello = "こんにちは"; %>
<%= hello %>
</p>
</body>
</html>

日本語を使う際はこのように <%@ page ... %> で文字コー ドを指定し、さらに HTML 本体で所定の文字コードの指定を行います。 HTML 4.01 であれば、 meta 要素、 XHTML 1.1 であれば XML 宣言で行います。

page で指定する項目

ページの動作を定義する <%@ page ... %> の設定項目には次のものが あります。

language

language="java" を指定します。

contentType

上記の例で示しましたが、ドキュメントで使う文字コードを規程します。 漢字を使うときは必ず指定します。 Windows で開発するには MS漢字コード(shift_jis) を contentType="text/html; charset=shift_jis" を指定します。

import

JSP の中で使用するパッケージを指定します。 import="java.util.*,java.io.*" などとします。 もし、実行する JSP が別に作成したクラスを必要とするときは、特定のパッ ケージに入れ、 Java Resources の src の中に、そのパッケージを入れます。 そして、このimport の中でパッケージとクラスを指定します。

session

ブラウザが連続して訪れる複数のページで情報を共有する場合、 session="true" を指定します。 但し、これはデフォルト値なので、逆に情報を共有せずに独立したしょりを行 う場合は必ずsession="false" を指定します。 情報を共有するには、後述する session 変数で行います。

include

<%@ include file="ファイル名" %> を指定すると、指定したファイル をその位置に読み込みます。 なお、読み込まれるファイルが JSP の場合、 <%@ page %> 指定で文字 コードを指定しておく必要があります。

暗黙に宣言されているオブジェクト

JSP では暗黙に宣言されているオブジェクトを使用することができます。 これを使って複雑な処理やプロセス間通信などを行います。

pageContext

javax.servlet.jsp.PageContext 型のオブジェクトです。 暗黙に定義されるすべてのオブジェクトへのアクセスを提供します。

out

out は javax.servlet.ServletOutputStream (java.io.OutputStreamのサブクラス) 型のオブジェクトで、これに対して print など のメソッドを使うとそのままブラウザに文字を送ることができます。 各 print, println をオーバライドしているだけで、その他はすべて OutputStream のメソッドを継承しています。

request

これは javax.servlet.ServletRequest インターフェイス型のオブジェクトで す。

java.lang.String getParameter(String)

指定した文字列を name として持つパラメータの value を文字列で返します。 但し、漢字を使用する場合、文字コード変換をする必要があります。 詳しくは例13-5を参照。

java.util.Enumeration getParameterNames()

パラメータの name の一覧を Enumeration として返します。

session

session は javax.servlet.http.HttpSession 型のオブジェクトです。 これはセッション間で共有されます。このオブジェクトには 下記のように自由に共有オブジェクトを登録できます。

void setAttribute(String, Object)

名前をつけて、オブジェクトを登録します。

Object getAttribute(String)

名前で登録したオブジェクトを取り出します。

java.util.Enumeration getAttributeNames()

登録した名前の集まりを Enumeration として取り出します。

void removeAttribute(String)

登録した名前のオブジェクトを取り消します。

void invalidate()

セッションを無効にし、登録したオブジェクトをすべて破棄します。

application

application は javax.servlet.ServletContext 型のオブジェクトを参照しま す。 このオブジェクトはサーバが起動してから終了するまでを通じてオブジェクト を共有できます。 これも session 同様に、下記のメソッドで共有オブジェクトを登録できます。

void setAttribute(String, Object)

名前をつけて、オブジェクトを登録します。

Object getAttribute(String)

名前で登録したオブジェクトを取り出します。

java.util.Enumeration getAttributeNames()

登録した名前の集まりを Enumeration として取り出します。

void removeAttribute(String)

登録した名前のオブジェクトを取り消します。

データの受け取り

コンテンツ間でデータのやりとりを行うには次のようにします。 まず、送信側は HTML の form 機能を使用して、受信側の JSP ドキュメント を指定します。 一方、データの受け取りは request.getParameter(String) により受信します。 但し、受け取ったデータの漢字コードは送信側の漢字コードに依存しますので、 受け取ったデータを指定の漢字コードに変換する必要があります。 このため、次のようなコードで漢字コードを変換します。

漢字コード変換


new String(request.getParameter("データ名").getBytes("iso-8859-1"),
           "送信側文字コード名")

例13-5

単純な文字データの送受信の例を示します。 Tomcat プロジェクトとして da13 という名前で作成したのち、次の html と jsp ファイルを プロジェクトに追加して下さい。

ex3.html

Eclipse で挿入する際、空のファイルに対してブラウザが開いてしまうため、 Package Explorer において、 ex3.html を右クリックして「Open with → Text Editor」を選んで修正を行ってください。


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="ja">
<head>
<meta http-equiv="content-type"
      content="text/html;charset=shift_jis">
<title>データの送り渡し</title>
</head>
<body>
<h1>データの送り渡し</h1>
<form action="ex3.jsp" method="post">
<p>
<input type="text" name="data" value="あいうえお">
<input type="submit" value="送信">
</p>
</form>
</body>
</html>
ex3.jsp

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="ja">
<%@ page contentType="text/html; charset=shift_jis" session="true" %>
<head>
<meta http-equiv="content-type"
      content="text/html;charset=shift_jis">
<title>データの受け取り</title>
</head>
<body>
<h1>データの受け取り</h1>
<p>
<%= new String(request.getParameter("data").getBytes("iso-8859-1"),"shift_jis") %>
</p>
</body>
</html>

カウンタ

JSP ではアプリケーション(起動時から終了時まで)レベル、セッションレベ ルのデータの共有は application, session オブジェクトを使います。 一方、この他に独自に共有オブジェクトを作成することも可能です。 但し、 多重アクセスによるデータ破壊を防ぐため、共有資源のアクセスには synchronized キーワードを使い排他制御を行います。

例13-6

下記の 3 つの JSP ファイルを一つのプロジェクト(仮に ex5)の中に入れます。 そして、 http://localhost:8080/ex5/counter1.jsp にアクセスすると、様々 なカウンタの値が表示されます。 リンク先をたどるとカウンタ値が増えますが、それぞれのカウンタにより増え 方が異なります。 http://localhost:8080/ex5/counter3.jsp にアクセスするとセッションを終了させるので、そこから他のリンクをたどる と、セッションに関してリセットされます。

counterbase.jsp

<%@ page language="java" contentType="text/html; charset=shift_jis" session="true" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="ja">
<head>
<meta http-equiv="content-type"
      content="text/html;charset=shift_jis">
<title>ドキュメント<%= docno %></title>
</head>
<body>
<h1>ドキュメント<%= docno %></h1>
<%!
   private static int static_counter=0;
   private int member_counter=0;
   private static synchronized void add_static_counter(){
   	static_counter++;
   }
   private synchronized void add_member_counter(){
   	member_counter++;
   }

%>
<% 
   Object ac = application.getAttribute("counter");
   int application_counter =1+(ac==null ? 0 : (Integer)ac);
   application.setAttribute("counter",application_counter);
	Object sc = session.getAttribute("counter");
   int session_counter = 1+( sc == null? 0 : (Integer)sc);
   session.setAttribute("counter",session_counter);
	add_static_counter();
	add_member_counter();
%>
<dl>
<dt>アプリケーション</dt>
<dd><%= application_counter %></dd>
<dt>セッション</dt>
<dd><%= session_counter %></dd>
<dt>スタティック</dt>
<dd><%= static_counter %></dd>
<dt>メンバー</dt>
<dd><%= member_counter %></dd>
</dl>
<ol>
<%
for(int i=1; i<=3 ; i++){
%>
<li><a href="counter<%=i%>.jsp">ドキュメント<%=i%></a></li>
<% } %>
</ol>
counter1.jsp

<%@ page language="java" contentType="text/html; charset=shift_jis" session="true" %>
<%! final private int docno=1; %> 
<%@ include file="counterbase.jsp" %>
</body>
</html>
counter2.jsp

<%@ page language="java" contentType="text/html; charset=shift_jis" session="true" %>
<%! final private int docno=2; %> 
<%@ include file="counterbase.jsp" %>
</body>
</html>
counter3.jsp

<%@ page language="java" contentType="text/html; charset=shift_jis" session="true" %>
<%! final private int docno=3; %> 
<%@ include file="counterbase.jsp" %>
<p>
セッション終了
</p>
<%
 session.invalidate();
 %>
</body>
</html>

補足

13-4. Web セキュリティ

Web アプリケーションなど、不特定多数へのサービスを行う際、無関係な個々 人に不利益を与えるような攻撃が可能になってしまう場合があります。 これは従来のクライアント/サーバの一対一の関係以上の関係を考慮しなけれ ばならないので、難易度が高くなっています。

XSS(クロスサイトスクリプティング)

古くは Web 掲示板などでタグを埋め込んで、点滅させたり字を大きくさせた りなど、利用者の可視性を損なうような嫌がらせがありましたが、それと同じ く、ページの制作者が意図しないタグを攻撃者が埋め込むことによる攻撃です。

例13-5 の ex3.jsp はこの XSS 脆弱性を含んでいます。 試しに http://localhost:8080/da13/ex3.jsp?data=abc というリンクをアクセスしてください。 このように、ex3.jsp は data に与えた文字列をそのまま表示してしまいます。 タグの利用も http://localhost:8080/da13/ex3.jsp?data=</p><h1>abc</h1><p> のように自由自在です。

まとめると、 変数の内容をそのまま表示してしまうようなサイトが有ったとき、そのリンク を利用して、そのサイトのドキュメントをある程度自由に作成できるというこ とです。 したがって、そのサイトにおいて、ユーザ管理や決済などの処理なども、 リンクを操作することによりコントロールができるようになるということです。

現在のオンラインショッピングなどではログインしてから商品を購入しますが、 各ページ毎にパスワードを入れるわけではなく、セッション管理がされていま す。 利用者がログインし、セッションが有効になっている間に、もし、上記のよう なリンクを攻撃者がメールなどで利用者に送り、利用者がリンクをクリックす ると、攻撃者が意図した攻撃を「ログイン後」に行うことができるようになっ てしまいます。 このセッションを乗っ取られることを セッションハイジャックと言います。 このように、攻撃者から利用者、利用者からサーバと悪意のスクリプトが渡る ことから「クロスサイトスクリプティング」という名称がつけられています。

対処法

入力された文字列を出力する場合、ブラウザに対して意図しないタグを出力さ せないということに尽きます。 そのための基本戦略として、次のことが挙げられます。

  1. 使用する変数をカプセル化して、 getter を作成する。
  2. 画面にだす際に getter を使用する。
  3. getter において、変数の中身をチェックし、タグが出力されないようにする。

但し、単純にタグを取り除いても失敗する場合があります。 例えば、 <, > のみを取り除く処理を行うとします。 しかし、ブラウザには文字列中に実体参照という &xxx; という表現を特 定の文字に変換するという機能があります。 また、 URL エンコードという %16進数 という形式で文字に変換する機能もあります。 そのため、特定の文字を取り除くだけだと取り除いた結果が別の文字を生じた りする場合もあります http://localhost:8080/da13/ex3.jsp?data=%3c/p%3e%3ch1%3eabc%3c/h1%3e%3cp%3e 。 そのため、様々な仕様から精査する必要があります。

CSRF(クロスサイトリクエストフォージェリ)

セッションの途中の認証を怠っていると、セッションの途中から割り込んでサー バーを誤動作させる攻撃があります。 これをクロスサイトリクエストフォージェリと言います。 掲示板への自動投稿や特定キーワードの検索などが良く知られています。

対処法としてはセッションハイジャックされないように、入力ページも単なる HTML ではなくセッションに加えるようにすることと、各ページで認証を確認 することが挙げられます。 Captchaと呼ばれる可読性を落とした文字画像を利用者に判定させて、機械的 なアクセスを防ぐという手法もありますが、これはあまり有効ではないという 指摘があります。

SQL インジェクション

Web において他の言語を用いるサービスと連携する場合において、コマンドを発行する 際にコマンド列に制作者が意図しないコマンドを紛れ込ませる攻撃です。 これはコマンドにおけるエスケープのルールなどが異なるために起きうるもの で、根本的には XSS と対処法は変わりません。 コマンドにおいて入力されたくない文字をピックアップして排除するのが基本 になります。

13-5. Tomcat のインストールと運用

Tomcat のインストール

  1. Apache Tomcatから Windows Service Installer をダウンロードします。 2013年1月10日現在、 7.0.34 です。 Windows Service Installer をダウンロード、実行します。
  2. http://localhost:8080/ にアクセスして、Apache Tomcat の画面が出たらインストール終了です。
  3. Eclipse にコントロールを渡すために、タスクトレイのアイコンを右クリック してサービスを止めます。
    stop Tomcat

Eclipse との結合

Tomcat 7.0 を local で動くように設定します。 なお、Eclipse は Eclipse IDE for Java EE Developers が必要です。

  1. Eclipse で、 Window→Show View → Servers (または Window → Show View → Other... を選択して Server の中の Servers) で Servers ウィンドウを表示 します。
  2. 案内に従って(あるいは New → Serverにより) New Server 画面を 開きます。 ここで、Select the Server Type のペインの中の Tomcat v7.0 Server を選 びます。
    Define a New Server
  3. 次に現れる Tomcat Server の画面で Tomcat のインストールしたフォルダを指定します。 通常は Browse... ボタンを押して、 C:\Program Files\Apache Software Foundation\Tomcat 7.0 を探して、Next を押します。
    Tomcat Server

Tomcat のインストールの注意点

Tomcat 7 は、Windows Vista, 7, 8 では、スタートメニューから起動できず、 「アクセスが拒否されました」と表示されます。 これは「管理者モード」で動かせば良いです。 但し、これを管理者モードを指定せずに動作させるには 次のような追加の操作が必要です。

    1. c:\Program Files\Apache Software Foundation\Tomcat 7.0\bin\tomcat7w.exeのプロパティを開く
    2. 「互換性」タブを開く
    3. 「特権レベル」の「管理者としてこのプログラムを実行する」をチェックして「Ok」を押す。
    1. c:\Program Files\Apache Software Foundation\Tomcat 7.0\conf\Catalina\localhost フォルダのプロパティを開く
    2. 「セキュリティ」タブの「グループ名またはユーザ名」欄の下の「編集」を押す
    3. 立ち上がった「localhost のアクセス許可」の「グループ名またはユーザ名」の中の Users を選択し、下の欄の「フルコントロール」をチェックし、「Ok」を押す。

13-6. 付録

newmallsrcのインストール

  1. 任意のパッケージ名(ここでは webenshu とします)でDynamic Web Project を 作成します。
  2. プロジェクトを右クリックして import を指定し、 General→Archive File を選んで、newmallsrc.zip を読み込みます。
  3. するとプロジェクトのルートに newmallsrc というフォルダができます。
  4. Java Resources の src 中に sakamoto パッケージを作成します
  5. newmallsrc 中の sakamoto フォルダ中にある User.java と Item.java を作 成した sakamoto パッケージに移動します。
  6. newmallsrc 中の html と jsp ファイルを WebContent フォルダに移動します。
  7. newmallsrc フォルダを残されたファイルとともに消します
  8. 必要に応じて、Servers タブ中にある Tomcat v7 server を右クリックして、 Add and Remove を選び、このプロジェクト(webenshu)を add します。
  9. 必要に応じて、Servers タブ中にある Tomcat v7 server を右クリックして、 Start または Restart します。
  10. http://localhost:8080/webenshu/toroku.html などでアクセスできるようになります。

13-7. 参考文献

  1. JSRs: Java Specification Requests JSR 315: Java™ Servlet 3.0 Specification http://jcp.org/en/jsr/detail?id=315
  2. http://www.coreservlets.com/Apache-Tomcat-Tutorial/tomcat-7-with-eclipse.html
  3. 寺田 佳央 「Yoshio Terada Servlet 3.0 の新機能概要」 http://yoshio3.com/2010/03/14/servlet-3-0-%E3%81%AE%E6%96%B0%E6%A9%9F%E8%83%BD%E6%A6%82%E8%A6%81/

坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科