ログイン失敗時のメッセージを増やしたい on Spring Boot
0. ことのはじまり
先日、ログイン/ログアウト前後でクエリパラメータを保つ on Spring Bootと題して、ログイン/ログアウト時にクエリパラメータを保持する方法について書いたんだけど、こいつの参照実装を後輩君に教えてやったところ、「ログイン失敗時のメッセージが出なくなったんすけど?」とかいうリプライが。
どうやら、槙 俊明さんのはじめての Spring Boot[改訂版]をもとに(というかほぼそのまんまの模様)Spring Securityの設定をしていたようだが、「ログインに失敗したときに、画面上どうふるまうか」を理解せぬまま、僕が示した参照実装のコードを丸写ししていたようで、あんなリプライを出してきた模様。
このあたり、「自分で考えろ」と突き放してもよかったんだが、後学のために「どうすればいいか」を少々考えてみることにする。
1. 実際どうなっているのか
氏の書籍には、だいたいこんな感じでエラーメッセージ周りの処理が紹介されている。
Viewのhtml(抜粋)
<div th:if="${param.error}>ユーザー名、またはパスワードが違います。</div>
SecurityConfig(抜粋)
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginProcessingUrl("/login")
.loginPage("/loginForm")
.failureUrl("/loginForm?error")
.defaultSuccessUrl("/", true)
.usernameParameter("username")
.passwordParameter("password")
.permitAll();
}
勘のいい方はお分かりと思うが、
- 認証に成功したら、「/」にリダイレクト
- 認証に失敗したら、「/loginForm?error」に移動
と、 URLだけで制御している ことがわかると思う。
それが証拠に、認証に失敗しなくても、「/loginForm?error」をブラウザのアドレスバーに直接入力すると、エラーメッセージが出るはずである。
これ自体は、話を簡単にするための処置なのだろう、と考えているのだが、今回の一件では、認証の成功、失敗で適切にクエリパラメータを加工して流す考慮が必要になるので、アプローチとしては以下の2つがあると考える。
- 認証ハンドラで、クエリパラメータを適切に追加、削除する
- 失敗時の認証ハンドラで、エラーメッセージをセッションに置く
2. URLだけで制御する場合
Viewの実装をそのままにしたい場合、いままでどおりURLでコントロールするよう、クエリパラメータを適切に追加、削除する処理を書けばよい、ということになる。
たとえば、こんな感じ。
認証成功時(抜粋)
// add whole query parameters to url
String queryParams = request.getQueryString() == null ? "" : "?" + request.getQueryString();
// remove error parameter if present.
if (queryParams.contains("error&")) {
queryParams.replaceAll("error&", "");
} else if (queryParams.contains("&error")) {
queryParams.replaceAll("&error", "");
}
認証失敗時(抜粋)
// add whole query parameters to url
String queryParams = request.getQueryString() == null ? "" : "?" + request.getQueryString();
// add error parameter if not present
if (!queryParams.contains("error")) {
if (queryParams.length() == 0) {
queryParams = queryParams + "?error";
} else {
queryParams = queryParams + "&error";
}
}
汚い実装で恐縮だが、雰囲気は伝わっただろうか。
3. セッションを使う場合
認証失敗時に呼ばれる AuthenticationFailurehandler#onAuthenticationFailure(HttpServletRequest, HttpServletResponse, AuthenticationException) では、認証失敗に至った理由が AuthenticationException のオブジェクトとして受け取ることができるようになっている。(参考)
「パスワードが違う」「アカウントが有効期限切れ」「アカウント凍結中」など、詳細に調べることができるので、これを使って流すエラーメッセージを選別すればいい。
たとえば、こんな感じ。
認証失敗時(抜粋)
// Analyze the cause of the error
String errReason = null;
if (exception instanceof BadCredentialsException) {
errReason = "Invalid user name or password.";
} else if (exception instanceof AccountExpiredException) {
errReason = "This account is expired. Please contact administrator.";
} else if (exception instanceof CredentialsExpiredException) {
errReason = "Your password is expired. Please contact administrator.";
} else if (exception instanceof DisabledException) {
errReason = "Your password is disabled. Please contact administrator.";
} else if (exception instanceof LockedException) {
errReason = "Your accouunt is locked. Please contact administrator.";
} else {
errReason = "Unknown problem occured. Please contact administrator.";
}
if (errReason != null && errReason.length() > 0) {
HttpSession session = request.getSession();
session.setAttribute("errReason", errReason);
}
一方Thymeleafには、セッションオブジェクトを扱うための「session」というそのものずばりな予約変数がある。
利用方法は「Spring MVC and Thymeleaf: how to access data from templates - Thymeleaf」という公式ドキュメントに詳しく書かれているので、ひとまずそちらを参照していただくとして、セッションにエラーメッセージを置くなら、セッションから値を取り出して張る処理をViewテンプレートに書く。
たとえば、こんな感じ。
Viewのテンプレート(抜粋)
<div th:unless="${session == null}" th:text="${session.errReason}">Hoge</div>
この方法のメリットは、クエリパラメータをこねくり回す必要がなくなる点で、おそらくこういう方法のほうがフレームワークの設計思想に沿ったものなんじゃないだろうか、と個人的に考えている。
4. とりあえず動く参照実装
URLだけでコントロールしたい場合はこちらを、セッションを使う場合はこちらを、それぞれご参照ください。
« ログイン/ログアウト前後でクエリパラメータを保つ on Spring Boot | トップページ | Redmineを入れてみる on CloudGarage »
« ログイン/ログアウト前後でクエリパラメータを保つ on Spring Boot | トップページ | Redmineを入れてみる on CloudGarage »
コメント