前回は、フォームからデータをサーブレットに渡すところまでをやりました。

今回は、今までの知識を応用してログイン処理を作ってみます。

前回学んだリクエスト属性を使えば、入力画面に入力した内容をもとにJSPの出力画面に情報を表示できました。

しかし、実はリクエスト属性は複数ページを跨いで情報の保持ができません。

そこで必要となるのがセッション属性です。

セッション属性を使うことで複数ページを跨いで情報の保持ができます。

そしてログイン処理は複数ページを跨いで情報を保持する必要のある典型的な処理です。

1. ログイン処理とは

まずは一般的なログイン処理についてまとめておきましょう。

皆さんも是非、普段お使いのWebアプリケーションで実験してみてください。(あるいは講師の実演をご覧ください)

突然ですがログインに関する質問です。

  • あるブラウザでログイン中に同じブラウザで他のタブを開いたら新しい方のタブでログインは継続されますか?
あなたの答え:
  • あるブラウザ(例えばクローム)でログイン中に新しいブラウザ(例えばエッジ)を起動したら新しいブラウザでログインは継続されますか?
あなたの答え:
  • ブラウザでログインしてから閉じるボタンを押してブラウザを閉じ、その後また同じブラウザを起動したらログインは継続されたままですか?
あなたの答え:
  • ログアウトしてからブラウザの戻るボタンを押すと元のログイン状態に戻りますか?
あなたの答え:

上記の実験からも明らかなように一般的にはログインとは以下のような処理を指します。

①ユーザーはブラウザからIDとパスワードといった自分だけが知る情報を使ってログインする

②ログイン後はそのユーザーだけが見られるページに遷移できるようになる

③ログイン中は同じブラウザで他のタブを開いてもログインされたままである

④しかし、同じユーザーが別のブラウザで同じページにアクセスしても再度ログイン処理が必要である

⑤ログアウト処理をする、ブラウザを終了するまたは一定時間(大抵は30分間だが銀行のように5分程度と短い場合も)が経過するとログアウトする

上記結果④から考えるとログイン処理はブラウザごとに固有の情報を利用していると推測できます。

また、結果⑤から考えるとログアウトのタイミングは技術者が個々に設定していることが分かります。

本章では、ログイン処理を実現するにはセッションという仕組みを理解することが必要になるということ、さらに同一セッションで有効なセッション属性を使うことでリクエスト属性では実現できなかった複数リクエストを跨いだデータ保持が可能になるということを解説していきたいと思います。

例えば、オンラインショッピングで買い物かごの仕組みが実現できるのは、かごの中身をこのセッション属性に入れているから複数ページに渡ってデータを保持できるのです。

セッション属性とは開始してから破棄されるまで同一ブラウザで有効なスコープです。

セッションの破棄は、「ログアウト処理」、「ブラウザを終了する」または「セッションタイムアウト」により実行されます

じつは、前章までの知識で実現できるのは上記「①ユーザーはブラウザからIDとパスワードといった自分だけが知る情報を使ってログインする」だけです。

ですから、まずは①を実現しましょう。

1.1. IDとパスワードでログインする

以下の図5.1が処理の概要です。

ユーザーがブラウザからユーザーIDとパスワードを入力してログインボタンを押下します。

サーブレットはIdとpasswordが正しいことをチェックします。

①IDとパスワードが正しければ、messageをmember_only.jspにフォワードします。

②IDとパスワードが正しくなければ、エラー画面を表示します。

図5.1 ログイン処理の概要

2. 不完全なログイン処理の実装

不完全ながらも、これまでの知識でログイン処理を実装してみます。

新しく「05Login」というフォルダを用意してその中にウェルカムファイル「index.jsp」を用意してください。

なお、HTMLファイルではなく、JSPファイルにしたのは後でメニューを組み込む処理を書くためです。

図5.2 ログイン画面

ログイン画面

以下のlogin.jspはユーザーIDとパスワードの入力を求める画面です。

ソースコードを読み込んで質問に答えてください。

  • 8~19行目の記述は何のためですか?
あなたの答え:
  • 25行目のフォーム部品は何ですか?
あなたの答え:

サーブレットのソースコード

以下のLogin1.javaはIDとパスワードを認証するサーブレットです。

プログラムを読み込んで質問に答えてください。

  • 20行目ではどんな属性に対して何をしていますか?
あなたの答え:
  • 22行目のif文でtrueの場合はどうなりますか?
あなたの答え:

次に、22行目のif文でfalseの場合、すなわち25行目のIDまたはパスワードが間違った場合を見てみましょう。

ここでは、フォワードを使っていませんね。

フォワードを使ってもよいのですが、フォワードですとアドレスバーの表記が変わらないためログインしていないことが伝わりづらいため、リダイレクト処理というものを行っています。

3. フォワードとリダイレクト

画面遷移の方法にはフォワードとリダイレクトがあります

英語のredirectには「re=再び」「direct=方向づける」という意味があります。

データの出力先などを変更するという意味でIT業界では広く使われる言葉です。

その特徴をまとめると以下の図5.3の通りです。

下図5.3で特に注目いただきたいのは、

「2.ソースコード」のところでフォワードがrequestのメソッドであるのに対して、リダイレクトはresponseのメソッドであるという点

「3.典型的な処理の流れ」で、フォワードがリクエストとレスポンスを1往復交わすのに対して、リダイレクトは2往復であるという点

「4.リクエストスコープ」でリダイレクトはリクエスト属性を引き継がない点

です。

また、リダイレクトは「5.転送可能範囲」で外部サーバーOKとなっています。

したがって上記の記述を以下のように絶対パスで書いても同じ結果が得られます。

response.sendRedirect("http://localhost:8080/03_JavaWebText/05Login/login_error.jsp");

fowardRedirect
図5.3 forwardとredirectの比較

実験1

フォワードで以下のように絶対パスで指定するのは間違っています。

一度ご自身でも確かめてみてください。

request.getRequestDispatcher(" http://localhost:8080/03_JavaWebText/ 05Login/member_only.jsp").forward(request, response);

確かめた結果:

参考

なお、やや細かい話ではありますが、上記の例で「"imai".equals(id)」のように「文字列.equals(変数名)」としているのはNullPointerExceptionの可能性を排除するためです。

id.equals("imai")

のように「変数名.equals(文字列)」とすると、idがnullの可能性を排除できません。

NullPointerExceptionが発生する可能性がありますので、

if (id != null){
if (id.equals("imai")… )
}

のようにnull判定をする必要があります。

しかし、"imai"という文字列にはnullの心配がないのです。

ログインエラー画面のソース

以下のlogin_error.jspはログインエラー時の画面、menber_only.jspはログイン成功時の画面です。

menber_only.jspの3行目の以下の記述

${requestScope.message}

は、リクエスト属性を指定する書き方です。

次のセッション属性を指定する書き方と比べてみてください。


ここでの問題点を2点指摘しておきます。

①リクエストパラメータにIDとパスワードが表示されてしまっている

②member_only.jspのURLをブラウザのアドレスバーに直接入力することでアクセスできてしまう

①のIDとパスワード丸見え問題については以下のようなアドレスバーの表示が気になっていた人もいるのではないでしょうか?

「http://localhost:8080/03_JavaWebText/Login1?id=imai&pass=p」

  • この問題の解決方法はなんですか?HTMLのフォームで学んだ知識でお答えください。
あなたの予想:

まずは、この問題を解決していきましょう。

4. getメソッドとpostメソッドの違い

HTMLで学んだ図5.4にあるpostメソッドの特徴をログイン処理に応用しましょう。

getPost
図5.4 getメソッドとpostメソッドの違い

<login_post.jsp>※差分のみ表示

 <form action="/03_JavaWebText/Login2" method="post">

<Login2.java>※差分のみ表示

@WebServlet(urlPatterns = {"/Login2"})
public class Login2 extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

ブラウザのアドレスバーは以下のようになり「パスワード丸見え問題」は解決しました。

「http://localhost:8080/03_JavaWebText/Login2」

では、ここでもう少し踏み込んでgetメソッドとpostメソッドの違いについて見ていきましょう。

ブラウザには開発用のツール(ディベロッパーツール)があります。

例えば、当社の新人エンジニア研修で使用しているChromeの場合はブラウザの画面を右クリックして「検証」を選びます。(ショートカットキーはF12)

当初は英語で表示されると思いますが、以下の設定画面で日本語にすることもできますので講師から説明を受けてください。

図5.5 Google Chromeのディベロッパーツールを日本語化する

図5.6にある「ネットワーク」のタブをクリックします。

ちなみに、ここの「キャッシュを無効化」にチェックを入れるとブラウザが過去の表示内容をキャッシュしなくなり、ファイルを更新したはずなのに表示が更新されないというトラブルを無くすことができます。

もうすでに講師から説明があったかも知れませんね。

図5.6 ネットワークタブ

F5キーを押してリクエストとレスポンスのやり取りをもう一度実行します。

次に、「名前」のところでサーブレットのURLパターン(例.Login2)を選択して、右の「ヘッダー」をクリックします。

図5.7の上がpostメソッド、下がgetメソッドの表示例です。

図5.7 ヘッダーの情報
  • postとgetどこが違うか分かりますか?
あなたの答え:

ただし、postリクエストにするとリクエストパラメータがアドレスバーに表示されないというだけです。

上図5.7の「Payload」というタブを開くとパラメータを見ることができてしまいます。

httpsを使った暗号化などをしないとパケットを中継する機器からは丸見えですので誤解のないように。

ちなみにステータスコードが「200 OK」になっていますね。

HTTPプロトコルにおいてステータスコードとは、Webサーバからのレスポンスの意味を表す3桁の数字です。

ここで当社の新人エンジニア研修で見かけるステータスコードを表5.1にまとめておきます。

ステータスコード意味
200正しく表示されている
403アクセスを禁止されている
404ページが見つからない
405このURLではサポートされていないメソッドでアクセスした
500サーバ内部エラー
表5.1 ステータスコード

新人エンジニアの皆さんは、まずステータスコードの400番台はクライアント側のエラー、500番台はサーバー側のエラーだということを覚えましょう。

なぜなら、Webアプリケーションのエラーが起こったときにどちら側の責任なのかを切り分けることができるようになるからです。

次に図5.8のように「Cookie」をクリックしてください。

名前に「JSESSIONID」、値に32文字の16進数が入っていますね。

図5.8 JSESSIONID

Cookie とは、Webサイトの訪問者の情報を一時的にパソコン(やスマートフォン等)に保存するための仕組みです。

Cookie の目的はユーザーの識別です。

例えば、フォーム画面でオートコンプリートが可能なのもこの Cookie の仕組みがあるからです。

また、近年 Cookie を使った広告のトラッキングが問題になっていることをご存じの方もいるかと思います。

そして今回作りたいログイン処理もこの Cookie を使った仕組みなのです。


次に、 JSESSIONID とはJ2EEを使用したWebアプリケーションにおいてデフォルトで使用されるセッションIDを表す番号です。

JSESSIONID の先頭のJはJavaのJなのですね。

セッションとは、ブラウザとWebサーバの一連の複数のリクエストとレスポンスの活動期間のことです。

(例えば、ギターセッションというとギターのプレイヤー同士が楽器を奏でてやり取りをすることでした)

このセッションごとにIDが付いているわけです。

実は一般的なログイン処理はこのセッションの仕組みを利用しているのです。(注:他にも方法はあります)

次はこのセッションの仕組みを使ってログイン処理を実装しましょう。

実験2

サーブレット(やJSP)を実行した場合にはJSESSIONIDが発行されました。

HTMLを単体で実行した場合はJSESSIONIDが発行されますか?

実験結果のメモ:

実験3

同じユーザ(PC)であってもブラウザが違えば異なるJSESSIONIDが発行されるとの説明でした。

お手元のパソコンでそのことを確かめてください。

実験結果のメモ:

調べてみましょう

クッキーはブラウザ側に保存されるのでした。

実はブラウザの設定画面からもクッキーを見ることができます。

講師のアドバイスも受けながらあなたのPCにどのようなクッキーが保存されているかを調べてみましょう。

また、クッキーがどのような用途に使われているかを考察しましょう。

調べた結果のメモ:

5. セッションIDの仕組み

セッションIDの仕組みを解説します。

  • クライアント(ブラウザ)からサーバにリクエストが送られます
  • サーバはセッションIDを生成します
  • サーバはレスポンスにセッションIDを入れてクライアントに返します
  • クライアントはセッションIDをCookieに保存します
  • 以降、クライアントは全てのリクエストにセッションIDを入れて送ります
  • サーバはクライアントから送られたセッションIDとサーバに保存されたセッションIDを照合することによりユーザーを認証します
  • それ以降は上記5,6の手順でリクエストとレスポンスを繰り返します
  • セッションIDのタイムアウト(デフォルトでは30分間)または破棄(大抵はログアウト)するまでセッションは有効です

パスワードとは異なりセッションIDは1回限りの意味のない16進数なので盗まれたとしても実害は少ないです。

(ただし、そのセッションIDを悪用する攻撃もありますので注意が必要です。もちろん現在皆さんがお使いのブラウザでは簡単には盗まれないようになっていますのでひとまずは安心して大丈夫ですが)

図5.9

5.1. セッションを使ったログイン処理を実装する

セッションを使って次のログイン処理(再掲)を実現することにしましょう。

ユーザーはブラウザからIDとパスワードといった自分だけが知る情報を使ってログインする(対応済み)

②ログイン後はそのユーザーだけが見られるページに遷移できるようになる

③ログイン中は同じブラウザで他のタブを開いてもログインされたままである

④しかし、同じユーザーが別のブラウザで同じページにアクセスしても再度ログイン処理が必要である

⑤ログアウト処理をする、ブラウザを終了するまたは一定時間(大抵は30分間だが銀行のように5分程度と短い場合も)が経過するとログアウトする


セッションIDに紐づく形でセッション属性という記憶領域が用意されています。

セッション属性の概要は下図5.10のとおりです。

リクエスト属性に似ていますが、リクエスト属性は1つのリクエストだけがスコープでした。

しかし、セッション属性は複数のリクエストにまたがって有効なスコープです。

セッション属性は同一ブラウザであれば全てのページで有効なスコープであるともいえます。

したがってセッションを使ったWebアプリケーションをテストする際には注意が必要です。

例えばWebブラウザのタブを全て閉じてからテストしないと、セッション属性にデータが残っていて意図しない動作をすることがあります。

図5.10

<login_post.jsp>はコピーして <login_post2.jsp> として使用します。

飛び先だけ次の<Login3.java>に変更しておいてください。

  • <Login2.java> からの変更点はなんですか?
あなたの答え:

HttpSession session = request.getSession();

このように記述することでセッションを開始します

リクエスト属性はdoPostやdoGetメソッドの引数だったため宣言が不要でしたね。

対して、セッション属性はHttpSessionクラスの宣言が必要なのですね。

session.setAttribute("id", id);
session.setAttribute("message", "こんにちは" + id + "さん。");

このように記述することでセッション属性にオブジェクトを名前をつけて保存することができます。

この記述は全くリクエスト属性のときと同じと覚えましょう。


ログインしないと見られないページは次のmember_only2.jspに記述されています。

menber_only2.jspの17行目の以下の記述

${sessionScope.message}

は、セッション属性を指定する書き方です。

以前のリクエスト属性を指定する書き方と比べてみてください。

(15行目はメニューの読み込み処理で、次の章で解説します)

  • 2行目の記述は何のためでしたか?
あなたの答え:
  • 9~11行目の記述は何のためにあるか推測してみてください。
あなたの答え:
  • この <member_only2.jsp>を単体で実行したらどうなりますか?
あなたの答え:

今回、“jstl/core”という記述があります。

JSTLのコアタグというものを使用するための記述です。

この解説は次回にしたいと思いますが、ここでも簡単に解説すると、

<c:if>から</c:if>までが一つのif文になっています。

ちなみに、私達はJavaSEで学んだように{ }(波括弧)を一つのブロックとして考えることが半ば常識になっています。

しかし、このようにif文の始まりと終わりを明示するプログラム言語も多く存在します。

いまのうちに慣れておきましょう。

そして、「test=true」であれば、ifブロックの内容を実行し、「test=false」であれば何もしない、という処理になっています。

<c:redirect url="login_post3.jsp"/>というタグは文字通り指定のURLにリダイレクトするという意味です。

以下のvideo.jspはログイン後にメンバー専用でビデオが見られるページです。

セッション属性は有効範囲が広いため、新人エンジニアの中にはリクエスト属性ではなくセッション属性に何でも入れようとする人がいます。

しかし、それは良くないことです。

なぜなら、アプリケーションサーバのメモリを圧迫してしまうからです。

リクエスト属性は一つのリクエスト間だけ有効

セッション属性は複数のリクエスト間でも有効

ということは、リクエスト属性はすぐにサーバーのメモリから消えるが、セッション属性は残りがち、ということを理解して適切に使い分けましょう。

下図5.11はセッション属性とリクエスト属性の比較です。

セッション属性とリクエスト属性の比較
図5.11 セッション属性とリクエスト属性の比較

例題1

<video.jsp> にもログイン後でなければアクセスできないような記述を加えなさい。

実験4

セッション属性とリクエスト属性で同じ名前の属性を作ったとします。(例えばid)

ELでその名前を呼出したときにどちらの値が出力されるでしょうか?

あなたならどのような実験を計画して、どのように実施しますか?

実験結果のメモ:

余裕があればそれぞれの属性を特定して呼び出す方法についてインターネットで調べてみてください。

実験5

<member_only2.jsp>においてprefix="c"という記述がありました。

この"c"を他の文字、例えば"a"などに変更できると思いますか?

実験結果のメモ:

6. メニューをインクルードする

ここで、下図5.12のようにWebアプリケーションらしくログイン後の各ページにはメニューメニューがある状態にしたいと思います。

図5.12 メインメニューのあるアプリケーション

メニューとして以下のmenu.jspをご用意ください。

商品検索や買い物かごのリンク先は今はダミー(#)です。

また、7行目~11行目のスクリプトレットの記述はすべてブラウザバック対策のための記述です。

コメントにその詳細を記述しておきました。

ブラウザバックとは、Webブラウザの「戻る」操作で直前に開いていたページに遷移することです。

ログアウトしたにも関わらず、ブラウザバックでログイン中の画面に戻れるとすると、例えば、ネットカフェなどの共用のパソコンではセキュリティの問題があることは言うまでもありません。


ログインのリンク先のJSPはこれから以下のlogin_include_menu.jspを作ります。

  • 18行目では何をしていると推測されますか?
あなたの答え:

7. ログアウト処理の実装

次にログアウト処理を考えてみましょう。

私達はこれまでユーザーがログイン済みかどうかをセッション属性にあるidという名前のオブジェクトがnullで“ない”ということによって確かめてきました。

したがって、ログアウト処理はこのidというオブジェクトを削除してnullにすることによって実現できそうです。

以下のLogout.javaはセッションにあるidという名前の属性を削除して、ログイン画面にリダイレクト処理をするサーブレットです。

このプログラムを読んで質問に答えてください。

  • HttpSession session = request.getSession(); という記述は、なぜ必要なのでしたか?
あなたの答え:
  • session.removeAttribute("id");という記述では何をしていると推測されますか?
あなたの答え:

実験6

このとき removeAttributeメソッドの代わりにinvalidateメソッドを使うとどうなりますか?

実験結果のメモ:

この2つのメソッドはどのように使い分ければよいのでしょうか?

例題2

ログイン後のページ全てにmenu.jspを組み込みなさい。

また、ブラウザバック対策を盛り込みなさい。

※なお、開発中はブラウザバック対策を外した方が楽かもしれません。

例題3

上記の説明をできるだけ見ないようにして、0からログインの仕組みを作りなさい。

今回はログイン処理の実装方法を通じてセッションについて学びました。

Webページというものは本来1ページ、1ページが独立していて、ページをまたいでデータを引き継ぐ仕組みがありません。

このようにhttp通信のリクエストとレスポンスがそれぞれ独立していて他の http通信にデータを引き継がないことをステートレス【stateless】といいます

【state】= 状態 が【less】=(引き継げ)ないというわけですね。

本来http通信はステートレスであり、一対のリクエストとレスポンスで通信は終了します。

しかし、複数のリクエストとレスポンスを同じものとして扱うことができなければ、ログイン処理や買い物かごの処理は実現できません。

ページをまたぐごとにユーザーを忘れてしまったり、買物かごの中身が消えてしまったら使い物になりませんね。

そこで必要となるのがセッションです

Webアプリケーションは、本来ステートレスとして作られたhttp通信を使って擬似的にステートフルにしてオンライン商取引などに使っているわけです。

そのため、かなり無理をしている(?)感じは否めないかと思います。


次回は、JavaBeansを学んでいきます。

JavaBeansを使うことで今回のセッション属性にオブジェクトをまるごと入れて複数ページをまたいだ処理が可能になります。

例えば、買物かごのような仕組みも実現できるようになります。

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ ログイン処理を実現するにはセッションという仕組みを理解することが必要

□ セッション属性は同一ブラウザで開始してから破棄されるまで、同一ユーザー(ブラウザ)であれば全てのページで有効なスコープである

□ 画面遷移の方法にはフォワードとリダイレクトがある

□ フォワードはリクエスト属性を引き継ぐがリダイレクトは引き継がない

□ 新ステータスコードの400番台はクライアント側のエラー、500番台はサーバー側のエラー

□ クライアントはセッションIDをCookieに保存する

□ サーバはクライアントから送られたセッションIDとサーバに保存されたセッションIDを照合することによりユーザーを認証する

□ HttpSession session = request.getSession() と記述することでセッションを開始する

□ セッション属性へのオブジェクトの出し入れのメソッド名はリクエスト属性のときと同じsetAttributeである

□ http通信はステートレスなためページをまたいで処理を継続するにはセッションが必要である

JavaWebアプリケーション目次に戻る