Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その45 )( 貸出承認画面の作成5 )
概要
Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その44 )( 貸出承認画面の作成4 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 貸出承認画面の作成
- 承認権限 ( ROLE_APPROVER ) を付与されていないユーザが /lendingapproval にアクセスした場合にはエラーにする処理の実装。
- 貸出承認画面の作成
参照したサイト・書籍
23.Expression-Based Access Control
https://docs.spring.io/spring-security/site/docs/current/reference/html/el-access.html- Spring Security で、Spring EL を使用して権限ベースのアクセス制御を行う方法が記述されているドキュメントです。
- このドキュメントだと
@PreAuthorize("hasRole('USER')")
と書かれていますが、これは Spring Security 4 から可能な記述なので ( "ROLE_" が不要になります )、Spring Security 3 を使用している ksbysample-webapp-lending では@PreAuthorize("hasRole('ROLE_USER')")
と記述します。
目次
手順
ROLE_USER のみ持つユーザの作成
動作確認をするのに ROLE_USER のみを持つ使用可能なユーザがないことに気づいたので作成します。
- username は "ito aoi" にします。
- password は "aoi" です。パスワードの暗号化文字列は http://localhost:8080/encode?password=aoi で生成します。
- ROLE は ROLE_USER のみ付与します。
src/test/resources/testdata/base の下の user_info.csv, user_role.csv を リンク先の内容 に変更します。
追加したデータを IntelliJ IDEA の Database tool から手動で追加します。
src/test/java/ksbysample/common/test の下の SecurityMockMvcResource.java を リンク先の内容 に変更します。
一旦 commit します。
LendingapprovalController クラスの変更
- src/main/java/ksbysample/webapp/lending/web/lendingapproval の下の LendingapprovalController.java を リンク先の内容 に変更します。
動作確認
動作確認します。Gradle projects View から bootRun タスクを実行して Tomcat を起動します。
最初に ROLE_APPROVER 権限を付与されているユーザで貸出承認画面が表示されることを確認します。
ブラウザを起動し http://localhost:8080/lendingapproval?lendingAppId=105 へアクセスします。ログイン画面が表示されますので ID に "suzuki.hanako@test.co.jp"、Password に "hanako" を入力して、「次回から自動的にログインする」をチェックせずに「ログイン」ボタンをクリックします。
貸出承認画面が表示されることが確認できます。
次に ROLE_APPROVER 権限を付与されていないユーザで貸出承認画面が表示されないことを確認します。
画面右上の「ログアウト」リンクをクリックしてログアウトした後、http://localhost:8080/lendingapproval?lendingAppId=105 へアクセスします。ログイン画面が表示されますので ID に "ito.aoi@test.co.jp"、Password に "aoi" を入力して、「次回から自動的にログインする」をチェックせずに「ログイン」ボタンをクリックします。
貸出承認画面は表示されず、共通エラー画面が表示されることが確認できます。画面上には "Access is denied" のメッセージが表示されます。
共通エラー画面が表示された時の HTTPステータスコードもアクセス禁止を示す 403 ( Forbidden ) が返ってきています。Fiddler ( http://www.telerik.com/fiddler ) というツールで確認しています。
※共通エラー画面表示時に 403 が返るのは src/main/java/ksbysample/webapp/lending/web/ExceptionHandlerAdvice.java の
if (e instanceof org.springframework.security.access.AccessDeniedException) { response.setStatus(HttpStatus.FORBIDDEN.value()); }
の実装によるものです。Ctrl+F2 を押して Tomcat を停止します。
一旦 commit します。
次回は。。。
テストを作成します。
ソースコード
user_info.csv, user_role.csv
■user_info.csv
user_id,username,password,mail_address,enabled,cnt_badcredentials,expired_account,expired_password 1,"tanaka taro",$2a$10$LKKepbcPCiT82NxSIdzJr.9ph.786Mxvr.VoXFl4hNcaaAn9u7jje,tanaka.taro@sample.com,1,0,"9999-12-31 23:59:00.000000","9999-12-31 23:59:00.000000" 2,"suzuki hanako",$2a$10$.fiPEZ155Rl41/e.mdM3A.mG0iEQNPmhjFL/aIiV8dZnXsCd.oqji,suzuki.hanako@test.co.jp,1,0,"9999-12-31 23:59:00.000000","9999-12-31 23:59:00.000000" 3,"kimura masao",$2a$10$yP1dLPIq9j7WQVH6ruSwkepf8jIkPxTtncbSnYM0/jAGQ4HCQO8R.,kimura.masao@test.co.jp,0,0,"2015-12-31 22:30:54.425000","2015-10-15 22:31:03.316000" 4,"endo yoko",$2a$10$PVFe8Lh1Pkjc54DWS9mJL.q407x51ZK8MSXhwuTF9zxCnnt80LKwy,endo.yoko@sample.com,1,0,"2015-01-10 22:31:55.454000","2015-12-31 22:32:11.886000" 5,"sato masahiko",$2a$10$qIU0kM/p1pa7KSIjF6YA4eORd2wL1Eo6TlvH./DmPs7D.xXQPEq7a,sato.masahiko@sample.com,1,0,"9999-12-31 23:59:00.000000","2014-08-05 22:34:22.818000" 6,"takahasi naoko",$2a$10$iXp/d4wXmfaLKTjQKBvik.kETgx4nQ.FL1NjYt4ALJOGSyVOSchW6,takahasi.naoko@test.co.jp,1,0,"2015-12-01 22:39:48.475000","2015-11-10 22:39:55.422000" 7,"kato hiroshi",$2a$10$g5dtFTtNBdJO30aHg50rluGNa2pEAzArcwYkYyCG91ElBZPs9sDi2,kato.hiroshi@sample.com,0,5,"2014-01-01 15:58:53.295000","2013-12-31 15:59:07.668000" 8,"ito aoi",$2a$10$wD9VL.2cfiL0UY05N4PAs.l0Zd/X9CC0LouzGaHqp2W6PKLjQXJNq,ito.aoi@test.co.jp,1,0,"9999-12-31 23:59:00.000000","9999-12-31 23:59:00.000000"
8,"ito aoi",...
のデータを追加します。
■user_role.csv
role_id,user_id,role 1,1,ROLE_USER 2,1,ROLE_ADMIN 3,1,ROLE_APPROVER 4,2,ROLE_USER 5,2,ROLE_APPROVER 6,3,ROLE_USER 7,4,ROLE_USER 8,5,ROLE_USER 9,8,ROLE_USER
9,8,ROLE_USER
のデータを追加します。
SecurityMockMvcResource.java
package ksbysample.common.test; import org.junit.rules.ExternalResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import javax.servlet.Filter; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @Component public class SecurityMockMvcResource extends ExternalResource { public final String MAILADDR_TANAKA_TARO = "tanaka.taro@sample.com"; public final String MAILADDR_SUZUKI_HANAKO = "suzuki.hanako@test.co.jp"; public final String MAILADDR_KIMURA_MASAO = "kimura.masao@test.co.jp"; public final String MAILADDR_ENDO_YOKO = "endo.yoko@sample.com"; public final String MAILADDR_SATO_MASAHIKO = "sato.masahiko@sample.com"; public final String MAILADDR_TAKAHASI_NAOKO = "takahasi.naoko@test.co.jp"; public final String MAILADDR_ITO_AOI = "ito.aoi@test.co.jp"; @Autowired private WebApplicationContext context; @Autowired private Filter springSecurityFilterChain; @Autowired private UserDetailsService userDetailsService; public MockMvc authTanakaTaro; public MockMvc authSuzukiHanako; public MockMvc authItoAoi; public MockMvc noauth; @Override protected void before() throws Throwable { // 認証ユーザ用MockMvc ( user = tanaka.taro@sample.com ) UserDetails userDetailsTanakaTaro = userDetailsService.loadUserByUsername(MAILADDR_TANAKA_TARO); this.authTanakaTaro = MockMvcBuilders.webAppContextSetup(this.context) .defaultRequest(get("/").with(user(userDetailsTanakaTaro))) .addFilter(springSecurityFilterChain) .build(); // 認証ユーザ用MockMvc ( user = suzuki.hanako@test.co.jp ) UserDetails userDetailsSuzukiHanako = userDetailsService.loadUserByUsername(MAILADDR_SUZUKI_HANAKO); this.authSuzukiHanako = MockMvcBuilders.webAppContextSetup(this.context) .defaultRequest(get("/").with(user(userDetailsSuzukiHanako))) .addFilter(springSecurityFilterChain) .build(); // 認証ユーザ用MockMvc ( user = ito.aoi@test.co.jp ) UserDetails userDetailsItoAoi = userDetailsService.loadUserByUsername(MAILADDR_ITO_AOI); this.authItoAoi = MockMvcBuilders.webAppContextSetup(this.context) .defaultRequest(get("/").with(user(userDetailsItoAoi))) .addFilter(springSecurityFilterChain) .build(); // 非認証ユーザ用MockMvc this.noauth = MockMvcBuilders.webAppContextSetup(this.context) .addFilter(springSecurityFilterChain) .build(); } }
public final String MAILADDR_ITO_AOI = "ito.aoi@test.co.jp";
を追加します。public MockMvc authItoAoi;
を追加します。- before メソッド内に
// 認証ユーザ用MockMvc ( user = ito.aoi@test.co.jp )
の処理を追加します。
LendingapprovalController.java
package ksbysample.webapp.lending.web.lendingapproval; import ksbysample.webapp.lending.exception.WebApplicationRuntimeException; import ksbysample.webapp.lending.helper.message.MessagesPropertiesHelper; import ksbysample.webapp.lending.helper.thymeleaf.SuccessMessagesHelper; import org.seasar.doma.jdbc.OptimisticLockException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import javax.mail.MessagingException; @Controller @PreAuthorize("hasRole('ROLE_APPROVER')") @RequestMapping("/lendingapproval") public class LendingapprovalController { @Autowired private LendingapprovalService lendingapprovalService; @Autowired private MessagesPropertiesHelper messagesPropertiesHelper; @Autowired private LendingapprovalFormValidator lendingapprovalFormValidator; @InitBinder(value = "lendingapprovalForm") public void initBinder(WebDataBinder binder) { binder.addValidators(lendingapprovalFormValidator); } @RequestMapping public String index(@Validated LendingapprovalParamForm lendingapprovalParamForm , BindingResult bindingResult , LendingapprovalForm lendingapprovalForm , BindingResult bindingResultOfLendingapprovalForm) { if (bindingResult.hasErrors()) { throw new WebApplicationRuntimeException( messagesPropertiesHelper.getMessage("LendingapprovalParamForm.lendingAppId.emptyerr", null)); } // 画面に表示するデータを取得する lendingapprovalService.setDispData(lendingapprovalParamForm.getLendingAppId(), lendingapprovalForm); // 指定された貸出申請IDで申請中、承認済のデータがない場合には、貸出承認画面上にエラーメッセージを表示する if (lendingapprovalForm.getLendingApp() == null) { bindingResultOfLendingapprovalForm.reject("LendingapprovalForm.lendingApp.nodataerr"); } return "lendingapproval/lendingapproval"; } @RequestMapping(value = "/complete", method = RequestMethod.POST) public String complete(@Validated LendingapprovalForm lendingapprovalForm , BindingResult bindingResult , Model model) throws MessagingException { if (bindingResult.hasErrors()) { return "lendingapproval/lendingapproval"; } try { // データを更新し、承認完了メールを送信する lendingapprovalService.complete(lendingapprovalForm); // 画面に表示するデータを取得する lendingapprovalService.setDispData(lendingapprovalForm.getLendingApp().getLendingAppId(), lendingapprovalForm); // 画面に表示する通常メッセージをセットする SuccessMessagesHelper successMessagesHelper = new SuccessMessagesHelper("確定しました"); successMessagesHelper.setToModel(model); } catch (OptimisticLockException e) { bindingResult.reject("Global.optimisticLockException"); } return "lendingapproval/lendingapproval"; } }
- クラスに
@PreAuthorize("hasRole('ROLE_APPROVER')")
アノテーションを付加します。
履歴
2016/01/19
初版発行。