Spring Securityでハマったことをまとめてみた

Spring Securityでハマったことをまとめてみた

以前、以下のツイートをしました。

サーブレットフィルタレベルで細かく調べていったのにspring security のdb認証がどうしてもうまくいかない springbootで作成してるからautoconfigが邪魔してるとかもみたけど問題なさそう。。 次は最小アプリつくってみて原因をさがすことにします こういうのって大抵小さなことが原因なんだよな


Spring SecurityはSpringフレームワークのサブプロジェクトの中でも難しいです。

よく、挫折ポイントなんて言われたりします。


Spring Securityを使用して初めて挫折しそうになりました。。ノウハウとしてハマったことをまとめておきます。


そんな方向けになります。

本記事ではSpring Securityでハマった方の手助けになることを目的としています。


構成は以下です。

それでは、順に見ていきましょう。

著者情報

ちなみにですが、私は5年以上IT系エンジニアとして働いており、主にJavaを主戦場にしています。Webアプリケーションと業務系のアプリケーションの経験を持つごく普通のエンジニアです。

Spring Securityでハマったことをまとめてみた

実際にハマったことは以下になります。

  • 仕組みがわかりにくい
  • URLごとのセキュリティ認証の設定がうまくいかない
  • POSTメソッド実行時に反応がない【Tymeleaf】


順に説明します。

【ハマったこと①】仕組みがわかりにくい

そもそもですが仕組みがわかりにくいです。

うまく行かない場合に対策を考えますが、Spring Securityの仕組みを理解していないと解決できませんでした。

(はじめは闇雲に変更したりしましたがうまく行かない場合がほとんどでした。。)

なので、どういった仕組みでSpring Securityが動いているかをまず先に理解することをおすすめします。

詳しくは別の記事で書く予定です。

【ハマったこと②】URLごとのセキュリティ認証の設定がうまくいかない

Spring Securityでは認証と認可の設定を細かくできます。

今回の場合は認証の設定の話ですが、

URLごとのセキュリティ認証の設定をする際にうまく行かない場合が多かったです。

URLごとのセキュリティ認証の設定は、@EnableWebSecurityアノテーションを付与し、WebSecurityConfigurerAdapterクラスを継承したコンフィグクラスで設定をします。

具体的には、以下のように設定します。

    /**
     * 主にURLごとの異なるセキュリティ設定
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //http.authorizeRequests().anyRequest().permitAll();
        //http.authorizeRequests().anyRequest().authenticated();
        //http.csrf().disable()
        //    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
            
        // 最低限の実装
        http.authorizeRequests()
                //.antMatchers("/user/**").hasRole("ADMIN")
                .anyRequest().authenticated() // それ以外は認証が必要
                .and()
            .formLogin() // フォーム認証を有効(フォーム認証用のフィルタUsernamePasswordAuthenticationFileter)
                .loginPage("/login")
                .loginProcessingUrl("/authenticate") // ログインフォームのアクションに指定したURL[action="@{/login}"]を設定
                .usernameParameter("email") // ログインフォームのユーザー欄のname属性を設定
                .passwordParameter("password") // ログインフォームのパスワード欄のname属性を設定
                //.successForwardUrl("/users") // ログイン成功時に遷移するURL ★これだとエラーになる
                .defaultSuccessUrl("/user/list") // ログイン成功時に遷移するURL
                .failureUrl("/login?error")
                .permitAll()
                .and()
            .logout()
                .logoutUrl("/logout")
                .permitAll()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
        
        // セッション設定
        http.sessionManagement()
            // デフォルト設定
            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            // ログイン前のセッションを破棄して、新しいセッションを作成する。
            // ログイン前のオブジェクトは引き継がれない(デフォルトは引き継がれる)
            .sessionFixation().newSession()
            // 同時ログイン数
            .maximumSessions(1)
            // ログインは先勝ち(false→後勝ち)
            .maxSessionsPreventsLogin(false)
            // エラー時の処理を指定
            .expiredSessionStrategy(new CustomSessionInformationExpiredStrategy());

        // セキュリティヘッダー設定
        //http.headers().defaultsDisabled()
        //    .cacheControl();
    }


上記の例では、カスタマイズログイン画面の認証設定やセッションの設定などをしています。

詳しくは、ソースコード内のコメントに説明が記載してあります。


設定をする際に以下について注意が必要です。

  • loginPageやloginProcessingUrlなどのメソッドで指定するパラメータはURLを指定すること。
  • ログイン認証が成功した場合の遷移先URLはdefaultSuccessUrlで指定すること。successForwardUrlではエラーになります。

【注意①】loginPageやloginProcessingUrlなどのメソッドで指定するパラメータはURLを指定すること

loginPageのようなメソッド名から、当初は画面名を指定するのかと勘違いしました。

ここで指定するパラメータ名はコンテキストパス以降のURLを指定して下さい。

参考にしていた資料やサイトだと、コンテキストパス以降のURLとクラスパス以降の画面名が同じ場合がほとんどでした。

なのでこのことになかなか気づけませんでした。

【注意②】ログイン認証が成功した場合の遷移先URLはdefaultSuccessUrlで指定すること。successForwardUrlではエラーになります。

参考にしていた資料だとsuccessForwardUrlを使用していたのでエラーになり、対策もなかなか気づきませんでした。。

これはなぜだか調査中ですがdefaultSuccessUrlを指定するようにしてください。

【ハマったこと③】POSTメソッド実行時に反応がない【Tymeleaf】

デフォルトではテンプレートエンジンはJSPを使用しますが、最近良く使用されるTymeleafを使用した際の話になります。

ちなみに、今回TymeleafでSpring Securityを使用する場合は、Mavanプロジェクトの依存関係にthymeleaf-extras-springsecurity5を追加してます。

カスタマイズログイン画面は以下になります。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

<th:block th:include="~{layout/header}"></th:block>

<body class="bg-gradient-primary">

  <div class="container">

    <!-- Outer Row -->
    <div class="row justify-content-center">

      <div class="col-xl-10 col-lg-12 col-md-9">

        <div class="card o-hidden border-0 shadow-lg my-5">
          <div class="card-body p-0">
            <!-- Nested Row within Card Body -->
            <div class="row">
              <div class="col-lg-6 d-none d-lg-block bg-login-image"></div>
              <div class="col-lg-6">
                <div class="p-5">
                  <div class="text-center">
                    <h1 class="h4 text-gray-900 mb-4">Welcome Back!</h1>
                  </div>
                  <form th:action="@{/authenticate}" th:method="POST" class="user">
                    <div class="form-group">
                      <input type="email" name="email" class="form-control form-control-user" id="exampleInputEmail" aria-describedby="emailHelp" placeholder="Enter Email Address...">
                    </div>
                    <div class="form-group">
                      <input type="password" name="password" class="form-control form-control-user" id="exampleInputPassword" placeholder="Password">
                    </div>
                    <div class="form-group">
                      <div class="custom-control custom-checkbox small">
                        <input type="checkbox" class="custom-control-input" id="customCheck">
                        <label class="custom-control-label" for="customCheck">Remember Me</label>
                      </div>
                    </div>
                    <button type="submit" class="btn btn-primary btn-user btn-block">
                      Login
                    </button>
                    <hr>
                    <a href="index.html" class="btn btn-google btn-user btn-block">
                      <i class="fab fa-google fa-fw"></i> Login with Google
                    </a>
                    <a href="index.html" class="btn btn-facebook btn-user btn-block">
                      <i class="fab fa-facebook-f fa-fw"></i> Login with Facebook
                    </a>
                  </form>
                  <hr>
                  <div class="text-center">
                    <a class="small" href="forgot-password.html">Forgot Password?</a>
                  </div>
                  <div class="text-center">
                    <a class="small" href="register.html">Create an Account!</a>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>

      </div>

    </div>

  </div>

  <!-- JavaScript -->
  <th:block th:include="~{layout/script}">js</th:block>

</body>

</html>


そして、今回の問題となった箇所は以下です。

・・・
<form th:action="@{/authenticate}" th:method="POST" class="user">
・・・


なんてことない記述だと思います。

この中でハマった原因は「th:method=”POST”」です。

Spring Securityのログイン認証が実行されるのはPostメソッドを実行した場合のみです。

HTMLでフォームのデフォルトはGETメソッドです。なので、POSTメソッドを実行したい場合は直接指定する必要があります。

ハマったときは「th:method=”POST”」を指定してませんでした。

単純に考えればわかりそうですが、気づかなかった理由としては

TymeleafではなくJSPを使用した場合はメソッドを指定しなくても内部でPOSTメソッドに変換して実行してくれているので、メソッドを記述する必要がなかったからです。

これに気づくのに結構時間がかかりました。。



以上、Spring Securityでハマったことについてでした。

【余談】ハマったときこそ成長ポイントです

Spring Securityにハマりながらも以下のツイートをしました。

spring securityで1週間はまるとは。。 レベル低いのバレバレ笑 ただ、原因がわからないことについて調べるのは、本では頭に入らない知識が大量に身につくから割と大事な時間だとおもってます笑 見当違いで調べたことが意外と現場で説得力のある発言につながる、あるある



結局、ハマったときが一番の成長するタイミングです。

見当違いな箇所を調べる場合がありますが、そういった知識が積み重なることでエンジニアとしての厚みが増すと私は考えます。

今回の場合ですと、すぐに対策をみつけることができなかったのでSpring Securityの基礎から調べ倒しました。

その結果、かなりSpring Securityについて詳しくなりました。

結局、本を読むだけでは頭に入らないんだと思います。

必要にかられた場合に知識として身につくと日々実感しております。

なので、初学者の方は目の前のことに一喜一憂するのではなく、長い目線で物事を考えた場合に今やっていることは無駄にならないのだと前向きに考えてみて下さい。

そうした苦労の先に楽しいエンジニア生活が待っているかと思います。


今回の記事が参考になれば幸いです。


人気記事①:
【厳選4冊+α】Spring Framework初心者におすすめな本

人気記事②:現役エンジニアがおすすめするプログラミングスクール5社:無料あり