Spring Securityで認証によく使うUserDetailsについて
Spring Bootで認証を実装しようとしたとき、よく使うSpring側ですでに用意されているクラスと言えばUser
やUserDetails
およびUserDetailsService
だと思いますが、至極当たり前のことながら意外とこれらの挙動を正しく理解していなかったので、改めて確認してみました。
実装
まず、ユーザ情報はデータベースから取得するので、取得用のEntityを実装します。
LoginUser.java
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name="m_user")
public class LoginUser{
@Id
@GeneratedValue(strategy=GenerationType.TABLE)
@Column(name = "user_id")
@Getter
@Setter
private String userId;
@Column(name = "user_name")
@Getter
@Setter
private String userName;
@Column(name = "password")
@Getter
@Setter
private String password;
@Column(name = "pass_update_date")
@Getter
@Setter
private Date passUpdateDate;
@Column(name = "login_miss_times")
@Getter
@Setter
private int loginMissTimes;
@Column(name = "unlock")
@Getter
@Setter
private boolean unlock;
@Column(name = "enabled")
@Getter
@Setter
private boolean enabled;
@Column(name = "user_due_date")
@Getter
@Setter
private Date userDueDate;
@Column(name = "role_id")
@Getter
@Setter
private String roleId;
}
肝心のUserDetailsです。
UserDetailsImpl.java
import java.util.Collection;
import java.util.Date;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDetailsImpl implements UserDetails {
private LoginUser user;
public UserDetailsImpl(LoginUser user) {
this.user = user;
}
private Collection<? extends GrantedAuthority> authority;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authority;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserId();
}
@Override
public boolean isAccountNonExpired() {
if(user.getUserDueDate().after(new Date())) {
return true;
} else {
return false;
}
}
@Override
public boolean isAccountNonLocked() {
return user.isUnlock();
}
@Override
public boolean isCredentialsNonExpired() {
if(user.getPassUpdateDate().after(new Date())) {
return true;
} else {
return false;
}
}
@Override
public boolean isEnabled() {
return user.isEnabled();
}
}
ユーザ情報のテーブル:
select * from m_user;
user_id | password | pass_update_date | login_miss_times | unlock | role_id | user_name | enabled | user_due_date
---------+--------------------------------------------------------------+---------------------+------------------+--------+---------+----------------+---------+---------------------
system | $2a$10$xRTXvpMWly0oGiu65WZlm.3YL95LGVV2ASFjDhe6WF4.Qji1huIPa | 2099-12-31 23:59:59 | 0 | t | admin | システム管理者 | t | 2099-12-31 23:59:59
user | $2a$10$xRTXvpMWly0oGiu65WZlm.3YL95LGVV2ASFjDhe6WF4.Qji1huIPa | 2099-12-31 23:59:59 | 0 | t | general | 一般ユーザー | t | 2099-12-31 23:59:59
sample1 | $2a$10$xRTXvpMWly0oGiu65WZlm.3YL95LGVV2ASFjDhe6WF4.Qji1huIPa | 1999-12-31 23:59:59 | 0 | t | general | ユーザー1 | t | 2099-12-31 23:59:59
sample2 | $2a$10$xRTXvpMWly0oGiu65WZlm.3YL95LGVV2ASFjDhe6WF4.Qji1huIPa | 2099-12-31 23:59:59 | 0 | f | general | ユーザー2 | t | 2099-12-31 23:59:59
sample3 | $2a$10$xRTXvpMWly0oGiu65WZlm.3YL95LGVV2ASFjDhe6WF4.Qji1huIPa | 2099-12-31 23:59:59 | 0 | t | general | ユーザー3 | f | 2099-12-31 23:59:59
sample4 | $2a$10$xRTXvpMWly0oGiu65WZlm.3YL95LGVV2ASFjDhe6WF4.Qji1huIPa | 2099-12-31 23:59:59 | 0 | t | general | ユーザー4 | t | 1999-12-31 23:59:59
この実装内容で認証した際に、有効なアカウントとして認識されるのは、system
とuser
のみです。
というのも、UserDetails
を実装したクラスでは、isEnabled
やisUnlocked
、isAccountNonLocked
などのメソッドをオーバーライドしており、ここでfalse
がひとつでも返ってきた場合は401Unauthorized
のメッセージが返ってきて、はじかれるようになっています。
オーバーライドしている内容を見れば明らかなのですが、最終的な挙動を正しく理解していなかったので、地味に悩みました。