はじめに
現場ではCSRF対策でトークンを使用するため、POSTで画面遷移を行う必要がありました。
この記事を参考に実装されたものを流用したのですが、今回Thymeleafで少し詰まってしまったのでまとめます。
バージョン
Spring Boot 2.7.3
Thymeleaf 3.0.15
コード例
まずは動作した例です。
※全てのコードはこちらを参照→https://github.com/1noseA/mailnaxx
htmlでは、table td内のaタグに詳細画面遷移のJavaScript関数を入れています。
詳細画面遷移用のformは下に設置(一部抜粋のため省略していますが、formが入れ子にならないように)。
// 一覧表示部分のみ抜粋
<div class="container">
<div class="listArea mx-auto">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr class="border-bottom border-dark">
<th scope="col" class="text-canter">選択</th>
<th scope="col">社員番号</th>
<th scope="col">氏名</th>
<th scope="col">入社年月</th>
<th scope="col">所属</th>
<th scope="col">権限区分</th>
<th scope="col"><!-- 営業 --></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="form-check d-flex justify-content-center align-items-center">
<input class="form-check-input" type="checkbox" name="deleted_flg" value="1">
<input type="hidden" name="user_id" th:value="${userList.user_id}">
</div>
</td>
<td>
<a href="javascript:void(0);" th:onclick="showDetail('[[${userList.user_id}]]')">
<span th:text="${userList.user_number}"></span>
</a>
</td>
<td>
<a href="javascript:void(0);" th:onclick="showDetail('[[${userList.user_id}]]')">
<span th:text="${userList.user_name}"></span>
</a>
</td>
<td>
<a href="javascript:void(0);" th:onclick="showDetail('[[${userList.user_id}]]')">
<span th:text="${#strings.replace(userList.hire_date, '-', '/')}"></span>
</a>
</td>
<td>
<a href="javascript:void(0);" th:onclick="showDetail('[[${userList.user_id}]]')">
<span th:text="${userList.affiliation_id}"></span>
</a>
</td>
<td>
<a href="javascript:void(0);" th:onclick="showDetail('[[${userList.user_id}]]')">
<th:block th:each="roleClass : ${roleClassList}">
<th:block th:if="${userList.role_class == roleClass.value}">
<span th:text="${roleClass.viewName}"></span>
</th:block>
</th:block>
</a>
</td>
<td>
<a href="javascript:void(0);" th:onclick="showDetail('[[${userList.user_id}]]')">
<span th:text="${userList.sales_flg == '1'} ? '営業' : ' '"></span>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<form method="post" th:action="@{/user/detail}" id="detailForm">
</form>
htmlの下に設置したformに対象のユーザIDをhiddenで入れ、submitしています。
function showDetail(id) {
let form = document.getElementById('detailForm');
let input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'user_id');
input.setAttribute('value', id);
form.appendChild(input);
form.submit();
}
// 一部抜粋
// 詳細画面初期表示
@PostMapping("/user/detail")
public String detail(int user_id, Model model) {
Users userInfo = usersMapper.findById(user_id);
model.addAttribute("userInfo", userInfo);
return "user/detail";
}
詰まったこと
①ThymeleafのJavaScript関数の引数にJavaの値を入れる書き方が分からない
jsのデバッグでそのまま文字列として表示されていました。
// JavaScript関数部分のみ抜粋
// NG例①
showDetail('${userList.user_id}')
// NG例②
showDetail('__${userList.user_id}__')
// OK例
showDetail('[[${userList.user_id}]]')
②aタグにJavaScript関数を入れる書き方が分からない
// NG例
<td>
<a href="javascript:showDetail('[[${userList.user_id}]]')">
<span th:text="${userList.user_name}"></span>
</a>
</td>
// OK例
<td>
<a href="javascript:void(0);" th:onclick="showDetail('[[${userList.user_id}]]')">
<span th:text="${userList.user_name}"></span>
</a>
</td>
③403になってしまう
トークンのエラーと予想できました。
一覧画面では複数のformがある状態だったので、jsでformを作成しsubmitするようにしていました。
Spring Boot+Thymeleafではform内の「th:action〜」をつけることでCSRFトークンを設定してくれるのだそうです。
jsでどうトークンを設定するのかは分からず、html上にformを作成することで対処しました。
// NG例
function showDetail(id) {
let form = document.createElement('form');
form.setAttribute('action', '/user/detail');
form.setAttribute('method', 'post');
let input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'user_id');
input.setAttribute('value', id);
form.appendChild(input);
form.submit();
}
最後に
最近は現場でも家でもASP.NETをやっており、久しぶりのSpring BootでThymeleafの書き方どうだったっけという感じでした。
Controllerの書き方の違いはそんなに気にならないですが、htmlはテンプレートエンジンの書き方を調べながらになりますね。。
参考
Thymeleafでjavascriptの関数を呼び出したときに引数がうまく渡せない
Spring Boot + ThymeleafのCSRFトークン設定は超簡単