【Spring Security はじめました】#4 ログインページの作成

Web

今回の目標

前回はDB認証について説明をしました。UserDetailsServiceの実装により、ユーザー情報を検索することでDB認証を行えるようになりました。

【Spring Security はじめました】#3 DB認証
今回はSpring SecurityでDB認証を行う方法について説明します。DB認証を行うためにはUserDetailsServiceでユーザーを検索する処理を実装する必要があります。

今回はログインページのカスタマイズについて説明します。

デフォルトページの確認

ログインページ

ログインページは「/login」にアクセスすることで表示されます。

<body cz-shortcut-listen="true">
  <div class="container">
    <form class="form-signin" method="post" action="/login">
      <h2 class="form-signin-heading">Please sign in</h2>
      <p>
        <label for="username" class="sr-only">Username</label>
        <input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus="">
      </p>
      <p>
        <label for="password" class="sr-only">Password</label>
        <input type="password" id="password" name="password" class="form-control" placeholder="Password" required="">
      </p>
      <input name="_csrf" type="hidden" value="3ac2d8bf-5dff-4e89-b411-aabeb671669b">
      <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
    </form>
  </div>
</body>

上記は実際にDOMに展開されたものです。ここからわかることは、

  1. POSTメソッドで「/login」にリクエストを送信している
  2. username、password、_csrfをパラメーターとして送信している

ということです。

つまり最低限このルールに従ってログインページをカスタマイズすれば良いということになります。

またパスワード間違えなどによるログイン失敗時は「/login?error」へ、ログアウト時は「/login?logout」へリダイレクトされています。

ログアウトページ

ログアウトページは「/logout」にアクセスすることで表示されます。

<form class="form-signin" method="post" action="/logout">
  <h2 class="form-signin-heading">Are you sure you want to log out?</h2>
  <input name="_csrf" type="hidden" value="ed501316-8f64-4fdc-809c-2feb7c8276cb">
  <button class="btn btn-lg btn-primary btn-block" type="submit">Log Out</button>
</form>

ここから、

  1.  POSTメソッドで「/logout」にリクエストを送信している
  2.  _csrfをパラメータとして送信している

ということが分かります。

ログインページの作成

「/login」にアクセスした場合に、カスタムしたログインページが表示されるようコントローラーとHTMLファイルを作成します。

  • コントローラー
@GetMapping("/login")
public String login() {
  return "login";
}
  • HTMLファイル(Thymeleaf)
<form th:action="@{/login}" method="post">
  <input type="text" id="id" name="id" placeholder="ID" />
  <input type="password" id="password" name="password" placeholder="Password" />
  <button type="submit">ログイン</button>
</fotm>

とりあえずは必要最低限の実装にしてあります。_csrfについては後述しますので今は無視してください。

また意図的にusernameをidに変更してあります。

実はこれだけでは作成したページは表示されません。これに加え設定の変更が必要になります。

ログインページに関する設定変更

これまでの記事でも設定クラスのconfigureメソッドをオーバーライドすることで、Spring Securityに関する様々な設定をしてきました。

ログインページに関する設定はHttpSecurityを引数に持つconfigureメソッドに手を加えます。

対象となるのは下記のformLogin()です。formLogin()ではログインフォームの設定情報を持つFormLoginConfigureというクラスのインスタンスを返しています。

@Override
protexted void configure(HttpSecurity auth) throws Exception {
  http.authorizeRequests().antMatchers("/home").permitAll()
      .antMatchers("/admin").hasRole("ADMIN")
      .anyRequest().authenticated()
      .and()
      .formLogin()
      .and()
      .httpBasic();
}

ログインページの変更

ログインページのパスを設定するには、以下のようにloginPage()を使用します。ログインページはすべてのユーザーがアクセスできるようにpermitAll()で認可する必要があります。

.formLogin().loginPage("/login").permitAll()

デフォルトのログインページと同じパスを指定していますが、これで作成したページが表示されるようになります。
実はloginPage()によって「カスタムしたログインページを使うよー」と内部的に切り替えをしてくれます。

パラメータ名の変更

先程作成したフォームでは、usernameではなくidというパラメータを送信するようにしています。当然このままでは認証はされません。

このように異なるパラメータ名を使用したい場合は次のような設定をします。

.formLogin().loginPage("/login").permitAll()
            .usernameParameter("id")

仮にパスワードのパラメータ名(password)を変更したい場合はpasswordParameter()を使用します。

ログイン成功後のリダイレクト先

ログインに成功した場合に、どのページにリダイレクトするかという設定になります。デフォルトではログインページへのリダイレクト前のページとなります。

例えば「/page1」にアクセスしようとして「/login」にリダイレクトされた場合、ログイン後は「/page1」にリダイレクトされます。

ただしそのままの設定だと、直接「/login」にアクセスした場合に、ログイン後は「/error」にリダイレクトされてしまいます。

そうならないようにするため、以下のようにデフォルトのリダイレクト先を設定します。

.formLogin().loginPage("/login").permitAll()
            .usernameParameter("id")
            .defaultSuccessUrl("/login-success")

これで直接「/login」にアクセスした場合は、設定した「/login-success」へリダイレクトされます。

直接「/login」にアクセスしなかった場合にも「/login-success」へリダイレクトさせたい場合は、defaultSuccessUrl()の第二引数にtrueを設定します。

.formLogin().loginPage("/login").permitAll()
            .usernameParameter("id")
            .defaultSuccessUrl("/login-success", true)

ログイン失敗時のリダイレクト先

ログインに失敗した場合、デフォルトでは「/login?error」にリダイレクトされます。フォームにエラーメッセージを表示したい場合は次のように設定したパラメータを参照します。

<p th:if="${param.error}">ログインに失敗しました</p>

デフォルトのリダイレクト先を変更したい場合は以下のように設定します。

.formLogin().loginPage("/login").permitAll()
            .usernameParameter("id")
            .defaultSuccessUrl("/login-success", true)
            .failureUrl("/login-error")

ログアウトの実装

ログアウトの実装はシンプルで、デフォルトで実装されているようにPOSTで「/logout」に送信するだけです。

<form th:action="@{/logout}" method="post">
  <button type="submit">ログアウト</button>
</form>

デフォルトではログアウトすると「/login?logout」にリダイレクトされます。このリダイレクト先を変更したい場合は、ログインと同様に設定を変更します。

@Override
protexted void configure(HttpSecurity auth) throws Exception {
  http.authorizeRequests().antMatchers("/home").permitAll()
                          .antMatchers("/admin").hasRole("ADMIN")
                          .anyRequest().authenticated()
      .and()
      .formLogin().loginPage("/login").permitAll()
                  .usernameParameter("id")
                  .defaultSuccessUrl("/login-success", true)
                  .failureUrl("/login-error")
      .and()
      .logout().logoutSuccessUrl("/logout").permitAll();
      .and()
      .httpBasic();

CSRF対策

CSRF(クロスサイトリクエストフォージェリ)は、WEBアプリケーションの脆弱性、またそれを利用した攻撃方法の1つです。すごく簡単に説明しておくと、正規でないリクエストを受け取れる状態になっています。

CSRFの対策にはワンタイムトークンを使用します。流れとしては次の通りです。

  1. (サーバー)  ページ表示時にトークンを発行し、一緒に送信する。トークンはセッションで保持する。
  2. (クライアント)フォームの送信時に、サーバーから受け取ったトークンを送る。
  3. (サーバー)  保持したトークンとクライアントから送られたトークンを照合する。

Spring Securityではデフォルトでこの対策が行われています。ログインやログアウトのフォームの_csrfはCSRF対策用のトークンだったということです。これが送信されていない場合はエラーになります。

ではもう一度自作したログインページを見てみましょう。

<form th:action="@{/login}" method="post">
  <input type="text" id="id" name="id" placeholder="ID" />
  <input type="password" id="password" name="password" placeholder="Password" />
  <button type="submit">ログイン</button>
</fotm>

_csrfが設定されていませんが、これでも動作します。実はThymeleafで「th:action=”@{/login}”」とすることで_csrfが自動で展開されます。実際にブラウザからDOMに展開された要素を確認してみてください。

また単に「action=”/login”」とした場合の動作も確認してみてください。リクエストをみると_csrfが送信されずエラーになると思います。

あとがき

今回はログインページのカスタマイズ方法について説明しました。認証処理などはSpring Securityの方で実装されているので、あとはその形式に合わせてページを作るだけです。

またCSRF対策をデフォルトでやってくれるというのもありがたいところです。

 

- Spring Bootのおすすめ書籍はコチラ -

コメント

タイトルとURLをコピーしました