Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その10 )( ログイン画面の作成4 )
概要
Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その9 )( ログイン画面の作成3 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- ID の有効期限を過ぎている場合にはログインできないようにする機能の作成
- パスワードの有効期限を過ぎている場合にはログインできないようにする機能の作成
参照したサイト・書籍
PostgreSQL 9.4.0文書 - 第 9章関数と演算子 - 9.9. 日付/時刻関数と演算子
https://www.postgresql.jp/document/9.4/html/functions-datetime.htmlJava 最強リファレンス
- 作者: 田中裕一
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/10/27
- メディア: 大型本
- この商品を含むブログ (1件) を見る
- 「12-07 2つの日時を比較する」を参照しました。
倖せの迷う森 - Spring Framework Advent Calendar 2011 part.7 - Spring Security で認証成功時に条件によって遷移先を変える
http://d.hatena.ne.jp/ocs/20111207/1323269801- 今回実装していませんが、ログイン時にパスワードの有効期限を過ぎていたらパスワード変更画面にリダイレクトさせる記事を見つけました。今後実装する時のために備忘録としてメモしておきます。
目次
- はじめに
- 1.0.x-expired-idpass ブランチの作成
- user_info テーブルの変更
- UserInfoUserDetails クラスの変更
- UserInfoUserDetailsService クラスの変更
- 動作確認
- commit、Push、Pull Request、マージ
手順
はじめに
ID とパスワードに有効期限を設けて、有効期限を過ぎている場合にはエラーメッセージを表示してログインできないようにします。
以下の内容で実装を進めます。
- user_info テーブルに ID、パスワードそれぞれの有効期限を設定するためのカラムを追加します。
- UserInfoUserDetails クラスで現在日時と有効期限を比較して、現在日時を超えている場合には accountNonExpired, credentialsNonExpired を適宜 false に設定します。
1.0.x-expired-idpass ブランチの作成
- IntelliJ IDEA で 1.0.x-expired-idpass ブランチを作成します。
user_info テーブルの変更
/sql の下の create_table.sql を リンク先の内容 に変更します。
コマンドプロンプトから以下のコマンドを実行して user_info テーブルに expired_account, expired_password カラムを追加します。
C:\project-springboot\ksbysample-webapp-lending>psql -U ksbylending_user ksbylending ユーザ ksbylending_user のパスワード: psql (9.4.1) "help" でヘルプを表示します. ksbylending=> alter table user_info add column expired_account timestamp not null default now() + interval '90 day'; ALTER TABLE ksbylending=> alter table user_info add column expired_password timestamp not null default now() + interval '30 day'; ALTER TABLE ksbylending=> \d user_info テーブル "public.user_info" 列 | 型 | 修 飾語 --------------------+-----------------------------+------------------------------------------------------------- user_id | bigint | not null default nextval('user_info_user_id_seq'::regclass) username | character varying(32) | not null password | character varying(256) | not null mail_address | character varying(256) | not null enabled | smallint | not null default 1 cnt_badcredentials | smallint | not null default 0 expired_account | timestamp without time zone | not null default (now() + '90 days'::interval) expired_password | timestamp without time zone | not null default (now() + '30 days'::interval) インデックス: "user_info_pkey" PRIMARY KEY, btree (user_id) "user_info_idx_01" btree (mail_address) 参照元: TABLE "user_role" CONSTRAINT "user_role_user_id_fkey" FOREIGN KEY (user_id) REFERENCES user_info(user_id) ON DELETE CASCADE ksbylending=> \q C:\project-springboot\ksbysample-webapp-lending>
IntelliJ IDEA の Database Tools の「Shynchronize」ボタンをクリックして変更内容を反映します。
登録済の username = 'tanaka taro' のデータを見ると expired_account, expired_password のカラムにはカラムを追加した日時の 90日後、30日後の日時がセットされていました。
Gradle projects View から gen タスクを実行して user_info テーブルの Entity クラスを作成し直します。
UserInfoUserDetails クラスの変更
UserInfoUserDetailsService クラスの変更
- src/main/java/ksbysample/webapp/lending/security の下の UserInfoUserDetailsService.java を リンク先の内容 に変更します。
動作確認
Gradle projects View から bootRun タスクを実行して Tomcat を起動します。
まずは正常にログインできることを確認します。ブラウザを起動して http://localhost:8080/ にアクセスし、ログイン画面を表示します。
ID に "tanaka.taro@sample.com"、Password に "taro" を入力後「ログイン」ボタンをクリックして、「ログイン成功!」の画面が表示されることを確認します。
次に ID の有効期限を過ぎている場合にログインできないことを確認します。user_info テーブルの username = 'tanaka taro' のデータの expired_account の値を昨日の日付に変更します。
ブラウザで http://localhost:8080/ にアクセスしてログイン画面を表示した後、ID に "tanaka.taro@sample.com"、Password に "taro" を入力して「ログイン」ボタンをクリックします。
ログインはできず「入力された ID の有効期限が切れています」のメッセージが表示されました。
user_info テーブルの username = 'tanaka taro' のデータの expired_account の値を元に戻します。
最後にパスワードの有効期限を過ぎている場合にログインできないことを確認します。user_info テーブルの username = 'tanaka taro' のデータの expired_password の値を昨日の日付に変更します。
ブラウザで http://localhost:8080/ にアクセスしてログイン画面を表示した後、ID に "tanaka.taro@sample.com"、Password に "taro" を入力して「ログイン」ボタンをクリックします。
ログインはできず「入力された ID のパスワードの有効期限が切れています」のメッセージが表示されました。
user_info テーブルの username = 'tanaka taro' のデータの expired_password の値を元に戻します。
Ctrl+F2 を押して Tomcat を停止します。
commit、Push、Pull Request、マージ
commit します。
GitHub へ Push、1.0.x-expired-idpass -> 1.0.x へ Pull Request、1.0.x でマージ、1.0.x-expired-idpass ブランチを削除、をします。
ソースコード
create_table.sql
create table user_info ( user_id bigserial primary key , username varchar(32) not null , password varchar(256) not null , mail_address varchar(256) not null , enabled smallint not null default 1 , cnt_badcredentials smallint not null default 0 , expired_account timestamp not null default now() + interval '90 day' , expired_password timestamp not null default now() + interval '30 day' ); create index user_info_idx_01 on user_info(mail_address);
- expired_account, expired_password の2つのカラムを追加します。
- expired_account には登録日時から 90日後、expired_password には 30日後の日時がデフォルトで設定されるようにします。
UserInfoUserDetails.java
package ksbysample.webapp.lending.security; import ksbysample.webapp.lending.entity.UserInfo; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.time.LocalDateTime; import java.util.Collection; import java.util.Set; public class UserInfoUserDetails implements UserDetails { private UserInfo userInfo; private final Set<? extends GrantedAuthority> authorities; private final boolean accountNonExpired; private final boolean accountNonLocked; private final boolean credentialsNonExpired; private final boolean enabled; public UserInfoUserDetails(UserInfo userInfo , Set<? extends GrantedAuthority> authorities) { LocalDateTime now = LocalDateTime.now(); this.userInfo = userInfo; this.authorities = authorities; this.accountNonExpired = !userInfo.getExpiredAccount().isBefore(now); this.accountNonLocked = (userInfo.getCntBadcredentials() < 5); this.credentialsNonExpired = !userInfo.getExpiredPassword().isBefore(now); this.enabled = (userInfo.getEnabled() == 1); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return userInfo.getPassword(); } @Override public String getUsername() { return userInfo.getUsername(); } public String getMailAddress() { return userInfo.getMailAddress(); } @Override public boolean isAccountNonExpired() { return accountNonExpired; } @Override public boolean isAccountNonLocked() { return accountNonLocked; } @Override public boolean isCredentialsNonExpired() { return credentialsNonExpired; } @Override public boolean isEnabled() { return enabled; } }
- コンストラクタの以下の点を変更します。
- 引数から boolean accountNonExpired、boolean credentialsNonExpired を削除します。
- this.accountNonExpired にセットする値を
!userInfo.getExpiredAccount().isBefore(now)
へ変更します。 - this.credentialsNonExpired にセットする値を
!userInfo.getExpiredPassword().isBefore(now)
に変更します。
UserInfoUserDetailsService.java
package ksbysample.webapp.lending.security; import ksbysample.webapp.lending.dao.UserInfoDao; import ksbysample.webapp.lending.dao.UserRoleDao; import ksbysample.webapp.lending.entity.UserInfo; import ksbysample.webapp.lending.entity.UserRole; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Component public class UserInfoUserDetailsService implements UserDetailsService { @Autowired private UserInfoDao userInfoDao; @Autowired private UserRoleDao userRoleDao; @Autowired private MessageSource messageSource; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserInfo userInfo = userInfoDao.selectByMailAddress(username); if (userInfo == null) { throw new UsernameNotFoundException( messageSource.getMessage("UserInfoUserDetailsService.usernameNotFound" , new Object[]{}, LocaleContextHolder.getLocale())); } Set<SimpleGrantedAuthority> authorities = new HashSet<>(); List<UserRole> userRoleList = userRoleDao.selectByUserId(userInfo.getUserId()); if (userRoleList != null) { authorities.addAll( userRoleList.stream().map(userRole -> new SimpleGrantedAuthority(userRole.getRole())) .collect(Collectors.toList())); } return new UserInfoUserDetails(userInfo, authorities); } }
- loadUserByUsername メソッドの最後の return 文から最後の
, true, true
を削除します。
履歴
2015/07/19
初版発行。