【社内勉強会】Sessionの基礎(2017/09/28) 


# 0. はじめに


-------
## 前提知識
* HTTP（リクエスト、レスポンス、ヘッダ、ボディ）
* Servlet, JSP


-------
## 参考図書


-------
## 目次
1. HTTPはステートレスプロトコル
2. Cookie
3. セッションとは
4. Servletでセッションの作成

-------
## 環境構築
* Tomcat8(Servlet, JSP)
* Java8
* プロジェクト名: "SampleWeb"
* Firefox55

----------
# 1. HTTPはステートレスプロトコル

----------
## [復習] HTTP通信の流れ

![image](https://qiita-image-store.s3.amazonaws.com/0/37903/6732a3cc-d855-c163-c3a6-24659cafa40b.png)
※ [HTTP はステートレス](https://www.websec-room.com/2013/02/23/178) 引用

1. クライアントがサーバにリクエストを送る
2. サーバがリクエストを受け取る
3. サーバがクライアントにレスポンスを返す
4. クライアントがレスポンス(HTMLなど)を表示する


----------
## HTTPはステートレスプロトコル

* HTTPは1回のやりとり（リクエスト、レスポンス）で処理が完結しているので、「状態」を管理できない


* ステートレスとは「サーバがクライアントのアプリケーション状態を保存しない」制約のこと。
* ステートレスの逆は「ステートフル」

※ [HTTP はステートレス](https://www.websec-room.com/2013/02/23/178) 参考



----------
### ステートフルなやりとり（ハンバーガショップにて）

>店員「いらっしゃい」
客「ハンバーガーセット1つ」
店員「OK サイドメニューはどうする？」
客「ポテトMで」
店員「OK ドリンクはどうする？」
客「コーラMで」
店員「OK かしこまりました」

※ [「Webを支える技術」を読みました とRESTのまとめ](http://karur4n.hatenablog.com/entry/read-webtechbook) 引用

----------
### ステートレスなやりとり（ハンバーガショップにて）

>店員「いらっしゃい」
客「ハンバーガーセットで」
店員「OK サイドメニューはどうする？」
客「ハンバーガーセットのポテトMで」
店員「OK ドリンクはどうする？」
客「ハンバーガーセットのポテトMとコーラMで」
店員「OK かしこまりました」

※ [「Webを支える技術」を読みました とRESTのまとめ](http://karur4n.hatenablog.com/entry/read-webtechbook) 引用



----------
### ステートレス、ステートフルなやりとりの特徴

* ステートフルなやりとりは、
    - 簡潔
    - サーバがクライアントのそれまでの注文を覚えている

* ステートレスなやりとりは、
    - 冗長
    - クライアントは毎回すべての注文を繰り返している


※『Webを支える技術』P082 引用


----------
## [補足] FTPはステートフルなプロトコル

FTPは、ログインしてからログアウトするまで、そのクライアントがどのディレクトリにいるかといった、アプリケーション状態をサーバが管理する。

※『Webを支える技術』P082 参考



-----------
# 2. HTTP Cookie

-----------
## ステートレスプロトコルは不便

* ログイン状態など保持できない
    - ネットショッピングができない

⇒「Cookie」という仕組みで、疑似的に「ステートフル」を実現した


-----------
## Cookieとは？

Cookie は、ステートレスな HTTP プロトコルのためにステートフルな情報を記憶したもの。

Cookie は主に、以下の 3 つの用途で使用される。

* セッション管理 (ユーザーのログイン、ショッピングカート)
* 個人設定 (ユーザーの設定)
* トラッキング (ユーザーの行動を分析する)

※ [HTTP Cookie - MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies) 引用

-----------
## Cookieの具体的な流れ

1. サーバーはクライアントへ、Cookie を保存するよう指定する( `Set-Cookie`ヘッダ使用）
  
```txt:レスポンスヘッダ
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
...
```

2. クライアント（ブラウザ）はCookieを保存する。

3. クライアントはサーバにリクエストを送るときに、`Cookie`ヘッダでCookieを送る。

```txt:リクエストヘッダ
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
```

※ [HTTP Cookie - MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies) 参考

----------
## [Try] ServletでCookieを確認

Cookieを保存させるServletを作成

```java:Cookie.java
@WebServlet(urlPatterns = { "/Cookie" })
public class Cookie extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setHeader("Set-Cookie", "yummy_cookie=choco");
		PrintWriter out = response.getWriter();
		out.println("/Cookie");
	}
}
```


----------
### レスポンスヘッダの`Set-Cookie`を確認

1. http://localhost:8080/SampleWeb/Cookie にアクセス
2. レスポンスヘッダの`Set-Cookie`を確認
![image](https://qiita-image-store.s3.amazonaws.com/0/37903/125ea9ef-d94e-b1e9-972f-c810d9f45b23.png)

3. ブラウザでCookieを確認
![image](https://qiita-image-store.s3.amazonaws.com/0/37903/1fc8fdcc-8f30-c4db-214b-81fd19da16f1.png)


----------
### リクエストヘッダの`Cookie`を確認

1. プロジェクト配下のURLにアクセス（ http://localhost:8080/SampleWeb/Hello ）

![image](https://qiita-image-store.s3.amazonaws.com/0/37903/06bbdc64-3cd5-4abe-4565-47b485ca85a2.png)





----------
# 3. セッション


----------
##  Cookieのみでログイン状態を維持できそうだけど…

下記の情報をCookieに保存すれば、ログイン状態を維持できそう…

* ユーザID
* パスワード
* ユーザ名
* 住所
* 電話番号


----------
##  Cookieのみでログイン状態を維持したときの問題

* Cookieは確実に保存される訳ではない
    - シークレットモード
    - ユーザがブラウザの履歴を削除する
* 最大容量が4KBで小さい  
* デフォルト(secureを付与しなければ）は平文で送るため、**セキュリティに問題あり**

※ 『Real World HTTP』P40 参考


>Cookie の仕組み全体が本質的に安全ではないため、HTTP Cookie で機密情報を保存したり送信したりしてはいけません。

※ [HTTP Cookie - MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies) 引用

----------
## [補足] IPA試験対策：Cookieにsecure属性をつける

>https: のページで使用するCookieには発行時に secure属性をつける。
secure属性が付けられたCookieは、http:のコンテンツ宛には送られない。
http: のページにおいてもCookieを使用する必要がある場合は、Cookie の値を第三者が傍受し得ることを十分に考慮した上で secure属性のつかない Cookieを発行する。

※ [セッション対策 - セキュアプログラミング講座]( https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/304.html)  引用


----------
##  セッションとは？
本スライドでの「セッション」の意味：  
ログインしてからログアウトするまでの一連の操作


----------
##  先の問題を解決する対応案

* Cookieにはユーザを認識できるID（セッションID)のみを保存
* ユーザ名などの個人情報はサーバで管理

Cookieに個人情報は保存していないので、先の例よりセキュリティ的に良い（完全に問題が解決された訳ではない。後述のセッションハイジャック参照）


----------
##  [Try] Servletでセッションを作成

`request.getSession(true);`でセッションを作成する。

```java:CreateSession.java
@WebServlet(urlPatterns = { "/CreateSession" })
public class CreateSession extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//Session取得。セッションがなければセッション作成。
		HttpSession session = request.getSession(true);
		PrintWriter out = response.getWriter();
		out.println("CreateSession");
        //セッションIDを表示
		out.println("session_id=" + session.getId());
	}

}
```


----------
### セッションIDを確認

```txt:ブラウザ表示結果
CreateSession
session_id=5F4F3DF78E1BC8822CE970D9B3E3ECF7
```

![image](https://qiita-image-store.s3.amazonaws.com/0/37903/83910102-701e-ef14-202e-0f68950e3a79.png)

* ServletのセッションIDは、32文字のランダムな値（0-9A-F)
* Cookieに保存されたセッションIDのキーは、"JSESSIONID"


----------
## [Try] セッションハイジャックを確認

>セッションハイジャックは、利用者が Webアプリケーションにログインした際に発行されるセッション ID を、ネットワーク上で盗聴されたり、規則性から類推されることで、攻撃者が利用者になりすます攻撃です。

※ [セッション管理の不備と対策](https://www.websec-room.com/2013/03/09/497) 引用



----------
### ログイン用のサーブレットを作成

```java:Login.java
/** SessionにUser Nameを保存する */
@WebServlet(urlPatterns = { "/Login" })
public class Login extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");    
        //Sessionにユーザ名を設定
        HttpSession session = request.getSession(true);
        String userName = request.getParameter("userName");
        session.setAttribute("userName", userName);

        PrintWriter out = response.getWriter();
        out.println("Login Success");
    }

}
```


```html:LoginForm.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/SampleWeb/Login" method="POST">
    User Name <input type="text" name="userName">
    <input type="submit" value="Login">
</form>
</body>
</html>
```

```java:HelloUser.java
/** Sessionに保存したUser Nameを表示 */
@WebServlet("/HelloUser")
public class HelloUser extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// Sessionがなければnullを返す
		HttpSession session = request.getSession(false);

		PrintWriter out = response.getWriter();
		out.println("HelloUser");

		if (session == null) {
			out.println("Session Does Not Exist");
		} else {
			out.println("userName = " + session.getAttribute("userName"));
		}
	}
}
```

----------
### 一般ユーザAの動作

1. http://localhost:8080/SampleWeb/form.html にアクセス
2. User Nameに"Hoge"を入力して、Login   
⇒ SessionにUser Nameが保存される
3. http://localhost:8080/SampleWeb/HelloUser にアクセス  
⇒ User Name"Hoge"が表示される


----------
### 攻撃者Xの動作（セッションハイジャック）

1. ユーザA：ログインする
2. 攻撃者X：ユーザAのセッションIDをなんらかの手段で取得する(盗聴など）
3. 攻撃者X：Cookieに取得したセッションIDを保存する
4. 攻撃者X：http://localhost:8080/SampleWeb/HelloUser  
⇒ User Name "Hoge"が表示される



----------
## [補足] セッション固定化攻撃


セッションID固定化攻撃は、Webサイトが生成する正規のセッションIDを含むURLに利用者をアクセスさせ、攻撃者が用意したセッションIDを使用する通信を意図的に確立させる攻撃です。攻撃者はその後同じセッションIDでWebサイトにアクセスし、ログイン中のセッションを乗っ取ります。

たとえセッションIDに推測が困難な乱数を使用していたとしても、以下に挙げるWebサイトでは攻撃者に正規のセッションIDを取得され、セッション固定化攻撃を受ける可能性があります。

* ログイン画面(URLなど)にセッションIDを表示する仕様になっており、ログイン後も同じセッションIDを使用する
* クッキーを使用できない場合に、セッション情報をURLに埋め込むURLリライティング機能が有効になっている
* セッションIDが利用者ごとに固定されているなど容易に推測が可能である

※ [情報処理安全確保支援士 平成29年春期 午前Ⅱ 問5](http://www.sc-siken.com/kakomon/29_haru/am2_5.html) 引用





-----------
## [補足] Cookieに情報を格納するのは昔のやり方

ローカルストレージ用に考えられた新しい API を使うべき。

### Web storage API (localStorage, sessionStorage) 

![image](https://qiita-image-store.s3.amazonaws.com/0/37903/811841b2-c946-9824-8ee4-9d198bdd8e4d.png)
※ [Web Storage](http://www.htmq.com/webstorage/) 引用

```js
// sessionStorage にデータを保存する
sessionStorage.setItem("yummy", "choco");
// sessionStorage に保存したデータを取得する
var data = sessionStorage.getItem("yummy");
// sessionStorage に保存したデータを削除する
sessionStorage.removeItem("yummy");
```

### IndexedDB 

>IndexedDB は、ユーザのブラウザ内にデータを永続的に保存する手段です。ネットワークの状態にかかわらず高度な問い合わせ機能を持つ Web アプリケーションを作成できますので、オンラインとオフラインの両方で動作するアプリケーションになります。

※ [IndexedDB を使用する](https://developer.mozilla.org/ja/docs/Web/API/IndexedDB_API/Using_IndexedDB) 引用


-----------
## [補足] HTTPクッキーの命名例

* JSESSIONID (JSP)
* PHPSESSID (PHP)
* CGISESSID (CGI)
* ASPSESSIONID (ASP) 

名前からプログラム言語が推測できる（確定ではない）。



-------
## [補足] セッションタイムアウトを設定する方法

```xml:web.xml
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
```

※ [web.xml デプロイメント記述子の要素](http://otndnld.oracle.co.jp/document/products/wls/docs103/webapp/web_xml.html#wp1017275) 参考

-------
## [補足] JSPにアクセスするとセッションが作成される。

jspにアクセスすると、Cookieに"JSESSIONID"が保存される。
これは、JSPからコンパイルされたServletの`pageContext.getSession()`によるものだと考えられる（未確認）。

```java:hello_jsp.java
public void _jspService(/* ... */) {
  //...
    final javax.servlet.jsp.PageContext pageContext;
  //...

    try {
      response.setContentType("text/html; charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      //Sesssion 取得
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;
      ....
      }
}
```