【Goで学ぶコンピューターサイエンス】SessionとCookie

## Session

Sessionとは、ユーザーごとの状態をサーバー側で保持する仕組みです。Webは本来「ステートレス」（通信が終わると状態を保持しない）なプロトコルであるため、ユーザーがログイン中かどうかなどの情報を保持するためにSessionが使われます。

### **Sessionの仕組み**
1. ユーザーがログインすると、サーバー側でSessionを生成し、SessionIDを発行します
2. SessionIDはCookieやURLパラメータを通じてクライアントに渡されます
3. 次回以降のリクエストで、SessionIDが送信されることで、サーバー側でユーザーを識別できます

### **Sessionの特徴**
- **サーバー側にデータを保存**
  - Sessionデータはサーバー上に保持されます（例: メモリやデータベース）
- **クライアントの負担が少ない**
  - クライアント側ではSessionIDだけを保持
- **セキュリティが高い**
  - Sessionデータはサーバー側に保存されるため、直接改ざんされにくい

### **Sessionの欠点**
- サーバーのリソースを消費する
- ロードバランサーを使用する場合、Session管理が複雑になる（Sessionの共有が必要）

## Cookie

Cookieは、Webブラウザにデータを保存する仕組みです。ユーザー識別や状態管理のために使われ、SessionIDをクライアントに渡す手段としても活用されます。

### **Cookieの仕組み**
1. サーバーがクライアントにレスポンスを返す際に、HTTPヘッダーにCookieを付与します
2. クライアント（ブラウザ）はCookieを保存します
3. 次回以降のリクエストで、保存したCookieをHTTPヘッダーに含めてサーバーに送信します

### **Cookieの特徴**
- **クライアント側にデータを保存**
  - 状態や設定情報をブラウザに保持します
- **簡単に使用可能**
  - HTTPリクエストとレスポンスに付加するだけで利用できます

### **Cookieの用途**
- SessionIDの保存
- ユーザー設定（テーマ、言語など）の保持
- トラッキング情報（広告や分析ツールで利用）

### **Cookieの制限**
- 保存容量はブラウザごとに制限されている（通常4KB程度）
- 改ざんや盗聴のリスクがあるため、暗号化やセキュアな通信（HTTPS）が推奨されます

## SessionとCookieの違い

| **項目**             | **Session**                      | **Cookie**                    |
| -------------------- | -------------------------------- | ----------------------------- |
| **データの保存場所** | サーバー側                       | クライアント側                |
| **データ量の制限**   | サーバーのリソースに依存         | 通常4KB程度                   |
| **セキュリティ**     | 比較的高い（サーバー管理）       | 改ざんや盗聴のリスクがある    |
| **有効期限**         | 通常ブラウザを閉じると終了       | 有効期限を設定可能            |
| **使用例**           | ログイン情報、ショッピングカート | ユーザー設定、SessionIDの保存 |

## Goサンプルコード
```go
package main

import (
	"fmt"
	"net/http"

	"github.com/gorilla/sessions"
)

// セッションストアの設定（Cookieストアを使用）
var store = sessions.NewCookieStore([]byte("your-secret-key"))

func loginHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
		return
	}

	// フォームデータからユーザー名を取得
	username := r.FormValue("username")
	if username == "" {
		http.Error(w, "Username is required", http.StatusBadRequest)
		return
	}

	// セッションを取得（新規作成含む）
	session, _ := store.Get(r, "session-name")

	// セッションにユーザー情報を保存
	session.Values["username"] = username
	session.Save(r, w) // セッションを保存

	fmt.Fprintf(w, "Logged in as %s", username)
}

func profileHandler(w http.ResponseWriter, r *http.Request) {
	// セッションを取得
	session, _ := store.Get(r, "session-name")

	// セッションからユーザー名を取得
	username, ok := session.Values["username"].(string)
	if !ok || username == "" {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	fmt.Fprintf(w, "Welcome, %s!", username)
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
	// セッションを取得
	session, _ := store.Get(r, "session-name")

	// セッションデータをクリア
	session.Options.MaxAge = -1 // クッキーの有効期限を無効にする
	session.Save(r, w)          // セッションを保存

	fmt.Fprintln(w, "Logged out")
}

func main() {
	http.HandleFunc("/login", loginHandler)
	http.HandleFunc("/profile", profileHandler)
	http.HandleFunc("/logout", logoutHandler)

	fmt.Println("Server started at :8080")
	http.ListenAndServe(":8080", nil)
}
```

/loginにPOST
```
Logged in as hogehoge
```

/profileにGET
```
Welcome, hogehoge!
```

/logoutにGET
```
Logged out
```

## SessionとCookieのセキュリティ対策

### **HTTPSを使用する**
- 暗号化された通信を利用し、SessionIDやCookieが盗聴されるリスクを軽減します

### **SessionIDの保護**
- Sessionハイジャックを防ぐため、SessionIDは予測困難なものにする
- 定期的にSessionIDを更新（Session固定攻撃の防止）

### **CookieのHttpOnly属性**
- JavaScriptからアクセスできないようにすることで、XSS攻撃から保護します

### **CookieのSecure属性**
- HTTPS通信時のみCookieを送信

### **CSRFトークンの活用**
- Sessionを利用するリクエストにはCSRFトークンを含める