このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
アクセスカウンタを考えます。 アクセスカウンタは値を保持しており、その値を読み込んだ後、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 を実行できるようにな ります。 これを導入すると上記のカウンターの処理は次のようになります。
サーバのカウンタ値 | ブラウザ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 を使用します。 これは引数に何らかのオブジェクトか配列を取り、続いてブロックを書きます。 そして、オブジェクトがロックされてないときに、ロックしてからブロックを 実行して、実行を終えてからロックを解除します。
上記の概念的なカウンタ処理を Java 風に書くと次のようになります。
private Object semaphore = new Object();
private int counter = 0;
public void increment(){
synchronized(semaphore){ // P 操作
counter++;
} // V 操作
}
しかし、 Java ではさらに便利な表現として、メソッドの修飾子として synchronized を使用できます。
上記の概念的なカウンタ処理を Java 風に書くと次のようになります。
private Object semaphore = new Object();
private int counter = 0;
public synchronized void increment(){
counter++;
}
なお、あらゆるメソッドで上記のような synchronized を使用すると多重書き 込みを避けることはできますが、あらゆる場面でロックがかかりプログラムの 効率が落ちます。 そのため、どの場面で排他制御が必要か吟味する必要があります。 また、複数の資源の排他制御に関しては、二つのプロセスが双方で資源を取り 合って、互いに相手の資源の開放を永遠に待ってしまう デッドロック という重大なバグが発生する場合があります。 今回はデッドロックに関してまでは説明はしませんが、様々な定石が開発され てますので、もしデッドロックの問題を抱えたら、デッドロックをキーワード に文献等を調査して下さい。
Web のサービスは単なる静的なデータの提供に止まりません。 動的なデータサービスを行うため、ブラウザからのデータ入力に対して、サー バ側のアプリケーションプログラムが情報処理を行い、結果を表示する仕組み が必要です。 そのため、古くは CGI(Common Gateway Interface) という技術がありました。 これは、ブラウザからのデータをWeb サーバが受け付け、指定されたアプリケー ションプログラムの標準入力に与えるようにする仕組みです。 アプリケーションプログラムは標準入力から受け取った入力を解釈し、情報処 理を行った後、結果を HTML に変換し、標準出力に出力します。 すると、 Web サーバは得られた出力をブラウザに送ります。
CGI は任意のプログラミング言語で作成でき、また標準入力や標準出力と言っ た使いこなされた入出力方式を使用するため、取っ付きやすく開発がしやすい と言う特徴がありました。 そのため、入出力処理や文字列処理が得意で開発が容易なスクリプト言語であ る Perl 言語が多様され、多くの CGI プログラムが作られました。 (グループスタディ I の受け付けシステムは Perl で作られています。
しかし、 CGI は Web サーバと別にアプリケーションプログラムが起動されま す。 これは、 1 セッションという、ブラウザが送ったデータごとに処 理が行われるため、セッションごとに Perl の処理系が起動し、 Perl のスク リプトが読み込まれ実行されます。 そのため、アクセスが集中すると、大量の Perl の処理系が起動することにな るなど、メモリや起動時の処理の効率が悪くなります。
さらに、通常の Web アプリケーションは多くの画面の集合体になっています。 しかし、この CGI の仕組みだと、毎回独立して処理系が起動し、毎回処理を 終えます。 そのため、画面間のデータの受け渡しがサーバ側でサポートされません。 これをセッション管理と言います。 セッション管理を行うために、データ出力を HTML に変換する際に引き渡し情 報を hidden 属性の input 要素として埋め込んだり、ブラウザ側 のCookie と呼ばれる記憶領域に情報を書き込んだりするような処 理をアプリケーション側で記述する必要がありました。
このように、 Web のアプリケーションサーバを構築するのに、CGI は様々な 問題を抱えていました。 そこで、プログラミング言語を固定し、Web アプリケーションに必要な機能を 追加したような Web アプリケーション専用の Web サーバが開発されました。 一つは Microsoft の IIS というサーバです。 ここでは Java 言語に特化した Tomcat サーバについて説明します。 Tomcat 6.0 は Servlet 2.5 と JSP 2.1 に対応しています。
J2EE のうち、 Servlet 2.5 に関するドキュメントは http://jcp.org/aboutJava/communityprocess/mrel/jsr154/ にあります。 Tomcat サーバで Servlet を動作させるには、 javax.servlet.http.HttpServlet クラスのサブクラスを作って、 doGet メソッ ドをオーバライドします。 すると、ブラウザからのアクセスに応答できます。 一方、ブラウザからのデータは特定のオブジェクトとして与えられます。 なお、ブラウザへの出力は特定の出力データストリームオブジェクトに print メソッドで送ります。 さらに、同一ブラウザが複数のページを順にアクセスするとき、セッショ ン という概念でデータを関連付けることができます。
http://localhost:8080/aaa/bbb をアクセスすると Ccc という名前 のクラスが動くというアプリケーションを作ります。
サーブレットの基本は javax.servlet.http.HttpServlet のサブクラスを作って、 void doGet(HttpServletRequest req, HttpServletResponse resp) メソッドをオーバライドします。 HttpServletResponce は javax.servlet.ServletResponse をインプリメントしています。 HTML を出力するには、resp に対して、 getWriter メソッドにより java.io.PrintWriter オブジェクトを得ます。
まず、 次のクラスを WEB-INF/src フォルダへ加えます。
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
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>");
}
}
次に、次のファイルを WEB-INF に web.xml というファイル名で加えます。
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<servlet>
<servlet-name>bbb</servlet-name>
<servlet-class>Hello</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>
bbb
</servlet-name>
<url-pattern>
/bbb
</url-pattern>
</servlet-mapping>
</web-app>
すると http://localhost:8080/aaa/bbb という URI で「こんにちはサーブレット」というページにアクセスできます。
HTML には form というサーバへデータを送る仕組みがあります。 これを受けとるには
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 ... %> の設定項目には次のものが あります。
language="java"
を指定します。
上記の例で示しましたが、ドキュメントで使う文字コードを規程します。
漢字を使うときは必ず指定します。
Windows で開発するには MS漢字コード(shift_jis) を
contentType="text/html; charset=shift_jis"
を指定します。
JSP の中で使用するパッケージを指定します。
import="java.util.*,java.io.*"
などとします。
ブラウザが連続して訪れる複数のページで情報を共有する場合、
session="true"
を指定します。
但し、これはデフォルト値なので、逆に情報を共有せずに独立したしょりを行
う場合は必ずsession="false"
を指定します。
情報を共有するには、後述する session 変数で行います。
<%@ include file="ファイル名" %> を指定すると、指定したファイル をその位置に読み込みます。 なお、読み込まれるファイルが JSP の場合、 <%@ page %> 指定で文字 コードを指定しておく必要があります。
JSP では暗黙に宣言されているオブジェクトを使用することができます。 これを使って複雑な処理やプロセス間通信などを行います。
javax.servlet.jsp.PageContext 型のオブジェクトです。 暗黙に定義されるすべてのオブジェクトへのアクセスを提供します。
out は javax.servlet.ServletOutputStream (java.io.OutputStreamのサブクラス) 型のオブジェクトで、これに対して print など のメソッドを使うとそのままブラウザに文字を送ることができます。 各 print, println をオーバライドしているだけで、その他はすべて OutputStream のメソッドを継承しています。
これは javax.servlet.ServletRequest インターフェイス型のオブジェクトで す。
指定した文字列を name として持つパラメータの value を文字列で返します。 但し、漢字を使用する場合、文字コード変換をする必要があります。 詳しくは例13-3を参照。
パラメータの name の一覧を Enumeration として返します。
session は javax.servlet.http.HttpSession 型のオブジェクトです。 これはセッション間で共有されます。このオブジェクトには 下記のように自由に共有オブジェクトを登録できます。
名前をつけて、オブジェクトを登録します。
名前で登録したオブジェクトを取り出します。
登録した名前の集まりを Enumeration として取り出します。
登録した名前のオブジェクトを取り消します。
セッションを無効にし、登録したオブジェクトをすべて破棄します。
application は javax.servlet.ServletContext 型のオブジェクトを参照しま す。 このオブジェクトはサーバが起動してから終了するまでを通じてオブジェクト を共有できます。 これも session 同様に、下記のメソッドで共有オブジェクトを登録できます。
名前をつけて、オブジェクトを登録します。
名前で登録したオブジェクトを取り出します。
登録した名前の集まりを Enumeration として取り出します。
登録した名前のオブジェクトを取り消します。
コンテンツ間でデータのやりとりを行うには次のようにします。 まず、送信側は HTML の form 機能を使用して、受信側の JSP ドキュメント を指定します。 一方、データの受け取りは request.getParameter(String) により受信します。 但し、受け取ったデータの漢字コードは送信側の漢字コードに依存しますので、 受け取ったデータを指定の漢字コードに変換する必要があります。 このため、次のようなコードで漢字コードを変換します。
new String(request.getParameter("データ名").getBytes("iso-8859-1"),
"送信側文字コード名")
単純な文字データの送受信の例を示します。 Tomcat プロジェクトとして da13 という名前で作成したのち、次の html と jsp ファイルを プロジェクトに追加して下さい。
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>
<!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
キーワードを使い排他制御を行います。
下記の 3 つの JSP ファイルを一つのプロジェクト(仮に ex5)の中に入れます。 そして、 http://localhost:8080/ex5/counter1.jsp にアクセスすると、様々 なカウンタの値が表示されます。 リンク先をたどるとカウンタ値が増えますが、それぞれのカウンタにより増え 方が異なります。 http://localhost:8080/ex5/counter3.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>
<%@ page language="java" contentType="text/html; charset=shift_jis" session="true" %>
<%! final private int docno=1; %>
<%@ include file="counterbase.jsp" %>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=shift_jis" session="true" %>
<%! final private int docno=2; %>
<%@ include file="counterbase.jsp" %>
</body>
</html>
<%@ 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>
Web アプリケーションなど、不特定多数へのサービスを行う際、無関係な個々 人に不利益を与えるような攻撃が可能になってしまう場合があります。 これは従来のクライアント/サーバの一対一の関係以上の関係を考慮しなけれ ばならないので、難易度が高くなっています。
古くは Web 掲示板などでタグを埋め込んで、点滅させたり字を大きくさせた りなど、利用者の可視性を損なうような嫌がらせがありましたが、それと同じ く、ページの制作者が意図しないタグを攻撃者が埋め込むことによる攻撃です。
例13-3 の 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> のように自由自在です。
まとめると、 変数の内容をそのまま表示してしまうようなサイトが有ったとき、そのリンク を利用して、そのサイトのドキュメントをある程度自由に作成できるというこ とです。 したがって、そのサイトにおいて、ユーザ管理や決済などの処理なども、 リンクを操作することによりコントロールができるようになるということです。
現在のオンラインショッピングなどではログインしてから商品を購入しますが、 各ページ毎にパスワードを入れるわけではなく、セッション管理がされていま す。 利用者がログインし、セッションが有効になっている間に、もし、上記のよう なリンクを攻撃者がメールなどで利用者に送り、利用者がリンクをクリックす ると、攻撃者が意図した攻撃を「ログイン後」に行うことができるようになっ てしまいます。 このセッションを乗っ取られることを セッションハイジャックと言います。 このように、攻撃者から利用者、利用者からサーバと悪意のスクリプトが渡る ことから「クロスサイトスクリプティング」という名称がつけられています。
入力された文字列を出力する場合、ブラウザに対して意図しないタグを出力さ せないということに尽きます。 そのための基本戦略として、次のことが挙げられます。
但し、単純にタグを取り除いても失敗する場合があります。 例えば、 <, > のみを取り除く処理を行うとします。 しかし、ブラウザには文字列中に実体参照という &xxx; という表現を特 定の文字に変換するという機能があります。 また、 URL エンコードという %16進数 という形式で文字に変換する機能もあります。 そのため、特定の文字を取り除くだけだと取り除いた結果が別の文字を生じた りする場合もあります http://localhost:8080/da13/ex3.jsp?data=%3c/p%3e%3ch1%3eabc%3c/h1%3e%3cp%3e 。 そのため、様々な仕様から精査する必要があります。
セッションの途中の認証を怠っていると、セッションの途中から割り込んでサー バーを誤動作させる攻撃があります。 これをクロスサイトリクエストフォージェリと言います。 掲示板への自動投稿や特定キーワードの検索などが良く知られています。
対処法としてはセッションハイジャックされないように、入力ページも単なる HTML ではなくセッションに加えるようにすることと、各ページで認証を確認 することが挙げられます。 Captchaと呼ばれる可読性を落とした文字画像を利用者に判定させて、機械的 なアクセスを防ぐという手法もありますが、これはあまり有効ではないという 指摘があります。
Web において他の言語を用いるサービスと連携する場合において、コマンドを発行する 際にコマンド列に制作者が意図しないコマンドを紛れ込ませる攻撃です。 これはコマンドにおけるエスケープのルールなどが異なるために起きうるもの で、根本的には XSS と対処法は変わりません。 コマンドにおいて入力されたくない文字をピックアップして排除するのが基本 になります。
Apache Tomcat は Tomcat 6.?からダウンロードできます。 2009年11月28日現在、 6.0.20 の Windows Service Installer をダウンロー ド、インストールします。 http://localhost:8080/ にアクセスして、Apache Tomcat の画面が出たらインストール終了です。
但し、 6.0.20 は Windows Vista や Windows 7 に完全に対応しているわけではありません。 Windows Vista, 7 では次のような追加の操作が必要です。
あらかじめインストールされている Eclipse に Tomcat 用のプラグインを導 入します。