Spring Boot のSessionが効かない

# 問題内容
画面で入力された情報をSessionに入れる→他の画面を経由して再度同じ画面を表示するときにSessionに入れた情報を表示する。
上記仕様でしたが、とある環境ではうまくいかないという事象でした。

もちろんローカルや他のテスト環境ではうまくいっていました。

# 原因
他のテスト環境ではうまくいっていたため環境起因なのは明白でした。
その環境についてこちらは全く情報を持っていなかったので、最初は悩みました。
たぶんロードバランサーだろうな、と感じていましたが見事ビンゴでした。

問題が発生した環境だけWebサーバーが複数台持ってる環境でした。
そしてロードバランサーの制御で、クライアントからアクセスがあったサーバーは、そのクライアントに対しての応答を強制する（スティッキーセッションだったか）をサーバー側で設定すれば終わりなのですが、ダメと言い張られてしまいました。

# じゃあどうするか
CookieにSessionで持たせたかった情報を設定する。
※重要な情報がある場合は考慮が必要です

# サンプル実装
```sample.html
<form action="/submit" method="post">
  <label for="username">ユーザー名:</label>
  <input type="text" id="username" name="username">
  <button type="submit">送信</button>
</form>
```
```java:sample.java
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.ui.Model;

@Controller
public class FormController {

    @PostMapping("/submit")
    public String submitForm(@RequestParam String username, HttpServletResponse response) {
        // Cookie作成
        Cookie cookie = new Cookie("username", username);
        cookie.setMaxAge(60 * 60); // 有効期限（1時間）
        cookie.setPath("/"); // パス指定
        response.addCookie(cookie);

        return "redirect:/show";
    }

    @GetMapping("/show")
    public String showPage(Model model, @jakarta.servlet.http.CookieValue(value = "username", defaultValue = "ゲスト") String username) {
        model.addAttribute("username", username);
        return "show"; // show.htmlに渡す
    }
}
```
```sample.html
<!-- show.html -->
<p>ようこそ、<span th:text="${username}"></span>さん！</p>
```
# 応用：オブジェクト形式で持ち回る
フォームなどで受け取った複数の値（例：ユーザー名、メールなど）を
→ JSONに変換
→ URLエンコード
→ Cookie に保存

1\. Javaオブジェクト → JSON文字列 → URLエンコード
```java:sample.java
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

...

@PostMapping("/set-cookie-json")
public String setJsonCookie(HttpServletResponse response) throws Exception {
    // ① JSONにしたいデータを作成
    Map<String, String> data = new HashMap<>();
    data.put("username", "tanaka");
    data.put("email", "tanaka@example.com");

    // ② ObjectMapperでJSONに変換
    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(data);

    // ③ URLエンコード
    String encodedJson = URLEncoder.encode(json, StandardCharsets.UTF_8);

    // ④ Cookieに設定
    Cookie cookie = new Cookie("userdata", encodedJson);
    cookie.setPath("/");
    cookie.setHttpOnly(true);
    cookie.setMaxAge(60 * 60); // 1時間
    response.addCookie(cookie);

    return "Cookie set with JSON!";
}
```
 Cookieに入るデータ（例）
```
userdata=%7B%22username%22%3A%22tanaka%22%2C%22email%22%3A%22tanaka%40example.com%22%7D
```
2\. Cookieから元のJSONを読み取る方法（復号）@CookieValueで取得
```java:sample.java
import java.net.URLDecoder;

@GetMapping("/read-cookie-json")
public String readJsonCookie(@CookieValue(value = "userdata", defaultValue = "") String encodedJson) throws Exception {
    if (encodedJson.isEmpty()) return "No userdata cookie found.";

    // ① URLデコード
    String json = URLDecoder.decode(encodedJson, StandardCharsets.UTF_8);

    // ② JSON → Mapに復元
    ObjectMapper objectMapper = new ObjectMapper();
    Map<String, String> data = objectMapper.readValue(json, new TypeReference<Map<String, String>>() {});

    return "こんにちは " + data.get("username") + " さん！メール: " + data.get("email");
}
```
2\. Cookieから元のJSONを読み取る方法（復号）HttpServletRequestで取得
```java:sample.java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@RestController
public class CookieReadController {

    @GetMapping("/read-cookie-json")
    public String readCookieJson(HttpServletRequest request) throws Exception {
        Cookie[] cookies = request.getCookies();

        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("userdata".equals(cookie.getName())) {
                    // ① URLデコード
                    String decodedJson = URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8);

                    // ② JSON → Mapに復元
                    ObjectMapper objectMapper = new ObjectMapper();
                    Map<String, String> data = objectMapper.readValue(decodedJson, new TypeReference<Map<String, String>>() {});

                    // ③ 好きに使える
                    return "こんにちは " + data.get("username") + " さん！メール: " + data.get("email");
                }
            }
        }

        return "userdata クッキーが見つかりません。";
    }
}
```

個人的にはCookieから元のJSONを読み取る方法（復号）HttpServletRequestで取得
する方法が汎用的かな、と思うのでこちらが好きです。