개요
Spring Security에서 제공하는 UserDetails이 사용자 정보를 저장하고 권한 관리를 한다고 대강 알고 있었는데요. 개념을 잘 알지 못해, 비효율적으로 사용하는 경우가 발생해 자세히 알아보려고 합니다.
UserDetails이 무엇인가 보다 앞서 UserDetails이 어떤 과정에 의해 호출되는지부터 알아보려고 합니다.
UserDetails 과정
UserDetails가 어떤 것인지에 앞서, UserDetails이 어떻게 작동하는 것인지 알아보고 가겠습니다.

Spring 공식 홈페이지에서는 DaoAuthenticationProvider(사용자 이름과 비밀번호를 인증)가 UserDetails의 유효성을 검사한 뒤 UserDetailsService에 의해 UserDetails의 주체인 Authentication이 반환된다고 합니다.
뭔가 말이 어렵지만, 정리해보자면 아래와 같습니다.
- DaoAuthenticationProvider는 UserDetailsService, PasswordEncoder를 이용해, 사용자 정보를 읽어온다.
- 인증을 통해 UserDetails가 유효한지 즉, 사용자 이름과 비밀번호가 유효한지 검사한다.
- UserDetilasSerivce를 통해, Authentication(자격 증명, 현재 인증된 사용자) 정보를 반환한다.
Spring의 공식 홈페이지에서 DaoAuthenticationProvider 과정에서 잘 살펴볼 수 있는 부분입니다.

UserDetails란?
Spring Security에서 사용자의 정보를 담는 인터페이스입니다.
Spring Security가 제공 해주는 기본 메서드는 표로 정리하면 아래와 같습니다.
메서드
| 메소드 | 리턴 타입 | 설명 |
| getAuthorities() | Collection<? extends GrantedAuthority>(Never Null) | 계정의 권한 목록을 리턴 |
| getPassword() | String | 계정의 비밀번호를 리턴 |
| getUsername() | String(Never Null) | 계정의 고유한 값을 리턴 |
| isAccountNonExpired() | boolean | 계정의 만료 여부 리턴 |
| isAccountNonLocked() | boolean(true: 잠겨 있지 않음, false: 잠겨있음) | 계정의 잠김 여부 리턴 |
| isCredentialsNonExpired() | boolean(true: 만료되지 않음, false: 만료) | 비밀번호 만료 여부 리턴 |
| isEnabled() | boolean boolean(true: 활성화, false: 비활성화) | 계정의 활성화 여부 리턴 |
예시
public class CustomDetails implements UserDetails {
private final UserEntity user;
public UserDetails(UserEntity user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> collect = new ArrayList<>();
collect.add(new SimpleGrantedAuthority(user.getRole().getRole()));
return collect;
}
@Override
public String getPassword() {
return user.getUserPw();
}
@Override
public String getUsername() {
return user.getUserId();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
UserDetailsService란?
앞서 UserDetails가 가져오는 과정에 따라, username을 검색해, UserDetails를 반환하는 인터페이스입니다.
UserDetails와 달리, loadUserByUsername() 메서드만 정의되어 있습니다.
✔ 여기서 잠깐! loadUserByUsername()이면, 비밀번호는 어디에서 확인하는 거지라는 의문이 들 수 있습니다.
비밀은 그림에서와 같이 DaoAuthenticationProvider가 부르는 PasswordEncoder에 있습니다. UserDetailsService는 사용자의 이름, PasswordEncoder에는 비밀번호를 각각 처리합니다.

메서드
| 메소드 | 리턴 타입 | 설명 |
| loadUserByUsername | UserDetails | userName을 받아서 유저의 정보를 가져옴 |
예시
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
UserEntity user = userRepository.findByUserId(userId);
if (user == nulll) {
throw new CustomException(ErrorCode.UserNotFound);
}
return new CustomDetails(user);
}
}
저는 DB를 통해 사용자의 이름이 아닌, 사용자의 아이디로 사용자의 정보를 가져오게 진행했습니다.
JPA를 이용해 findByUserId으로 사용자 ID를 검색해 DB에 존재하는지 체크
만약, DB에 존재하지 않는 회원이라면 커스텀 예외처리를 진행하고, 존재한다면 CustomDetails를 반환하게 만들었습니다.
마치며
저는 이 중에서 getAuthorities(), getPassword(), getUsername() 이 세 가지를 사용했는 데, 핑계라고 하면 핑계인데 Spring Security를 사용은 해보고 싶고 공부할 시간은 없고 해서 대충 개발했던 것 같습니다.(저의 업보라고 생각합니다ㅎㅎ)
isAccountNonExpired(), isCredentialsNonLocked()는 만료 기능은 예시로도 잘 안 나오는 기능들인데, 휴면계정과 같은 기능에 사용하면 좋지 않을까라고 생각이 드는 기능들입니다.

이번 UserDetails, UserDetailsService를 공부하면서 그럼 그 앞단은 어떻게 진행되는 걸까 🤔 라는 궁금증이 생겼고, 다음에는 AuthenticationManager에 대한 공부를 진행하려고 합니다.
💡 더 성장하는 개발자가 될 수 있도록, 틀린 부분을 알려주신다면 감사하겠습니다.
'코딩 > Spring' 카테고리의 다른 글
| JPA 쿼리 성능 향상 ( 퀴리 튜닝, 정규화 및 비정규화 ) (0) | 2024.05.17 |
|---|---|
| [Spring] Error attempting to get column 'xxx' from result set. (0) | 2023.10.17 |