はじめに
Spring Securityを用いてログイン・ログアウト機能を実装していた際、POSTリクエストを送りたいところ、aタグではGETリクエストが送られてしまうため、下記実装を行いました。
formタグでpostにしてダイレクトに送ってもいいんですが、aタグを使用して送りたいケースも仕様によっては考えうるケースかと思います。
この辺に疎いので一つひとつ実装解説も併せて、備忘録という形で残したいと思います。
※誤りありましたら、ご指摘いただけますと幸いです。
想定読者
初学者の方
フロントも頑張りたい方
実装の中身
html
<div>
<ul class="nav navbar-nav navbar-right">
<li><a href="#" onclick="document.getElementById('logout-form').submit(); return false;">
Logout <i class="fa fa-power-off"></i></a>
</li>
</ul>
<p class="navbar-text navbar-right">
<span id="loginName">user: userName</span>
</p>
</div>
...一部省略...
<form id="logout-form" th:action="@{/logout}" method="post" style="display: none;">
<input type="hidden" name="_csrf" th:value="${_csrf.token}" />
</form>
各部分の解説
1, 'a href="#"'
'href="#"' は、リンクをクリックしたときにページの一番上にスクロールするだけで、特定のURLには移動しないことを意味しています。
2, 'onclick="document.getElementById('logout-form').submit(); ":'
リンクがクリックされると、この部分が実行されます。
'document.getElementById('logout-form')'は、IDがlogout-formであるHTML要素(今回はコード下部に記載があるフォームを指す)を取得します。
'.submit();'は、そのフォームを送信するメソッドになります。
3, 'return false;'
この部分は、リンクのデフォルトの動作(今回で言うと、'a href="#"'の部分、ページの一番上にスクロールすること)を防止します。
続いて、コード下部のformタグ部分について。
4, 'form id="logout-form" th:action="@{/logout}" method="post" style="display: none;'
idに"logout-form"を指定することで、上記2番で記載した、JavaScriptコードで取得されるのがこのフォームになります。
methodにpostを指定することも忘れずに。
5, 'input type="hidden" name="_csrf" th:value="${_csrf.token}"'
CSRF(クロスサイトリクエストフォージェリ)トークンを含む隠し入力フィールドになります。ちなみにSpring Securityでは、CSRF保護のためにこのトークンが必要であり、デフォルトで有効になっていますが、下記のように記述することでThymeleafが実行時に適切なCSRFトークンを埋め込んでくれます。
※CSRF(クロスサイトリクエストフォージェリ)について
ざっくりいうと、悪意のあるウェブサイトがユーザーに対して別のウェブサイト上でのアクションを無意識に実行させるような攻撃のことです。
例えばhidden等を使用して、隠しフィールドで悪意のあるリクエストを送ったり。
CSRFトークンを使用することで、各フォームに一意のトークンを含め、リクエストごとに検証ができるようになります。
詳しく知りたい方はコチラ。
クロスサイトリクエストフォージェリについて
JavaScript
document.addEventListener('DOMContentLoaded', function() {
var logoutLink = document.querySelector('a[href="#"]');
if (logoutLink) {
logoutLink.addEventListener('click', function(event) {
event.preventDefault();
document.getElementById('logout-form').submit();
});
}
});
各部分の解説
1, 'document.addEventListener('DOMContentLoaded', function() { ... });'
このコードがあることで、ページの全てのコンテンツが読み込まれた後に実行されます。これにより、HTMLが完全に解析されてDOMツリー(bodyタグやhtmlタグを抽出し、それを階層構造にしたもの)
が構築された後にスクリプトが実行されるようになります。
2, 'var logoutLink = document.querySelector('a[href="#"]');'
href属性が#である最初のaタグを取得します。htmlの1番で['a href="#"']のように指定したことで、ここで取得されるようになります。
3, 'if (logoutLink) { ... }'
上記2番のlogoutLinkが存在する場合にのみ内部のコードを実行するための条件分岐文です。
4, 'logoutLink.addEventListener('click', function(event) { ... });'
logoutLinkがクリックされたときにイベントリスナーを追加している処理になります。ちなみに、addEventListenerメソッドの第一引数はイベントタイプ(この場合は'click')、第二引数はイベントが発生したときに実行されるコールバック関数になります。
5, 'event.preventDefault();'
この行は、リンクのデフォルトの動作(今回で言うとページのトップに移動すること)をキャンセルします。
6, 'document.getElementById('logout-form').submit();'
logout-formというIDを持つフォーム(html4番で記載した部分)をプログラムによって送信します。これにより、ログアウトのPOSTリクエストが送信されます。
まとめ
割と基本的な部分も抜け落ちていたので、一から調べる良い機会になりました。
CSS、JavaScript辺りのフロント部分に苦手意識があったので、逃げずに向き合う意味を込めて色々と調べてみましたが、出来ることも多く、得られたものも多かったです。
なるべく理解まで落とし込めるよう、気になった部分は逐一調べる癖をつけて、学習を進めていきたいと改めて思いました。