1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クライアント側データ管理とJWT認証 - いまさらながら Javaフレームワークを自作した(10年ぶり3回目) #09

1
Last updated at Posted at 2026-03-06

はじめに

業務 Webアプリケーションを開発していると、こんなわずらわしさがあると思います。

  • Webページ間での大量データ引き渡し(一度サーバーに送る?)
  • サーバー側セッションが重い(リソース消費、スケールアウト時の共有、タイムアウト管理…)
  • 認証の仕組みの構築が大変(LDAP連携、JWT発行・検証、トークンの保存場所の選定…)

今回は、これらの課題に対する 3つの設計アプローチ を紹介します。

  1. ブラウザストレージ管理 — スコープ付きストレージでページ間データを安全に持ち回る
  2. JS変数セッション管理 — サーバーと自動同期するクライアント側セッション
  3. 透過的な JWT 認証 — LDAP + JWT をクライアントが意識せずに使える設計

この3つは密接に連携しており、組み合わせることで「業務コードに認証やデータ管理のコードが混ざらない」状態を実現できます。

1. ブラウザストレージ管理 — スコープで整理すると楽になる

課題:ページ間のデータ引き渡しが散らかる

シングルページアプリケーションでなければ、ページ遷移のたびにデータの受け渡しが必要です。よくある方法とその問題点:

方法 問題点
URLパラメーター 項目数が多いと長大になる。タイムスタンプのような値は不向き
hidden フィールド HTML に埋め込むため管理が煩雑。意図せず受け取ってしまうことも。明細データは難易度高
cookie 容量制限がある。サーバーに毎回送信される
sessionStorage を直接使う キーが衝突しやすい。どのページのデータか区別がつかない

解決策:スコープ付きストレージ

sessionStorage をそのまま使うのではなく、URLパスからスコープを自動判定する仕組みを被せます。

スコープ 用途
ページ単位 そのページ内でのみ有効なデータ 検索条件の一時保存
機能単位 同一ディレクトリ内のページ間で共有 ヘッダ編集→明細編集へのデータ引き渡し
システム単位 システム全体で共有 ユーザー設定など
/app/exmodule/head-edit.html    →  機能単位キーは "/app/exmodule/"
/app/exmodule/detail-edit.html  →  機能単位キーは "/app/exmodule/"(同じ!)

同じディレクトリにあるページは、自動的にデータを共有できます。キーの命名規則を難しく考える必要はなく、ディレクトリ構成がそのままスコープになります。

メリット

  • キー衝突の心配がない — スコープが自動分離される
  • ディレクトリ設計 = データ設計 — 機能ごとにディレクトリを分けるだけでスコープが決まる
  • URLパラメーター不要 — 項目数が多くても問題ない

SIcore での実装例

SIcore では StorageUtil クラスがこの仕組みを提供しています。

ヘッダ編集ページ(head-edit.js):

// ページ全体のデータをストレージに保存してページ遷移
const headObj = PageUtil.getValues();
StorageUtil.setModuleObj('headData', headObj);
HttpUtil.movePage('detailedit.html');

明細編集ページ(detail-edit.js):

// ストレージからデータを取得して画面にセット
const headObj = StorageUtil.getModuleObj('headData');
PageUtil.setValues(headObj);

呼び出し元がどのページかを意識する必要はなく、同じ機能ディレクトリにいれば自然にデータが共有されます。データには 'headData' のようにキーで名前を付けて保存・取得するため、複数のデータを整理して持ち回ることもできます。実際の保存キーは _module@/app/exmodule/headData のようにスコープと URL パスが自動付与され、異なる機能のキーとは衝突しません。

2. JS変数セッション管理 — サーバーと自動同期する仕組み

セッションデータを localStoragesessionStorage のようなストレージではなく、JavaScript の変数 に保持します。ページをリロードすると消えますが、その特性がセキュリティ上の利点にもなります。

課題:サーバー側セッションの重さ

従来のサーバー側セッション(HttpSession 等)には課題があります。

  • セッションの保持にサーバーリソースを消費する(OutOfMemoryこわい)
  • スケールアウト時にサーバー間でのセッション共有が必要になる
  • セッションの有効期限管理やタイムアウト処理が必要

解決策:JS変数 + 自動同期

セッションデータを JS変数 で保持し、サーバー通信のたびに JSON の _session(フレームワークが使用)キーで自動的に送受信 する方式です。

// サーバー呼び出しの内部処理(簡略)

// リクエスト送信前:セッションデータを自動付与
req['_session'] = sessionData;

// レスポンス受信後:セッションデータを自動更新
sessionData = res['_session'];

サーバー側ではリクエスト JSON の _session を読み書きするだけで、クライアントとデータを共有できます。サーバーにセッション状態を保持する必要がありません

メリット

  • サーバーがステートレス — セッション状態をサーバーに保持しない。スケールアウトが容易
  • 業務コードがシンプル — セッション同期はフレームワークが行うため、業務コードでは意識不要
  • リロードでクリア — JS変数のためページリロードでデータが消える。ストレージに残らないためセキュリティ上の利点でもある

ブラウザストレージとの使い分け

ブラウザストレージ JS変数セッション
保存場所 sessionStorage JS変数
サーバー連携 しない 毎回自動で同期
リロード後 保持される 消える
用途 ページ間データ引き渡し サーバーとの共有データ

「ページ間で持ち回りたい」か「サーバーと共有したい」かで選べば迷いません。

3. 透過的な JWT 認証 — クライアントが認証を意識しない設計

LDAP 連携・JWT 発行と検証・トークンの保存場所の選定など、認証の仕組みを一から構築するのは手間がかかります。SIcore では認証を 3つのレイヤーに分離 し、業務コードから完全に隠蔽することでこの手間を解消します。

レイヤー 責務 業務コードの関与
サインイン LDAP 認証 → JWT 発行 → JS変数に格納 サインインページのみ
トークン管理 JS変数でトークンを保持。リクエスト時に自動付与 不要
サーバー検証 全リクエストで JWT を検証。不正なら 401 不要

業務ページのコードは、認証のことを一切知らずにサーバーを呼び出せます。

全体フロー

サインイン時:

[ブラウザ] サインインページで ID・パスワードを入力
     ↓
[サーバー] LDAP で認証 → 成功したら JWT を発行
     ↓ レスポンス JSON: { _session: { token: "hoge..." } }
[ブラウザ] _session.token を受け取り
     ↓ JS変数にトークンを保存
[ブラウザ] 業務ページへ遷移

以降の業務操作(すべて自動):

[ブラウザ] 業務ページから callJsonService() を呼ぶ(認証コードなし)
     ↓ フレームワークが Authorization: Bearer hoge... をHTTPヘッダに自動付与
[サーバー] JWT 検証 → 成功 → 業務処理を実行してレスポンスを返す

なぜJS変数にトークンを保存するのか

ブラウザの localStoragesessionStorage にトークンを保存すれば、ページリロード後も再認証が不要になります。しかし、これらのストレージは XSS 攻撃によってスクリプトから読み取られるリスク があります。

JS変数への保存はリロードのたびに再サインインが必要になりますが、その代わりにトークン漏洩のリスクを排除できます。

通常の業務操作でリロードする場面はほぼありませんし、業務アプリケーションの多くは「ブラウザを閉じたらサインアウト」が自然な動作であり、SI の現場ではむしろこの挙動が好まれます。

メリット

  • 業務コードがクリーン — トークン管理・ヘッダー付与・401 処理がすべて透過的
  • XSS 耐性 — ストレージに保存しないため、ストレージ経由のトークン窃取を防止
  • ステートレス — サーバー側にセッションを持たない JWT ベースの認証

SIcore での実装

SIcore では、この3レイヤーが以下のように実装されています。

サインイン(Java) — LDAP 認証して JWT を発行:

// SigninService.java(抜粋)
try {
  (new InitialDirContext(param)).close(); // LDAP 認証
} catch (final NamingException e) {
  io.session().put("token", ValUtil.BLANK);
  io.putMsg(MsgType.ERROR, "es001");
  return;
}
final String token = JwtUtil.createToken(id);
io.session().put("token", token);

トークン自動付与(JavaScript) — 業務コードは関与しない:

// サーバー呼び出しの内部処理(簡略)
const token = SessionUtil._token;
if (!ValUtil.isBlank(token)) {
  header['Authorization'] = 'Bearer ' + token;
}

サーバー側 JWT 検証(Java) — 全リクエストを検証:

// AbstractHttpHandler.java(抜粋)
private boolean validateJwt(final HttpExchange exchange) {
  final String authHeader = exchange.getRequestHeaders().getFirst("Authorization");
  if (authHeader == null || !authHeader.startsWith("Bearer ")) {
    return false;
  }
  JwtUtil.validateToken(authHeader.substring(7));
  return true;
}

おわりに

今回紹介した3つの設計アプローチをまとめます。

アプローチ 課題 解決策 メリット
スコープ付きストレージ管理 ページ間データ引き渡しの煩雑さ URLパスからスコープを自動判定 キー衝突なし。ディレクトリ設計 = データ設計
JS変数セッション管理 サーバー側セッションの重さ クライアント側JS変数 + サーバー自動同期 ステートレス。スケールアウト容易
透過的 JWT 認証 認証の仕組みの構築が大変 認証をレイヤー分離して業務コードから隠蔽 コードがクリーン。XSS 耐性

3つに共通するのは 「業務コードに余計なものを混ぜない」 という方針です。データ管理も、セッション同期も、認証も、フレームワーク層が面倒を引き受けることで、業務ロジックの実装に集中できる環境を作れます。

関連記事リンク

他の記事もぜひご覧ください!

SIcoreフレームワーク リンク

実装コードと資料はすべてこちらで公開しています。


読んでいただきありがとうございました!
❤いいね!をしていただけると励みになります。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?