サーブレットでセッション管理を行う

# はじめに
こんばんわ、きりです。
本記事は[Nablarchを使ってみよう](https://qiita.com/kirin1218/items/242ee0f174f1cb12ef17)のサブ記事として作成しております。

今回はセッション管理について整理します。
本記事ではセッション管理を利用して、（超）簡易的なログインの仕組みを実装してみようと思います。

本記事以外のコンテンツは[こちら](https://qiita.com/kirin1218/items/242ee0f174f1cb12ef17#nablarch%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E8%A8%98%E4%BA%8B%E4%B8%80%E8%A6%A7)から閲覧可能です。

なるべく、初心者目線で作成するつもりですが、分かりづらい部分ありましたら、コメント頂きたいです。

# セションとは
セッションとは、クライアントがブラウザなどのアプリケーションでWebサーバーとの接続を開始し、複数回のリクエストとレスポンスのやり取りを行った後、接続を終了するまでの一連の流れを指します。

[セッション管理](https://www.wakhok.ac.jp/~tomoharu/web2004/text/index_c9.html)
[典型的な HTTP セッション](https://developer.mozilla.org/ja/docs/Web/HTTP/Session)
[HTTPセッション](hhttps://software.fujitsu.com/jp/manual/manualfiles/m180006/b1ws1308/04z201/b1308-00-02-06-02.html)

# セッション管理とは
セッション管理とは、１つのセッションにおける、クライアントとサーバー間でその情報を保持し、アクセス制御を一つの集合体として管理する仕組みを指します。

セッション管理を行うことで、セッションの中で行われた操作情報や会員サイトなどにおけるログイン情報などを保持することが可能となり、通信負荷の軽減や処理の高速化を行える可能性があります。

[セッション管理](https://www.ipa.go.jp/security/awareness/administrator/secure-web/chap6/6_session-1.html)

#　セッションの作成と存在確認
セッションの作成と存在確認には　[getSessionメソッド](https://spring.pleiades.io/specifications/platform/8/apidocs/javax/servlet/http/httpservletrequest#getSession-boolean-)　を利用します。

引数にはtrueまたはfalseが指定可能で、falseの場合はセッション存在の有無を確認し、セッション情報またはnullを返却します。
trueを指定した場合は、セッションがまだ作成されていなければ新たにセッションを作成し、セッション情報を返却します。既に存在する場合は既存のセッション情報を返却します。
なお、セッションにはセッションIDと呼ばれるセッション毎の一意な値が割り振られます。

getSessionメソッドはHttpServletRequestクラスのメソッドのため、サーブレットの引数として渡されたrequestオブジェクトより呼び出しが可能です。

# セッションの破棄
getSessionメソッドより取得したHttpSession側のオブジェクトからinvalidate関数を呼び出すことでセッションが破棄されます。

[invalidate](https://github.megascus.dev/servlet-spec/docs/apidocs/javax/servlet/http/HttpSession.html#invalidate--)

# セッションへのオブジェクト追加と参照
セッションに情報を登録することで、同じセッションであれば別のリクエストが呼び出された場合も値が参照可能です。
セッションへのオブジェクト追加はHttpSessionクラスのsetAttibutes,値を参照するときはgetAttributesを使用します。
HttpSessionのオブジェクトはサーブレットの引数のrequestよりgetSessionすることで取得できます。

[setAttributes](https://github.megascus.dev/servlet-spec/docs/apidocs/javax/servlet/http/HttpSession.html#setAttribute-java.lang.String-java.lang.Object-)
[getAttributes](https://github.megascus.dev/servlet-spec/docs/apidocs/javax/servlet/http/HttpSession.html#getAttribute-java.lang.String-)

# セッション管理を利用した簡易ログインシステム
## 概要
* ログイン画面を作成し、名前の入力欄とログインボタンだけ配置します
* ログイン画面でログインボタンを押下すると、セッションを再作成し名前をセッションへ名前属性として記録し、ホーム画面へ遷移します
* ホーム画面ではセッションから名前属性を取得し画面へ表示します
* ホーム画面でログアウトボタンを押下すると、セッションを破棄し、ログイン画面へ遷移します
* web.xmｌを編集し、ルートアクセス時にHomeサーブレットへ遷移するように設定します

## ファイルの作成
* サーブレット

|パッケージ名|クラス名|作成されるパス|
|---|---|---|
|com.example|Home|src/main/java/com/example/Home.java|
|com.example.session|Login|src/main/java/com/example/session/Login.java|
|com.example.session|Logout|src/main/java/com/example/session/Logout.java|

※サーブレットの作成方法は[こちら](https://qiita.com/kirin1218/items/db1ba8af6c323c925919#%E3%82%B5%E3%83%BC%E3%83%96%E3%83%AC%E3%83%83%E3%83%88%E8%A8%AD%E5%AE%9A)を御覧ください
* JSP

|ファイル名|作成されるパス|
|---|---|
|Home.jsp|src/main/webapp/View/Home/Home.jsp|
|Login.jsp|src/main/webapp/View/Session/Session.jsp|

※JSPの作成方法は[こちら](https://qiita.com/kirin1218/items/9002dab5a30536559469#jsp%E3%81%AE%E4%BD%9C%E6%88%90)を御覧ください

# 各ファイルの実装
## Home.jsp
```Home.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
  // Servletのデータ受け取り
  request.setCharacterEncoding("UTF8");
  String strName = (String) request.getAttribute("name");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ホーム画面</title>
</head>
<body>
  <%=strName %>さん、こんにちわ
  <br>
  <br>
  <form action="<%= request.getContextPath() %>/Logout" method="post">
    <input type="submit" value="ログアウトする">
  </form>
</body>
</body>
</html>
```
* リクエストからname属性を抽出し画面に表示します
* ログアウトボタンを作成し、Logoutサーブレットを呼び出します

## Login.jsp
```Login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ログイン画面</title>
</head>
<body>
  <form action="<%= request.getContextPath() %>/Login" method="post">
    <h2>ログイン名</h2>
    <input type="text" name="name" />
    <input type="submit" value="ログイン">
  </form>
</body>
</html>
```
* 入力欄を作成し、ログインボタン押下時にLoginサーブレットへ遷移します

## Login.java
```Login.java
package com.example.session;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class Login
 */
@WebServlet("/Login")
public class Login extends HttpServlet {
  private static final long serialVersionUID = 1L;

  /**
   * @see HttpServlet#HttpServlet()
   */
  public Login() {
    super();
    // TODO Auto-generated constructor stub
  }

  /**
   * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    request.getRequestDispatcher("View/Session/Login.jsp").forward(request, response);
  }

  /**
   * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    // ログイン名をリクエストから取得する
    HttpSession session = request.getSession(false);
    // 既にセッションが存在する場合は一度破棄する
    if (session != null) {
      log("セッション破棄 セッションID=[" + session.getId() + "]");
      session.invalidate();
    }

    try {
      // セッションを新規で作成する
      session = request.getSession(true);
      log("セッション作成 セッションID=[" + session.getId() + "]");

      request.setCharacterEncoding("UTF-8");
      // 各フォームデータを取得する
      String name = request.getParameter("name");

      // ログイン名をセッションで保持する
      session.setAttribute("name", name);

      // ログイン後ホーム画面へ自動遷移する
      request.getRequestDispatcher("Home").forward(request, response);
    } catch (Exception e) {
      log("セッション作成 失敗");
      // 例外によりセッションの作成に失敗
      e.printStackTrace();
    }
  }

}
```
* GET時はLogin.jspを表示します
* Login.jspからポストされた場合はセッションを破棄 -> セッションの作成し、リクエストから取得した名前情報をセッションを保持します
* 処理完了後、Homeサーブレットへフォーワードします 

## Home.java
```Home.java
package com.example;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class Home
 */
@WebServlet("/Home")
public class Home extends HttpServlet {
  private static final long serialVersionUID = 1L;

  /**
   * @see HttpServlet#HttpServlet()
   */
  public Home() {
    super();
    // TODO Auto-generated constructor stub
  }

  /**
   * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    request.setCharacterEncoding("UTF8");

    String name = null;
    HttpSession session = request.getSession(false);
    // name属性が存在しない場合はセッション情報なしとして取り扱う
    if (session != null && (name = (String) session.getAttribute("name")) == null) {
      session = null;
    }

    // セッション情報がないもしくは期待した状態でなければログイン画面に遷移する
    if (session == null) {
      request.getRequestDispatcher("Login").forward(request, response);
      return;
    }

    // ホーム画面に名前を表示する
    request.setAttribute("name", name);

    // ホーム画面を表示する
    request.getRequestDispatcher("View/Home/Home.jsp").forward(request, response);
  }

  /**
   * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    // TODO Auto-generated method stub
    doGet(request, response);
  }

}
```
* セッションの有無を確認し存在しない場合はLoginサーブレットへフォワードします
* セッションが存在する場合も名前属性が存在しない場合は、ログインを経由していないためLoginサーブレットへフォワードします
* セッションが存在し名前属性が取得できた場合は、リクエストに設定しHomeサーブレットへフォワードします

## Logout.java
```Logout.java
package com.example.session;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class Logout
 */
@WebServlet("/Logout")
public class Logout extends HttpServlet {
  private static final long serialVersionUID = 1L;

  /**
   * @see HttpServlet#HttpServlet()
   */
  public Logout() {
    super();
    // TODO Auto-generated constructor stub
  }

  /**
   * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    // ログアウト時はセッションを破棄する
    HttpSession session = request.getSession(false);
    // 既にセッションが存在する場合は一度破棄する
    if (session != null) {
      log("セッション破棄 セッションID=[" + session.getId() + "]");
      session.invalidate();
    }

    // セッション破棄後にログイン画面に遷移する
    request.getRequestDispatcher("View/Session/Login.jsp").forward(request, response);
  }
}
```
* セッションを破棄し、ログイン画面へフォワードします

## ルート画面の設定
```web.xml
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>Home</servlet-name>
    <servlet-class>com.example.Home</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Home</servlet-name>
    <url-pattern></url-pattern>
  </servlet-mapping>
</web-app>
```

これまでweb.xmlについては触れてきませんでしたが、サーブレットの設定は主にweb.xmlで管理しています。
今回はservlet-mappingタグにて、ルートアクセス時(<url-pattern></url-pattern>)にHomeサーブレットへ遷移するように設定しました。
Homeサーブレットの設定はservletタグにて行っています。

LoginサーブレットやLogoutサーブレットは各ファイルにて@WebServletを指定しているので、web.xmlの設定なくともサーブレットとして認識されているようです。

# 動作確認
## ルートにアクセス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803458/5e5161bf-23b4-6685-a46a-65374303b732.png)

未ログインのため、ログイン画面に遷移します

ブラウザの開発ツールからレスポンスヘッダーを確認すると、JSESSIONIDというのがサーブレット側で作成されたセッションIDです。
自動でセッションが生成されたようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803458/04b03bf3-c1fa-e543-493d-9c458429b598.png)

## ホーム画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803458/93de1742-c20e-cfa6-2ec5-d371f96e7312.png)

名前を入力後、ログインボタンを押下します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803458/9ec2ede1-cfd0-bd64-dcff-78803c93f06f.png)

ログイン時に指定した名前が画面に表示されます

リクエスト時とレスポンスのJSESSIONIDが異なることからセッションIDが新たに作成されたことがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803458/afa33ffa-622a-416d-3c9e-baab4e2e7cd1.png)

## ログアウト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803458/09d26555-f782-ba5a-0a91-698ad9c13b33.png)

ログアウト後、ログイン画面に遷移します。

# 最後に
今回はセッション管理について整理しました。
セッションを利用することで、サーバ側でクライアント情報を保持することができるため、実装の幅が広がるかと思います。
参考になれば嬉しいです。

他の記事については[こちら](https://qiita.com/kirin1218/items/242ee0f174f1cb12ef17#nablarch%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E8%A8%98%E4%BA%8B%E4%B8%80%E8%A6%A7)から御覧ください。

# 参考リンク
* [セッション管理](https://www.wakhok.ac.jp/~tomoharu/web2004/text/index_c9.html)
* [典型的な HTTP セッション](https://developer.mozilla.org/ja/docs/Web/HTTP/Session)
* [HTTPセッション](hhttps://software.fujitsu.com/jp/manual/manualfiles/m180006/b1ws1308/04z201/b1308-00-02-06-02.html)
* [セッション管理](https://www.ipa.go.jp/security/awareness/administrator/secure-web/chap6/6_session-1.html)