Springでログインユーザのアカウントをロック/有効期限切れ等する場合

Pocket

Spring(SpringBootも同じ)でログインユーザのアカウントをロック、有効期限切れ、認証資格有効期限切れなどを行う方法についてです。

結構簡単だったのですが、イマイチ基本を理解できていなかったので半日ほど手間取りました。

基本的にユーザ情報に持たせたbooleanのフィールドのture/falseを切り替えるだけで、ロックや期限切れの処理自体を記述する必要はありません。

このフィールドとは、org.springframework.security.core.userdetails.Userを継承したログインユーザのクラスのフィールドです。これに気付くのにしばらくかかりました。。。

元々、Userには以下のメソッドが用意されています。
・ユーザアカウントが認証期限切れしていないか

boolean isAccountNonExpired();

・ユーザアカウントの資格が認証期限切れしていないか

boolean isCredentialsNonExpired();

・ユーザアカウントがロックしていないか

boolean isAccountNonLocked();

・ユーザアカウントが無効

boolean isEnabled();

ですので、Userを継承したログインユーザのクラスで上記のメソッドを実装しておきます。
例)アカウントのロックを管理するUserクラス

public class LoginUserDetails extends User {

    private final ユーザクラス user;

    public LoginUserDetails(ユーザクラス userBean, boolean accountNonExpired,
        boolean credentialsNonExpired, boolean accountNonLocked,
        Collection<GrantedAuthority> authorities) {
        super(userBean.getUsername(), userBean.getPassword(),
            true, true, true, userBean.isAccountNonLocked(), getAuthority(userBean));
        this.user = userBean;
    }

    public boolean isAccountNonLocked() {
         return user.isAccountNonLocked();
    }
    public boolean isEnabled() {
        return true;
    }

※無効、期限切れ、資格の期限切れには対応しないので、isEneabled()などは固定でtureを返し、コンストラクタのsuperに関する処理でも固定でtureを設定している

上記のフィールドのtrue/falseを切り替えることで、ログイン時にException(イベント)が発生することになります。

ログインに成功した場合、以下のイベントが発生します。

Springでログイン成功時に発生するイベント
イベント 内容
AuthenticationSuccessEvent 認証処理が成功(後続処理でエラー発生の場合あり)
SessionFixationProtectionEvent セッション固定攻撃対策の処理成功
InteractiveAuthenticationSuccessEvent 認証処理がすべて成功

ログインに失敗した場合、以下のイベントが発生します。

Springでログイン失敗時に発生するイベント
イベント イベントのトリガとなるException 内容
AuthenticationFailureBadCredentialsEvent BadCredentialsException 認証失敗
AuthenticationFailureDisabledEvent DisabledException アカウント無効
AuthenticationFailureLockedEvent LockedException アカウントロック
AuthenticationFailureExpiredEvent AccountExpiredException アカウント有効期限切れ
AuthenticationFailureCredentialsExpiredEvent CredentialsExpiredException 資格情報の有効期限切れ
AuthenticationFailureServiceExceptionEvent AuthenticationServiceException 認証処理異常

※参考6. TERASOLUNA Server Framework for Java (5.x)によるセキュリティ対策

これらのイベントを制御するために、イベントのリスナクラスを作成します。
@Componentを付与したDI可能なクラスで@EventListenerを付与したメソッドを作成するだけです。
例)イベントのリスナクラス

@Component
public class クラス名 {
    @EventListner
    public void メソッド名(イベント名 event) {
    }
}

基本的にイベントごとに対応すれば上記で全て対応可能です。
存在するユーザでログインに失敗した場合、DBなどに記録したユーザのアカウント失敗回数をインクリメントします。一定回数に到達したら、ロックのフラグをtrueにします。

ただしログイン失敗時に存在するユーザと存在しないユーザのどちらでログインしようとしたかを判別するためにUsernameNotFoundExceptionの発生を識別する必要があります。
これはAuthenticationFailureBadCredentialsEventに分類されますが、認証プロバイダ(AbstractUserDetailsAuthenticationProvider)でhideUserNotFoundExceptionsをfalseに変更しないと
UsernameNotFoundExceptionはBadCredentialsExceptionに書き換えられるため、存在しないユーザでのログインを識別できません。

Configクラスに以下の記述をすることでUsernameNotFoundExceptionの発生を識別できるようになりましたが、AuthenticationProviderを正確に理解できていないので少し挙動の理解が怪しいです。

@Autowired
UserDetailsService実装クラス service;
@Bean
public AuthenticationProvider daoAuhthenticationProvider() {
    DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
    daoAuthenticationProvider.setUserDetailsService(service);
    daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
    daoAuthenticationProvider.setPasswordEncoder(new パスワードエンコーダクラス());
    return daoAuthenticationProvider;
}    
@Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(daoAuhthenticationProvider());
}

※参考Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( 番外編 )( Spring Security でいろいろ試してみる )

上記の対応を実施した上でイベントのリスナクラスを以下のようにすると、存在するユーザと存在しないユーザの識別が可能になります。

@EventListener
public void authFailureBadCredentialsEventHandler(AuthenticationFailureBadCredentialsEvent event) {
    if (event.getException().getClass().equals(UsernameNotFoundException.class)) {
        // 存在しないユーザ名でのログイン失敗
    } else {
         // 存在するユーザ名でのログイン失敗
    }
}
広告

Pocket

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です