Spring Boot/認証/Spring Security/失敗例1

Spring Boot/認証/Spring Security/失敗例1

調べたら出てくるサンプルのコードはどれも非実用的なモノばかりなのでまともに使えるモノを探っていく。

・・・と思ってそのへんに転がってるサンプルコードかき集めれば出来るっしょと思ってたらまともに動かなくなった。

とやってはみたものの、そのお仕着せの非実用的じゃないやり方をしようとすると、実はデフォルトでよろしくやってくれていた部分を全部自分で実装しないといけないような・・・どうしたもんか。

大まかな流れ

  • ログインの画面を作る
  • ログインのパラメータの受け取り口を作る
  • ログインの検証処理を作る
  • 結果によるハンドリング方法を作る
  • 全体を統合する設定を書く

これは最後まで書かないとまともに動かないので焦らず最後まで書く。

ログインの画面を作る

まずはログインのためのログイン画面を作る。 大半のサンプルは、ログイン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);
    }
}

以上となる。

java/spring/spring_boot/auth/spring_security/ex/miss_ex1.txt · 最終更新: 2019-06-09 15:52 by ore