« ログイン/ログアウト前後でクエリパラメータを保つ on Spring Boot | トップページ | Redmineを入れてみる on CloudGarage »

2017年9月17日 (日)

ログイン失敗時のメッセージを増やしたい 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つがあると考える。

  1. 認証ハンドラで、クエリパラメータを適切に追加、削除する
  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 »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/164672/65801736

この記事へのトラックバック一覧です: ログイン失敗時のメッセージを増やしたい on Spring Boot:

« ログイン/ログアウト前後でクエリパラメータを保つ on Spring Boot | トップページ | Redmineを入れてみる on CloudGarage »