はじめに
業務 Webアプリケーションを開発していると、こんなわずらわしさがあると思います。
- Webページ間での大量データ引き渡し(一度サーバーに送る?)
- サーバー側セッションが重い(リソース消費、スケールアウト時の共有、タイムアウト管理…)
- 認証の仕組みの構築が大変(LDAP連携、JWT発行・検証、トークンの保存場所の選定…)
今回は、これらの課題に対する 3つの設計アプローチ を紹介します。
- ブラウザストレージ管理 — スコープ付きストレージでページ間データを安全に持ち回る
- JS変数セッション管理 — サーバーと自動同期するクライアント側セッション
- 透過的な 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変数セッション管理 — サーバーと自動同期する仕組み
セッションデータを localStorage や sessionStorage のようなストレージではなく、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変数にトークンを保存するのか
ブラウザの localStorage や sessionStorage にトークンを保存すれば、ページリロード後も再認証が不要になります。しかし、これらのストレージは 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つに共通するのは 「業務コードに余計なものを混ぜない」 という方針です。データ管理も、セッション同期も、認証も、フレームワーク層が面倒を引き受けることで、業務ロジックの実装に集中できる環境を作れます。
関連記事リンク
他の記事もぜひご覧ください!
- 01 Javaフレームワークを自作した動機
- 02 直結型URLマッピング
- 03 JSON限定
- 04 モックアップ=実装コード
- 05 動的リスト表示
- 06 独自HTML属性
- 07 Map型設計
- 08 1ファイルCSS設計
- 09 クライアント側データ管理とJWT認証(本記事)
- 10 Java直書きSQL
- 11 バニラで作る理由
SIcoreフレームワーク リンク
実装コードと資料はすべてこちらで公開しています。
- HP: https://onepg.com/ja/
- GitHub: https://github.com/sugaiketadao/sicore-ja
- サンプル画面の確認方法: https://github.com/sugaiketadao/sicore-ja#%EF%B8%8F-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E7%94%BB%E9%9D%A2%E3%81%AE%E7%A2%BA%E8%AA%8D%E6%96%B9%E6%B3%95---vs-code
- AI開発の始め方: https://github.com/sugaiketadao/sicore-ja#-ai%E9%96%8B%E7%99%BA%E3%81%AE%E5%A7%8B%E3%82%81%E6%96%B9
読んでいただきありがとうございました!
❤いいね!をしていただけると励みになります。