2.8K Views
July 25, 25
スライド概要
Java、Spring、IntelliJ IDEA
入門Spring Session JavaDo 2025年7月25日 多田真敏 1
このセッションについて ◆Webアプリではセッションの扱いが非常に重要になります。 ◆このセッションでは、複数サーバーでセッション情報を 共有するためのライブラリ「Spring Session」について、 基礎から解説します。 2
必要な前提知識 ◆このセッションを理解するには、以下の前提知識が必要です ◆シリアライズ ◆サーブレット・サーブレットフィルター ◆Spring DI・Spring MVC・Spring Securityの利用経験 3
前提知識を学べる資料 https://zenn.dev/masatoshi_t ada/articles/e6eaea9be96ee7 https://zenn.dev/masatoshi_t ada/articles/ab53b6de8be77d https://gihyo.jp/book/2023/9 78-4-297-13613-0 4
自己紹介 ◆多田真敏(@suke_masa) ◆JJUG・JSUGスタッフ ◆カード会社で 社内システムの内製化+AWS化 ◆OSSドキュメントの和訳 ◆Thymeleaf・Resilience4j 5
対象バージョン ◆JDK 21 ◆Spring Session 3.5 バージョンが異なる場合、 この資料の説明が正しくない 可能性があります ◆PostgreSQL 16 ◆Spring Boot 3.5 ◆Spring Boot利用前提で解説しています ◆Spring Bootを利用していない場合、 この資料では解説していない設定を追加する必要があります 6
目次 ① そもそもセッションって何? ② セッションの問題点とSpring Session ③ Spring Sessionの使い方 ④ Spring Sessionの仕組み 7
目次 ① そもそもセッションって何? ② セッションの問題点とSpring Session ③ Spring Sessionの使い方 ④ Spring Sessionの仕組み 8
クライアントをどうやって識別する? Aさん リクエスト レスポンス Bさん サーバー サーバーはどうやって 「Aさんからのリクエスト」 と識別する? リクエスト レスポンス 9
答:Cookie ◆Cookieは、以下の特徴を持つ特殊なHTTPヘッダー ◆キーと値のペアを複数持つ ◆発行元サーバーからのSet-Cookieレスポンスヘッダーで 指定されたペアをブラウザが記憶する ◆ブラウザは以降、発行元サーバーにリクエストする際、 Cookieリクエストヘッダーに記憶したペアを含める ◆各ペアには個別に有効期限などを設定できる 10
HttpSessionとは ◆jakarta.servlet.http.HttpSessionインタフェース ◆実装クラスはTomcatなどが持っている ◆クライアントごとにHttpSessionインスタンス(以下セッション) が 作られる = セッションはサーバーのメモリ上に作られる ◆各セッションごとに一意なセッションIDを持つ ◆予測不能になるように、ランダム値になっている ◆Spring MVCのModel同様、値を保持できる ◆値の保持期限は、セッション破棄またはタイムアウト(デフォルト30分間) 11
セッション生成の流れ ①初回アクセス Aさん ③ログイン画面をレスポンス Set-Cookie: jsessionid=0001 ④ログイン Cookie: jsessionid=0001 ⑥ログイン画面をレスポンス Set-Cookie: jsessionid=0002 サーバー ②セッション生成 (ID=0001) Aさんのセッション ID=0001 → 0002 ⑤セッションIDを 0002に変更 (セッション固定化攻撃対策) ⑦以降のリクエスト Cookie: jsessionid=0002 ※本来では、セッションIDはもっと複雑なランダム値になります 12
Spring Securityとセッション ◆Spring Securityでは、 ユーザー情報を保持するSecurityContextをセッションに 保存している Aさんのセッション AさんのSecurityContext ユーザー名、パスワードなど 13
目次 ① そもそもセッションって何? ② セッションの問題点とSpring Session ③ Spring Sessionの使い方 ④ Spring Sessionの仕組み 14
セッションの問題点 ◆一言で言うと「セッションはサーバーのメモリ上に作られる」 ことが原因 15
問題点① 負荷分散でうまくいかない サーバー1 Aさん ①リクエス ト ロード バランサー Aさんのセッション ID=0001 ②レスポン ス サーバー2 ③リクエス ト セッションが 無い 16
問題点② アプリ停止時に消える ◆再起動やデプロイなどでアプリを停止する → メモリに保持されていたセッションが消える → 起動後もセッションは復活しない サーバー1 Aさんのセッション ID=0001 17
解決策① スティッキーセッション ◆JSESSIONID Cookieの値により、 負荷分散先のサーバーを固定するようロードバランサーを設定 ◆問題①は解決 Aさん 、問題②は未解決 ①リクエスト JSESSIONID =0001 ②リクエスト JSESSIONID =0001 サーバー1 Aさんのセッション ID=0001 ロード バランサー サーバー2 18
解決策② セッションをシリアライズ ◆セッションをシリアライズしてファイル化 → サーバー内のどこかに保存 ◆問題②は解決 。しかしコンテナの場合は、アプリを停止すると コンテナごと削除されるため、ファイルも消えてしまう ◆問題①は未解決 サーバー1 Aさんのセッション ID=0001 シリア ライズ ファイル 19
解決策③ セッションをシリアライズして 外部ストレージに保存 ◆問題①は解決 、問題②も解決 ◆ただし外部ストレージアクセスの分だけ、レスポンスが遅くなる ◆一般的にアクセスの速さは メモリ >>> SSD >>> ネットワーク Aさん ロード バランサー サーバー1 外部ストレージ サーバー2 Aさんの セッション ID=0001 20
そこでSpring Session! ◆解決策③を実現するライブラリ ◆外部ストレージとしてはRDB・Redis・Hazelcast・MongoDB を選択可能 Aさん サーバー1 ロード バランサー 外部ストレージ Spring Session サーバー2 Spring Session Aさんの セッション ID=0001 21
外部ストレージの選び方 ◆同時アクセス数が少ない場合は、 業務で利用しているRDBで十分 ◆同時アクセス数が多い場合は、 RDBを別インスタンスにしたり、Redis等にする 22
[参考] 他にも解決策や考慮点はいろいろ ◆詳細は伊藤さんの資料を参照してください https://speakerdeck.com/chiroito/http-session-architecture-pattern 23
目次 ① そもそもセッションって何? ② セッションの問題点とSpring Session ③ Spring Sessionの使い方 ④ Spring Sessionの仕組み 24
Spring Session JDBC ◆外部ストレージとしてRDBを利用する ◆同時アクセス数が少ない場合はおすすめ これを追加するだけ! Java Configを 作成する必要ナシ! dependencies { implementation("org.springframework.session:spring-session-jdbc") implementation("org.springframework.boot:spring-boot-starter-jdbc") implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-web") runtimeOnly("org.postgresql:postgresql") ... } 25
セッション保存用テーブルの作成 ◆spring-session-jdbc.jar内のSQLファイルの内容を実行 26
テーブル説明 ◆SPRING_SESSION ◆セッションIDやセッション破棄日時などが保存されている ◆SPRING_SESSION_ATTRIBUTES ◆セッションに格納された値がシリアライズされて保存されている 27
主なapplication.propertiesの設定 # Spring Session JDBCが利用するテーブル名 spring.session.jdbc.table-name=SPRING_SESSION # DelegatingFilterProxyの順番(後述、デフォルト値はInteger.MIN_VALUE+50) spring.session.servlet.filter-order=-2147483598 # 起動時にCREATE TABLE分を実行するか否か(本番環境はneverにすべし) spring.session.jdbc.initialize-schema=never # RDBから無効なセッションを自動削除する間隔をcron形式で指定 spring.session.jdbc.cleanup-cron=0 * * * * * # セッション情報をRDBに書き込むタイミング(後述) spring.session.jdbc.flush-mode=on-save 28
デモ localhost:8080 ①こちらに ログインしたら Spring Session localhost:8081 ②こちらも ログイン済みになる Postgre SQL Spring Session サンプルアプリ https://github.com/MasatoshiTada/spring-session-sample 29
目次 ① そもそもセッションって何? ② セッションの問題点とSpring Session ③ Spring Sessionの使い方 ④ Spring Sessionの仕組み 30
重要なクラス・インタフェース ◆SessionRepositoryインタフェース ◆セッション生成・値の保存などを行う ◆JdbcIndexedSessionRepositoryクラス ◆上記インタフェースの実装クラス。DBに値を保存する ◆SessionRepositoryRequestWrapperクラス ◆HttpServletRequest実装クラス ◆Spring Session用のHttpSession実装を返すようになっている ◆SessionRepositoryFilterクラス ◆SessionRepositoryRequestWrapperを設定する 31
処理の流れ その他の Filter SessionRepositoryRequestWrapper が使われる Delegating FilterProxy Session Repository Filter SessionRepository RequestWrapper を設定 その他の Filter Dispatcher Servlet Controller 32
SessionRepositoryFilterのソース(一部) public class SessionRepositoryFilter extends OncePerRequestFilter { @Override protected void doFilterInternal( HttpServletRequest req, HttpServletResponse resp, FilterChain chain) { var wrappedReq = new SessionRepositoryRequestWrapper(req, resp); var wrappedResp = new SessionRepositoryResponseWrapper(wrappedReq, resp); try { // SessionRepositoryRequestWrapperを設定 // 以降のフィルターやサーブレットでは、これがHttpServletRequest実装として使われる chain.doFilter(wrappedReq, wrappedResp); } finally { // レスポンスを返し終わる際に、セッション情報をDBに書き込む wrappedReq.commitSession(); } } } 33
セッション情報をいつDBに書き込む? ◆FlushModeで決まっている ◆application.propertiesのspring.session.jdbc.flush-mode で設定可能 ① ON_SAVE(デフォルト) ◆setAttribute()実行時は、指定された値は内部でキャッシュする ◆レスポンスを返し終わる際にのみ、キャッシュした値をDBに書き込 む ② IMMEDIATE ◆setAttribute()が実行されるごとにDBに書き込む 34
JdbcIndexedSessionRepository ◆外部ストレージとしてRDBを使うSessionRepository実装 ◆RDBアクセスにはJdbcTemplateを利用 ◆セッションの生成・保存・削除などのメソッドがある ◆セッション削除メソッドは、 ThreadPoolTaskSchedulerで 定期実行されている(デフォルトでは1分ごと) 35
JdbcIndexedSessionRepository
のソース(一部)
public class JdbcIndexedSessionRepository implements SessionRepository {
@Override
public JdbcSession findById(String id) {
List<JdbcSession> sessions = jdbcTemplate.query(
"SELECT ... FROM ...", (ps) -> ps.setString(1, id), this.extractor);
if (sessions.isEmpty()) {
return null;
}
return sessions.get(0);
}
セッション検索
}
メソッド
36
Spring Security連携での注意点 その他の Filter Spring Sessionの Delegating FilterProxy こちらが先に実行されるようにする (デフォルトでそうなっている) その他の Filter Spring Securityの Delegating FilterProxy こちらが後 37
DB障害時の注意点 ◆DBに接続不能 → 例外発生 → /error にフォワードする → /error でも例外が発生する → Spring Bootのエラー画面ではなく、 Tomcatのデフォルトエラー画面が出る ◆Tomcatのデフォルトエラー画面を差し替える方法 ◆Spring BootでTomcatデフォルトのエラーページをカスタマイズする 38
もっと詳しく知りたい人は ◆SessionRepositoryFilterのソースを読む ◆https://github.com/spring-projects/springsession/blob/main/spring-sessioncore/src/main/java/org/springframework/session/web/http/Sessio nRepositoryFilter.java ◆公式ドキュメントを読む(ただし仕組みの解説は少ない) ◆https://docs.spring.io/spring-session/reference/ 39
ご清聴ありがとうございました! ◆よいSpringライフを! 40