今回は、Spring Securityを利用して、独自ログイン画面を作成した上で、Spring Securityの認証処理を実装してみたので、そのサンプルプログラムを共有する。
前提条件
下記記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
Spring Securityの認証方法の定義を行うJavaソースの内容は以下の通り。
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | package com.example.demo; 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.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.firewall.DefaultHttpFirewall; @Configuration @EnableWebSecurity public class DemoSecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(WebSecurity web) { //org.springframework.security.web.firewall.RequestRejectedException: //The request was rejected because the URL contained a potentially malicious String ";" //というエラーログがコンソールに出力されるため、下記を追加 DefaultHttpFirewall firewall = new DefaultHttpFirewall(); web.httpFirewall(firewall); } /** * SpringSecurityによる認証を設定 * @param http HttpSecurityオブジェクト * @throws Exception 例外 */ @Override protected void configure(HttpSecurity http) throws Exception { //初期表示画面を表示する際にログイン画面を表示する http.formLogin() //ログイン画面は常にアクセス可能とする .loginPage("/login").permitAll() //ログインに成功したら検索画面に遷移する .defaultSuccessUrl("/") .and() //ログイン画面のcssファイルとしても共通のdemo.cssを利用するため、 //src/main/resources/static/cssフォルダ下は常にアクセス可能とする .authorizeRequests().antMatchers("/css/**").permitAll() .and() //かつ //それ以外の画面は全て認証を有効にする .authorizeRequests().anyRequest().authenticated() .and() //かつ //ログアウト時はログイン画面に遷移する .logout().logoutSuccessUrl("/login").permitAll(); } /** * メモリ上にユーザー・パスワードを格納する処理 * @param auth 認証マネージャー生成ツール * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //ユーザー名「user」、パスワード「pass」が入力されたらログイン可能とする //パスワードエンコーダーを利用しないようにするため、パスワードの先頭に{noop}を //指定している auth.inMemoryAuthentication() .withUser("user").password("{noop}pass").roles("USER"); } } |
configure(HttpSecurity http)メソッドでログイン・ログアウトの認証設定を行い、configure(AuthenticationManagerBuilder auth)メソッドでユーザー・パスワードの格納処理を行っている。また、新規作成するログイン画面でCSSファイルを利用できるようにするよう、「src/main/resources/static/css」フォルダ下を常にアクセス可能にする設定を行っている。
CSSファイルの内容は以下の通りで、ファイルパスを、「src/main/resources/static/css」フォルダ下に変更している。内容は特に変更していない。
1 2 3 4 5 6 | .errorMessage{ color: #FF0000; } .fieldError{ background-color: #FFCCFF; } |
また、コントローラクラスには、以下のログインメソッドの追加を行っている。
1 2 3 4 5 6 7 8 | /** * ログイン画面に遷移する * @return ログイン画面へのパス */ @GetMapping(path = "/login") public String login(){ return "login"; } |
さらに、ログイン画面の内容は以下の通り。
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 | <!DOCTYPE html> <html lang="ja" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <link th:href="@{/css/demo.css}" rel="stylesheet" type="text/css" /> <title>ログイン画面</title> </head> <body> <div th:if="${param.error}" class="errorMessage"> ユーザー名またはパスワードが誤っています。 </div> <form method="post" th:action="@{/login}"> <table border="0"> <tr> <td align="left" valign="top">ユーザー名:</td> <td> <input type="text" id="username" name="username" /> </td> </tr> <tr> <td align="left" valign="top">パスワード:</td> <td> <input type="password" id="password" name="password" /> </td> </tr> </table> <br/><br/> <input type="submit" value="ログイン" /> </form> </body> </html> |
「<form method=”post” th:action=”@{/login}”>」と、th:action属性を利用することにより、独自メソッドでなく、Spring Securityに備わっているログイン認証処理が実行されるようになっている。さらに、Spring Securityのデフォルト値に合わせ、ユーザー名は「username」、パスワードは「password」を利用している。
また、検索画面の内容は以下の通り。
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | <!DOCTYPE html> <html lang="ja" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <link th:href="@{/css/demo.css}" rel="stylesheet" type="text/css" /> <title>index page</title> </head> <body> <p>検索条件を指定し、「検索」ボタンを押下してください。</p><br/> <form method="post" th:action="@{/search}" th:object="${searchForm}"> <!-- 2行エラーがある場合は、エラーメッセージを改行して表示 --> <span th:if="*{#fields.hasErrors('fromBirthYear')}" th:errors="*{fromBirthYear}" class="errorMessage"></span> <span th:if="*{#fields.hasErrors('fromBirthYear') && #fields.hasErrors('toBirthYear')}"> <br/> </span> <span th:if="*{#fields.hasErrors('toBirthYear')}" th:errors="*{toBirthYear}" class="errorMessage"></span> <table border="1" cellpadding="5"> <tr> <th>名前</th> <td><input type="text" th:value="*{searchName}" th:field="*{searchName}" /></td> </tr> <tr> <th>生年月日</th> <td><input type="text" th:value="*{fromBirthYear}" size="4" maxlength="4" th:field="*{fromBirthYear}" th:errorclass="fieldError"/>年 <select th:field="*{fromBirthMonth}" th:errorclass="fieldError" th:classappend="${#fields.hasErrors('fromBirthYear')} ? 'fieldError'"> <option value=""></option> <option th:each="item : *{getMonthItems()}" th:value="${item.key}" th:text="${item.value}"/> </select>月 <select th:field="*{fromBirthDay}" th:errorclass="fieldError" th:classappend="${#fields.hasErrors('fromBirthYear')} ? 'fieldError'"> <option value=""></option> <option th:each="item : *{getDayItems()}" th:value="${item.key}" th:text="${item.value}"/> </select>日~ <input type="text" th:value="*{toBirthYear}" size="4" maxlength="4" th:field="*{toBirthYear}" th:errorclass="fieldError"/>年 <select th:field="*{toBirthMonth}" th:errorclass="fieldError" th:classappend="${#fields.hasErrors('toBirthYear')} ? 'fieldError'"> <option value=""></option> <option th:each="item : *{getMonthItems()}" th:value="${item.key}" th:text="${item.value}"/> </select>月 <select th:field="*{toBirthDay}" th:errorclass="fieldError" th:classappend="${#fields.hasErrors('toBirthYear')} ? 'fieldError'"> <option value=""></option> <option th:each="item : *{getDayItems()}" th:value="${item.key}" th:text="${item.value}"/> </select>日 </td> </tr> <tr> <th>性別</th> <td> <select th:field="*{searchSex}"> <option value=""></option> <option th:each="item : *{getSexItems()}" th:value="${item.key}" th:text="${item.value}"/> </select> </td> </tr> </table> <br/><br/> <input type="submit" value="検索" /> </form> <br/> <form method="post" th:action="@{/logout}"> <button type="submit">ログアウト</button> </form> </body> </html> |
「閉じる」ボタンを「ログアウト」ボタンに変更し、「<form method=”post” th:action=”@{/logout}”>」とth:action属性を利用することにより、独自メソッドでなく、Spring Securityに備わっているログアウト処理が実行されるようになっている。
また、ログイン画面を追加している。さらに、cssファイルのパス変更を受け、CSSファイルを読み込むパスをCSSフォルダ下に変更している。その他のHTMLファイルも、同じような変更を行っている。
application.ymlの内容は以下の通りで、「spring.security.user.name」のユーザー名、「spring.security.user.password」のパスワードを削除している。
1 2 3 4 5 6 7 8 9 10 11 12 13 | server: port: 8084 spring: # DB接続情報 datasource: url: jdbc:oracle:thin:@localhost:1521:xe username: USER01 password: USER01 driverClassName: oracle.jdbc.driver.OracleDriver # 一覧画面で1ページに表示する行数 demo: list: pageSize: 2 |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-security-original-login/demo
サンプルプログラムの実行結果
サンプルプログラムの実行結果は、以下の通り。
1) Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスすると、以下のログイン画面が表示される
2) DemoSecurityConfig.javaに定義したユーザーと違うユーザー名またはパスワードを入力し、「ログイン」ボタンを押下
3) 以下のように、ログインができず、ログイン画面にエラーメッセージが表示される
4) DemoSecurityConfig.javaに定義したユーザー同じユーザー名・パスワードを入力し、「ログイン」ボタンを押下
5) 以下のように、ログインでき、検索画面が表示されることが確認できるので、「ログアウト」ボタンを押下
要点まとめ
- WebSecurityConfigurerAdapterクラスを継承したクラス内で、ログイン・ログアウトの認証設定やユーザー・パスワードの格納処理を定義できる。
- formタグ内で「th:action=”@{/login}”」「th:action=”@{/output}”」を指定することで、Spring Securityに備わっているログイン/ログアウト処理を実行することができる。この場合、POSTメソッドを利用する必要がある。