フォームからユーザ名とパスワードを入力してログインする機能(ユーザ認証)を実現しよう!
今回使うページを次に示す。
パス | アクセス制限 | 備考 |
---|---|---|
/suppl01/ | 誰でも | トップページ |
/suppl01/login | 誰でも | ログインフォーム |
/suppl01/loginProcess | 誰でも | ログイン処理を行う(ログインフォームからのPOST先) |
/suppl01/logout | 誰でも | ログアウト処理を行う(ログアウトフォームからのPOST先) |
/suppl01/secret/ | 登録ユーザ | ログイン後に遷移するページ |
/suppl01/secret/info | 登録ユーザ | アカウントの情報を表示するページ |
Spring Securityの有効化とBasic認証の無効化
Spring Securityの機能を加えよう。
build.gradleのdependenciesに次の1行を加える。
1 |
compile('org.springframework.boot:spring-boot-starter-security') |
これでSpring Securityの機能が有効になる。デフォルトでBasic認証の機能が有効になるので、
パスワードとユーザ名を入れないとページが見えなくなる。
今回はBasic認証は使わないので無効にしよう。無効にするにはapplication.propertiesに次の1行を追加する。
1 |
security.basic.enabled = false |
WebSecurityConfigurerAdapterを継承した設定用クラス
WebSecurityConfigurerAdapterクラスを継承したクラスを作ることで、ユーザ認証の設定を行うことができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package net.teachingprogramming.webapp; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // アカウントの設定 auth.inMemoryAuthentication().withUser("admin").password("adminpassword").roles("ADMIN"); auth.inMemoryAuthentication().withUser("user1").password("user1password").roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { // 「/suppl01/secret/**」は認証が必要で、それ以外は認証が不要 http.authorizeRequests().antMatchers("/suppl01/secret/**").authenticated().anyRequest().permitAll(); // ログイン http.formLogin().loginPage("/suppl01/login").usernameParameter("username").passwordParameter("password") .loginProcessingUrl("/suppl01/loginProcess").defaultSuccessUrl("/suppl01/secret/").failureUrl("/suppl01/login?error"); // ログアウト http.logout().logoutUrl("/suppl01/logout").logoutSuccessUrl("/suppl01/"); } } |
- 9行目: このクラスが設定用のクラスであることを示す。これに加え「@EnableWebSecurity」を記述するという解説が様々なところでなされているが、この設定については82.1 Switch off the Spring Boot security configurationを参照すること。
- 14〜16行目: ログインできるアカウントを設定する。今回はハードコーディングする(次回、データベースに変更する)。
- 21〜22行目: ユーザ認証を要求するページとしないページを設定する。
- 24〜26行目: ログインに関する設定をする。ログインフォームのURLと、ログインフォームの中でどのパラメータ名(input要素のname属性)を使うか、ログインを処理するURL(ログインフォームのactionで指定するパス)、成功した時に遷移するURL、ログイン失敗した時に遷移するURLを指定する。
- 28〜29行目: ログアウトに関する設定を行う。ログアウトを処理するURL(ログアウトフォームのactionで指定するパス)、ログアウトが成功した時に遷移するURLを指定する。
コントローラ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package net.teachingprogramming.webapp; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/suppl01") public class Suppl01Controller { /** * トップページ */ @GetMapping("/") public String index() { return "suppl01/index"; } /** * ログインフォームを表示する */ @GetMapping("/login") public String login() { return "suppl01/login"; } /** * ログイン後に遷移するページ */ @GetMapping("/secret/") public String secretIndex() { return "suppl01/secret/index"; } /** * ログイン中のユーザの情報を表示するページ */ @GetMapping("/secret/info") public String secretInfo(@AuthenticationPrincipal UserDetails userDetails, ModelMap modelMap) { modelMap.addAttribute("username", userDetails.getUsername()); return "suppl01/secret/info"; } } |
- ログインを処理する「/suppl01/loginProcess」とログアウトを処理する「/suppl01/logout」はコントローラで定義しない(Spring Secutiryの機能でアクセスできるようになるため)。
- 42行目: ユーザ情報を取得するには、@AuthenticationPrincipalアノテーションをつけたUserDetails型を引数にする。
テンプレート(解説が必要なもの)
login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>ログイン</title> </head> <body> <h1>Suppl01</h1> <h2>ログイン</h2> <p th:if="${param.containsKey('error')}"> <strong style="color: red">ユーザ名またはパスワードが違います。</strong> </p> <form th:action="@{/suppl01/loginProcess}" method="post"> ユーザ名: <input name="username" /><br/> パスワード: <input name="password" type="password"/><br/> <button type="submit">ログイン</button> </form> </body> </html> |
- 10〜12行目: ログインに失敗した場合は「/suppl01/login?error」に遷移する(SecurityConfiguration.javaを確認せよ)。つまり「error」というパラメータが付加される。「param.containsKey(‘error’)」でerrorというパラメータがあるかチェックできる(参考: request/session 属性などに対するウェブコンテキストネームスペース)
- 13行目: methodはpostにする。
- 13行目: actionはh:action属性で指定する(単なるaction属性じゃダメ)。action属性は、th:actionでリンクURL式を使う。このようにすることによって、自動的にCSRF対策用のパラメータが付加される。
- 13行目: URLはSecurityConfiguration.javaのloginProcessingUrlで指定したURLにする。
- 14〜15行目: name属性はSecurityConfiguration.javaで指定した文字列を設定する。
secret/{index,info}.htmlのログアウトフォーム
1 2 3 |
<form th:action="@{/suppl01/logout}" method="post"> <button type="submit">ログアウト</button> </form> |
- ログインの場合と同様に、methodはpostとし、actionはth:action属性で指定する。URLはSecurityConfiguration.javaのlogoutUrlで指定したURLにする。
- 単なるリンクにしたい場合は、logoutRequestMatcherメソッドを使った形にする(調べてみよう)。
問題点
基本的なユーザー認証の仕組みを見てきたが、このままでは不便(ユーザ情報がハードコードで管理が面倒)だし問題がある(パスワードが平文で管理されている)。次回では、ユーザ情報をデータベースに保存し、パスワードをハッシュ化して保存する方法を紹介する。