Spring Security の PasswordEncoder を移行したい

備忘録。

セキュリティ要件の変更などにより、Spring Security の PasswordEncoder (エンコード方式) を移行したいケースがあるとする。

AbstractDaoAuthenticationConfigurer#passwordEncoder を変更して DB に格納しているパスワードをマイグレーションするだけかと思ったけど、DB のパスワードはエンコードされているため機械的マイグレーションするのは無理そう。

Spring Security 5.x 以降であればマイグレーションの仕組みがあるっぽいけど、今回は 4.x のときにどうするかという話。(バージョンアップできるならそれでよし)

docs.spring.io

Migrating PasswordEncoder

2段階でパスワードをチェックする encoder を自作する。ここでは、BCryptPasswordEncoder から Pbkdf2PasswordEncoder に移行する例。

@Component
public class MigratingPasswordEncoder extends Pbkdf2PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        return super.encode(rawPassword);
    }
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        try {
            // 新しい PasswordEncoder (PBKDF2) でチェックする
            return super.matches(rawPassword, encodedPassword);
        } catch (IllegalArgumentException e) {
            // チェックNGの場合は旧の PasswordEncoder (bcrypt) でチェックする
            return new BCryptPasswordEncoder().matches(rawPassword, encodedPassword);
        }
    }
}

これを AbstractDaoAuthenticationConfigurer#passwordEncoder に設定する。あとは、アカウント作成やパスワード変更の際に PBKDF2 でエンコードして DB に格納できるようにすれば、旧方式の既存パスワードを段階的に移行できるはず。

旧方式のときにパスワード変更を強制したい

AuthenticationSuccessHandler#onAuthenticationSuccess で、どちらの方式でエンコードされているか判定して、旧方式の場合はパスワード変更画面に飛ばすとかで対応できそう。ただし、旧方式のパスワードかどうかを判定できることが前提。

bcrypt から PBKDF2 の移行なら以下で判定できる。(bcrypt でエンコードした値は先頭が $2a$ で始まるため)

public boolean upgradeEncoding(String encoded) {
    try {
        Hex.decode(encoded);
        return false;
    } catch (Exception e) {
        return true;
    }
}

現場からは以上です。