Spring Secuirty 를 구현하다보면, 반드시 UserDetailsService 를 구현해야 한다는 말을 종종 들었다. 왜 반드시 해당 인터페이스를 구현해야 하고, 이 인터페이스가 수행하는 기능은 무엇일까?
UserDetailsService 의 핵심적인 기능은 스프링 시큐리티에서 '인증 관련 정보'를 제공하는 User 클래스를 반환한다는 것이다.
User 클래스는 인증에 필요한 사용자 정보들을 담고 있다.(username, password, authorities)
일단, User 클래스를 구현하는 것부터 알아보자. 본 프로젝트에서는 User 클래스를 상속받는 MemberContext 클래스를 만들어 기능을 확장했다.
MemberContext 를 추가한 이유는 다음과 같다.
다음은 실제 구현한 MemberContext 클래스다.
@Getter
public class MemberContext extends User {
private final Long id;
private final LocalDateTime createdDate;
private final LocalDateTime modifiedDate;
private final String username;
private final String nickname;
private final String email;
public MemberContext(Member member, List<GrantedAuthority> authorities) {
super(member.getUsername(), member.getPassword(), authorities);
this.id = member.getId();
this.createdDate = member.getCreatedDate();
this.modifiedDate = member.getModifiedDate();
this.username = member.getUsername();
this.nickname = member.getNickname();
this.email = member.getEmail();
}
public boolean hasAuthority(String name) {
return getAuthorities().stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(name));
}
}
UserDetailsService 는 인증 과정에서 사용할 정보를 가져오는 역할을 수행한다. 즉, User 객체(인증 관련 정보를 담은 객체)를 반환하기 위해 존재하는 것이다. 이 곳에서 User 객체를 완성하고 반환하는 것이 목적이다.
본 프로젝트에서는 UserDetailsService 를 구현한 CustomUserDetailsService 를 만들고, MemberContext(extends User)를 반환하도록 구현했다.
@Service
@RequiredArgsConstructor
public class CustomerUserDetailsService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member member = memberRepository.findByUsername(username).get();
return new MemberContext(member, member.genAuthorities());
}
}
이 때 반드시, loadUserByUsername() 메서드를 오버라이딩 해야한다.
해당 메서드의 기능은 다음과 같다.