前回の続き。
アカウントの情報がハードコードだと管理が面倒なので、データベースを使おう。その際、保存するパスワードはハッシュ化しよう。
今回使うページを次に示す。
パス | アクセス制限 | 備考 |
---|---|---|
/suppl01/admin/ | 管理人ロールをもつ登録ユーザ | 管理人用トップページ |
/suppl01/admin/add_user | 管理人ロールをもつ登録ユーザ | ユーザ登録フォーム |
アカウント情報を格納するテーブル
40.1 User Schemaにあるようにテーブルを作ると、簡単にアカウント情報として利用することができる(もちろん設定をすれば他の構成でも使えるが)。今回は、これを参考に次のSQL命令でテーブルを作ることにする。
1 2 3 4 5 6 7 8 9 10 11 12 |
create table IF NOT EXISTS users ( username varchar_ignorecase(50) not null primary key, password varchar_ignorecase(100) not null, enabled boolean not null ); create table IF NOT EXISTS authorities ( username varchar_ignorecase(50) not null, authority varchar_ignorecase(50) not null, constraint fk_authorities_users foreign key(username) references users(username) ); create unique index IF NOT EXISTS ix_auth_username on authorities (username,authority); |
40.1 User Schemaからの変更点は以下の通り。
- passwordカラムは、ハッシュ化したパスワードを保存する為にサイズを100に変更
- すでに存在している場合は作成しないようにするために「IF NOT EXISTS」を追加
このSQL命令をH2コンソールで実行してもいいし、JdbcTemplateで実行してもいいのだが、ファイルに記述したSQL命令をアプリの起動時に実行する方法(78.3 Initialize a database using Spring JDBC)がある。
今回はこの方法を使うことにし、src/main/resources/schema.sqlを作成する。
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 33 34 35 36 37 38 39 40 |
package net.teachingprogramming.webapp; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; 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; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth, JdbcTemplate jdbcTemplate) throws Exception { // アカウントの設定(ハードコード) auth.inMemoryAuthentication().withUser("admin").password("adminpassword").roles("ADMIN"); auth.inMemoryAuthentication().withUser("user1").password("user1password").roles("USER"); // アカウントの設定(データベース)、利用するパスワードエンコーダの設定 PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); auth.jdbcAuthentication().dataSource(jdbcTemplate.getDataSource()).passwordEncoder(passwordEncoder); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/suppl01/secret/**").authenticated() // 「/suppl01/secret/**」は認証が必要(ロールは問わず) .antMatchers("/suppl01/admin/**").hasRole("ADMIN") // 「/suppl01/admin/**」は管理者ロールが必要 .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/"); } } |
- 16行目: 設定時にJdbcTemplate(かDataSourcedの)インスタンスが必要なので、引数として「JdbcTemplate jdbcTemplate』を追加し、DIコンテナから取得する。
- 21行目: パスワードのエンコーダとして使うBCryptPasswordEncoderをインスタンス化する。
- 22行目: JDBCを使った認証で、どのデータベースを使うか(jdbcTemplate.getDataSource())とどのパスワードエンコーダを使うか(passwordEncoder)を指定する。
- 29行目: 「/suppl01/admin/**」へのアクセスへは管理者ロールを持つアカウントがアクセスできることを指定する。
コントローラ
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
/** * 管理者用トップページ */ @GetMapping("/admin/") public String adminIndex() { return "suppl01/admin/index"; } /** * ユーザの追加(GET、フォームを表示) */ @GetMapping("/admin/add_user") public String adminAddUserGet() { return "suppl01/admin/add_user"; } /** * ユーザの追加(POST、データベースを操作) */ @PostMapping("/admin/add_user") public String adminAddUserPost(@RequestParam("username") String username, @RequestParam("password") String password, @RequestParam("role") String role) { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); jdbcTemplate.update("INSERT INTO users ( username, password , enabled ) VALUES (?, ?, ?)", username, passwordEncoder.encode(password), true); jdbcTemplate.update("INSERT INTO authorities ( username, authority) VALUES (?, ?)", username, role); return "redirect:/suppl01/admin/"; } |
- 86行目: SecurityConfiguration.javaと同様にパスワードエンコーダをインスタンス化する。
- 87ページ: パスワードエンコーダのencodeメソッドを使ってパスワードをエンコードしたものを保存する。
- 88ページ: ロールをauthoritiesテーブルに保存する。usersテーブルの情報だけではダメなことに注意する。
テンプレート
add_user.html
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 |
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>Secret</title> </head> <body> <h1>Suppl01</h1> <h2>Admin: ユーザを追加</h2> <form th:action="@{add_user}" method="post"> ユーザ名: <input name="username" /><br/> パスワード: <input name="password" type="password"/><br/> ロール: <select name="role"><option value="ROLE_USER">一般ユーザ</option><option value="ROLE_ADMIN">管理者</option></select><br/> <button type="submit">追加</button> </form> <ul> <li><a href="/suppl01/admin/">戻る</a></li> </ul> <form th:action="@{/suppl01/logout}" method="post"> <button type="submit">ログアウト</button> </form> </body> </html> |
- 14行目: ロールは「ROLE_」をつけることに注意する。
動作確認
ハードコードしたadminでログインし、一般ユーザ、管理者ユーザを作成し、それらのユーザで正しくアクセスできること等を確かめてみよう。