menu
書いてる野郎
orebike@gmail.com
調べたら出てくるサンプルのコードはどれも非実用的なモノばかりなのでまともに使えるモノを探っていく。
・・・と思ってそのへんに転がってるサンプルコードかき集めれば出来るっしょと思ってたらまともに動かなくなった。
とやってはみたものの、そのお仕着せの非実用的じゃないやり方をしようとすると、実はデフォルトでよろしくやってくれていた部分を全部自分で実装しないといけないような・・・どうしたもんか。
これは最後まで書かないとまともに動かないので焦らず最後まで書く。
まずはログインのためのログイン画面を作る。 大半のサンプルは、ログインID と パスワードで認証して、パスワードは MD5でハッシュ化されているという 前提で書かれているが実用としてはそんなもので対応できない場合が多い
今回は groupId というも一つのパラメータを加える。
form 自体は別になんの変哲もない form を作ればよい。 素の HTML でもいいし Thymeleaf で構築してもよい Vue.js でも最終的にこのようなモノを構築できればよい。
こんな感じでよいだろう。
<form method="post" action="doLogin"> <input type="text" name="loginId" /> <input type="password" name="loginPassword" /> <input type="hidden" name="groupId" /> <button type="submit">Login</button> </form>
Spring Security には Filter というものがあって、それで受ける。 まずは受け取ってそのパラメータを整形するというところから入る。
UsernamePasswordAuthenticationFilter というモノを継承して自分の独自の受けを作る。
このようになる。
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter{ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { Account input = new Account(); input.setLoginId(request.getParameter("loginId")); input.setGroupId(request.getParameter("groupId")); String password = request.getParameter("loginPassword"); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(input, password); this.setDetails(request, token); return this.getAuthenticationManager().authenticate(token); } }
最初に new している Account は特になんでもよく、送られてきたパラメータを格納できるならなんでもいい。
次に、格納する、あとはほぼ定形記述で、次の具体的な検証処理へ値を進める記述である。
実際の入力値の検証処理を作る
これは AuthenticationProvider というモノが実装されていればよいということになる。
@Configuration public class CustomAuthenticationProvider implements AuthenticationProvider{ private AccountDao accountDao; public CustomAuthenticationProvider(AccountDao accountDao) { this.accountDao = accountDao; } @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { Account accountInput = (Account)auth.getPrincipal(); String password = (String)auth.getCredentials(); Account accountDb = accountDao.findByLoginIdAndGroupId(accountInput.getLoginId(), accountInput.getGroupId()); if(accountDb == null) { throw new BadCredentialsException("Authentication Error"); } if(!password.equals(accountDb.getPassword())) { throw new BadCredentialsException("Authentication Error"); } Collection<GrantedAuthority> authorityList = new ArrayList<>(); authorityList.add(new SimpleGrantedAuthority(accountDb.getRole().getName())); return new UsernamePasswordAuthenticationToken(accountDb, password, authorityList); } @Override public boolean supports(Class<?> authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } }
クラスに authenticate というメソッドを実装してやればいい。
この Authentication という引数にさっきパラメータにして送り込んできたインスタンスやら情報が詰まっているので取り出してやる。
今回はその結果がデータベースに入っているとしてそれを引き出す。 DAO インスタンスを DI してもらくことにして、そこから取るようにする。 この例では DB を想定しているが、もうこれもなんでもよい。
DAO のメソッドに入力されたパラメータを使ってインスタンスを引き出す。 別に何のメソッドでもよい。
その後に何やらいろいろ比較する。この例ではパスワードが平文で保存されているというモノになっているが、 MD5やら何かのハッシュアルゴリズムを使ってハッシュ化するなら単にすればよいだけ。
認証に失敗ならば BadCredentialsException とかを投げればよいようだ。
認証処理が終了したら、権限の付加を行って、最後の締めの処理をしている。
最後にやった処理で、セッションにこの情報が保持されるようだ。 なのでこの情報は後でログイン後に引き出すことになる。
パスワードが間違っていたりして認証失敗した場合。 つまり流れとしては↑で BadCredentialsException を投げた時の挙動である。
これは AuthenticationFailureHandler というモノを実装すればいいようだ。
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String gid = request.getParameter("groupId"); // ログイン画面にリダイレクトする response.sendRedirect(request.getContextPath() + "/login/" + gid); } }
ログイン成功した場合は既存のクラスを使う(後述)
このようなクラスを作る。Spring Security の導入が終わっているなら このクラスが有るだけで設定が有効いなる。
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // 先程作った認証の本体を DI @Autowired CustomAuthenticationProvider authenticationProvider; @Override public void configure(WebSecurity web) throws Exception { // 認証を無視するパスの設定。主に静的ファイル web.ignoring().antMatchers("/img/**", "/css/**", "/js/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http // login 側の設定 .authorizeRequests() .antMatchers("/login/*").permitAll() // loginは全ユーザーアクセス許可 .anyRequest().authenticated() // それ以外は全て認証無しの場合アクセス不許可 .and() // ログアウト側の設定 .logout() .logoutUrl("/doLogout").permitAll() .logoutSuccessUrl("/login"); // 先程作った受け取り口を作り、そこにいろいろ設定を書き込んでいく CustomAuthenticationFilter filter = new CustomAuthenticationFilter(); // 成功後にどうするか。例では top にリダイレクトするだけでよいので出来合いの Handler を使って設定 // このへんを指定してないとデフォルトのおせっかい画面が勝手に開いたりする。 filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/top")); // どのようなリクエストでこのフィルタ、つまり認証処理を駆動するかの設定 // doLogin に POST メソッドで来ると駆動する filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/doLogin", "POST")); // これは定形記述 filter.setAuthenticationManager(authenticationManagerBean()); // 失敗時のハンドラを登録 filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler()); // フィルタ自体の登録 http.addFilterBefore(filter, CustomAuthenticationFilter.class); // CSRF はオフ http.csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 認証処理を登録 auth.authenticationProvider(authenticationProvider); } }
以上となる。