markdown

はじめに (対象読者・この記事でわかること)

この記事は、Spring Boot で Web アプリケーション開発を行っているエンジニアを対象としています。特に、サーバーサイドの Java オブジェクトの値を Thymeleaf のテンプレート上で JavaScript に埋め込み、フロントエンドのロジックで利用したい方に最適です。
本稿を読むことで、以下ができるようになります。

  • Java の POJO(例: User クラス)のフィールドを JSON 化し、Thymeleaf 経由で安全に HTML に出力する方法
  • 文字列エスケープや XSS 対策を踏まえた実装パターン
  • 実際のコード例を通じて、ページロード時に即座に JavaScript 変数として利用できるようになる

背景として、従来は Thymeleaf の ${...} を直接 JavaScript の文字列リテラルに埋め込むと、エスケープが不十分で XSS リスクが高まるケースが散見されました。本記事ではその問題点を回避しつつ、可読性の高い実装を提案します。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。
- Java / Spring Boot の基本的な開発経験
- HTML と Thymeleaf のテンプレート構文の基礎
- 基本的な JavaScript(ES6 以降)と JSON の扱い方

Java オブジェクトを Thymeleaf で JSON 化する概要

Web アプリケーションでは、サーバー側で取得したデータをクライアント側の JavaScript に渡すケースが頻繁にあります。Thymeleaf はサーバーサイドで HTML を組み立てるテンプレートエンジンですが、直接 Java オブジェクトを文字列に展開すると次のような問題が発生します。

問題点 具体例
文字列エスケープ不足 "' がそのまま出力され、JavaScript の構文エラーになる
XSS 脆弱性 ユーザー入力をそのまま埋め込むと、悪意あるスクリプトが実行される
可読性の低下 複数フィールドを個別に埋め込むとテンプレートが煩雑になる

これらを解決するために、Jackson などのシリアライザでオブジェクトを JSON 文字列に変換し、Thymeleaf の th:inline="javascript" 機能で安全にインライン化します。th:inline は自動的に JSON エスケープを行い、スクリプト内に直接埋め込める形に整形してくれます。

具体的な手順と実装例

以下では、実際の Spring Boot プロジェクトを例に、User オブジェクトの情報を JavaScript 変数 userData に展開するまでのフローをステップごとに解説します。

ステップ 1: POJO とコントローラの用意

まずは、渡したいデータ構造を表す POJO を作成します。

Java
// src/main/java/com/example/demo/model/User.java package com.example.demo.model; public class User { private Long id; private String name; private String email; // コンストラクタ, getter, setter 省略 }

次に、コントローラで User インスタンスをモデル属性として登録します。

Java
// src/main/java/com/example/demo/controller/UserController.java package com.example.demo.controller; import com.example.demo.model.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @Controller public class UserController { @GetMapping("/profile") public String profile(Model model) { User user = new User(); user.setId(1001L); user.setName("山田太郎"); user.setEmail("taro.yamada@example.com"); // JSON に変換させるためにそのままオブジェクトを格納 model.addAttribute("user", user); return "profile"; } }

ステップ 2: Thymeleaf テンプレートで JSON インライン化

profile.html では th:inline="javascript" を指定し、user オブジェクトを JSON.stringify で展開します。

Html
<!-- src/main/resources/templates/profile.html --> <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>ユーザプロフィール</title> </head> <body> <h1>ようこそ、[[${user.name}]] さん!</h1> <script th:inline="javascript"> /*[[${user}]] は Thymeleaf が自動で JSON エスケープし、以下のように出力されます 例: {"id":1001,"name":"山田太郎","email":"taro.yamada@example.com"} */ var userData = /*[[${user}]]*/ {}; console.log('User data:', userData); </script> </body> </html>

ポイントは次の通りです。

  1. th:inline="javascript" を付与すると、${...} の中身が JavaScript のリテラルとして安全に変換されます。
  2. /*[[${user}]]*/ のコメント構文は、Thymeleaf がテンプレートを解析したときに JSON に置き換え、最終的にブラウザに渡すコードは var userData = {"id":1001,"name":"山田太郎","email":"taro.yamada@example.com"}; となります。
  3. /*[[...]]*/ の前後に空白や改行を入れすぎると、意図しない文字列が混入する可能性があるため、1 行で完結させるのがベストです。

ステップ 3: XSS 対策を意識した実装

上記の方法は、Thymeleaf が内部で ObjectMapper(Jackson)を利用し、HTML エスケープJavaScript エスケープ を同時に行うため、基本的に安全です。ただし、以下の点に注意してください。

  • th:utext ではなく th:text を使用し、HTML タグの自動エスケープを有効に保つ。
  • JSON にシリアライズされるフィールドに @JsonIgnore を付与し、機密情報(パスワードなど)が漏れないようにする。
  • 大量データを直接インライン化するとページサイズが肥大化するため、ページネーションAJAX での逐次取得 を検討する。

ステップ 4: JavaScript 側での利用例

取得した userData をそのままオブジェクトとして扱えるので、以下のように UI に反映させることが可能です。

Javascript
document.addEventListener('DOMContentLoaded', () => { const nameEl = document.getElementById('userName'); if (nameEl && userData.name) { nameEl.textContent = userData.name; } // 例: プロフィール編集ボタンにデータを渡す const editBtn = document.getElementById('editBtn'); editBtn.addEventListener('click', () => { openEditModal(userData); }); });

ハマった点やエラー解決

現象 原因 解決策
Uncaught SyntaxError: Unexpected token < th:inline が付いていない状態で /*[[${user}]]*/ がそのまま出力され、ブラウザが HTML コメントと解釈 <script th:inline="javascript"> を必ず付与
JSON の文字列がエスケープされず " が破綻 th:text で直接出力した th:inline="javascript"/*[[...]]*/ の組み合わせに変更
大量データでページ読み込みが遅い 全件をインラインで埋め込んだ 必要な項目だけを DTO に絞り、残りは AJAX で取得

完全なサンプルプロジェクト構成

src/
 └─ main/
     ├─ java/com/example/demo/
     │   ├─ model/User.java
     │   ├─ controller/UserController.java
     │   └─ DemoApplication.java
     └─ resources/
         ├─ templates/profile.html
         └─ application.yml

この構成で ./mvnw spring-boot:run を実行し、http://localhost:8080/profile にアクセスすれば、コンソールに正しく JSON が出力され、フロントエンドのロジックで利用できることを確認できます。

まとめ

本記事では、Spring Boot と Thymeleaf を組み合わせて、Java オブジェクトのフィールドを安全に JavaScript に展開する手順 を解説しました。

  • Thymeleaf の th:inline="javascript"/*[[...]]*/ による自動エスケープ
  • Jackson での JSON シリアライズXSS 対策 のベストプラクティス
  • 実装上の ハマりポイントと解決策

これらを活用することで、サーバーサイドとクライアントサイドのデータ受け渡しがシンプルかつ安全になります。今後は、WebSocket でリアルタイムにオブジェクトを更新したり、React/Vue と組み合わせた高度な UI への応用も検討していく予定です。

参考資料