SpringSecurityはバージョンごとに設定の書き方が大幅に変更されている。
ここでは、バージョンごとに共通となっている処理の記載方法についてまとめる。
依存関係の追加
dependencies { // Spring implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' testImplementation 'org.springframework.security:spring-security-test' // Spring Boot2系の場合 // Thymeleaf Extras Spring Security5 implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' // Spring Boot3系の場合 // Thymeleaf Extras Spring Security6 implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' }
フォームログイン画面を作成する
今回は、ユーザ名、メールアドレス、識別子を入力してもらうログイン画面を実装する。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>ログイン画面</title> </head> <body> <form action="#" th:action="@{/login}" method="post"> <h1>Login</h1> <p>ユーザ名 : <input type="text" name="username" th:value="${username}" id="username"/></p> <p>メールアドレス : <input type="text" name="email" th:value="${email}" id="email"/></p> <p>ユーザ識別子 : <input type="text" name="sub" th:value="${sub}" id="sub"/></p> <p><input type="submit" value="ログイン" /></p> </form> </body> </html>
フォームログイン画面でユーザをプルダウンから選べるようにする
開発時の動作確認の際に毎回自由入力だと大変なため、プルダウンを選択することで任意のユーザ情報がテキストボックスに入力されるように実装を追加する。
プルダウンで反映されるユーザ情報はymlファイルに定義する。
プルダウンで表示するユーザ情報
- name: ユーザー選択 email: sub: - name: 山田太郎 email: yamada.tarou@co.jp sub: auth|000001 - name: 佐藤花子 email: sato.hanako@co.jp sub: auth|000002 - name: 鈴木一郎 email: suzuki.ichiro@co.jp sub: auth|000003
プルダウンで指定されたユーザ情報をテキストボックスに反映する処理
let userinfo; /* * プルダウンで選択されたユーザーの情報をテキストボックスに反映する。 */ function getUserInfo() { const index = document.getElementById('userinfo').selectedIndex; const username = document.getElementById('username'); const email = document.getElementById('email'); const sub = document.getElementById('sub'); username.value = userinfo[index].name; email.value = userinfo[index].email; sub.value = userinfo[index].sub; } /* * ymlファイルからユーザー情報を読み込んでプルダウンを生成する。 */ $(function(){ $.get('js/login-user.yml') .done(function (data) { userinfo = jsyaml.load(data); let pulldown = document.getElementById('userinfo'); document.createElement('option'); for(let i = 0; i < userinfo.length; i++){ let option = document.createElement('option'); option.innerHTML = userinfo[i].name; pulldown.appendChild(option); }; }); });
HTML
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>ログイン画面</title> <script th:src="@{/webjars/jquery/jquery.min.js}"></script> <script th:src="@{/webjars/js-yaml/dist/js-yaml.min.js}"></script> <script type="text/javascript" th:src="@{/js/userinfo.js}"></script> </head> <body> <form action="#" th:action="@{/login}" method="post"> <h1>Login</h1> <div><select name="userinfo" id="userinfo" onchange="getUserInfo()"></select></div> <p>ユーザ名 : <input type="text" name="username" th:value="${username}" id="username"/></p> <p>メールアドレス : <input type="text" name="email" th:value="${email}" id="email"/></p> <p>ユーザ識別子 : <input type="text" name="sub" th:value="${sub}" id="sub"/></p> <p><input type="submit" value="ログイン" /></p> </form> </body> </html>
フォームログイン画面を表示するコントローラを実装する
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class LocalLoginController { /** * フォーム認証のログイン画面を表示する。<br> * * @return フォーム認証のログイン画面 */ @RequestMapping(value = "/login", method = RequestMethod.GET) public String login() { return "form-login"; } }
ユーザ情報を構築するサービスを実装する
今回はパスワードを固定値にして認証したかったので、以下のように、固定値「password」をエンコードした値でユーザ情報を構築している。
デフォルトでは、ログで出力されたパスワードを入力する必要がある。以下は出力されるログのサンプル。
2024-02-16 15:27:46.393 WARN org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.getOrDeducePassword <restartedMain> --- Using generated security password: a4c4a5a0-0fb8-4565-89d3-b3fdb9ea8d64 This generated password is for development use only. Your security configuration must be updated before running your application in production.
import java.util.Collections; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; public class LocalUserDetailService implements UserDetailsService { /** パスワードのエンコーダー */ @Autowired private PasswordEncoder passwordEncoder; /** * {@inheritDoc} <br> * パスワードを固定値にしたユーザーを返却する。<br> */ @Override public UserDetails loadUserByUsername(String username) { if (username == null) { throw new UsernameNotFoundException("ユーザー名が入力されていません"); } String password = passwordEncoder.encode("password"); return new User(username, password, Collections.emptySet()); } }
フォーム認証成功時にユーザ情報を構築するハンドラを実装する
フォーム認証完了後に、ログイン画面で入力された値を使用して、ユーザオブジェクトを構築してスレッドローカルに格納するクラス。
スレッドローカルに格納することで、ユーザ情報をコントローラの引数などで手軽に取得できるようになる。
import java.io.IOException; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.StandardClaimNames; import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; public class LocalAuthenticationSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements AuthenticationSuccessHandler { /** * {@inheritDoc} */ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { handle(request, response, authentication); // リクエストからユーザー情報を取り出す String email = request.getParameter("email"); String username = request.getParameter("username"); String sub = request.getParameter("sub"); // 取り出したユーザー情報を使用してOidcUserを作成する Map<String, Object> map = new HashMap<>(); map.put(StandardClaimNames.SUB, sub); map.put(StandardClaimNames.EMAIL, email); map.put(StandardClaimNames.NAME, username); OidcIdToken oidcToken = new OidcIdToken("tokenvalue", Instant.now().minusSeconds(10), Instant.now(), map); SimpleGrantedAuthority authority = new SimpleGrantedAuthority("role"); List<SimpleGrantedAuthority> list = new ArrayList<>(); list.add(authority); DefaultOidcUser user = new DefaultOidcUser(list, oidcToken); OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(user, null, "authorizedClientRegistrationId"); // 作成したユーザオブジェクトをスレッドローカルに格納する SecurityContextHolder.getContext().setAuthentication(token); } }
今回はSpringSecurityのバージョンによらない部分のソースの実装をしていきました。
次回の記事で、SpringSecurityの有効化とJavaConfigの設定をしていきます。