[패스트캠퍼스X야놀자 : 미니 프로젝트] Spring Security의 UserDetails 및 UserDetailsService 구현하기

꼬마요리사레미·2023년 12월 14일

💡 로그인 로직

  • 사용자가 로그인 폼에 인증 정보를 입력한 후 로그인을 시도하였을 때, UsernamePasswordAuthenticationFilter가 이를 가로채서 AuthenticationManager에게 전달한다.
  • 이때, UsernamePasswordAuthenticationToken을 사용하여 Authentication 객체를 생성한다.
// UsernamePasswordAuthenticationFilter에서 생성된 Authentication 객체
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
  1. AuthenticationManager는 JwtAuthenticationProvider에서 UserDetailsService의 loadUserByUsername를 사용하여 입력된 이름을 이용하여 UserDetails를 가져온다. 이후 입력된 비밀번호와 저장된 비밀번호를 비교하여 인증을 수행한다.
  • 성공하면, UsernamePasswordAuthenticationToken을 사용하여 Authentication 객체를 생성한다.
// AuthenticationManager에서 사용자 인증 수행
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 입력된 비밀번호와 저장된 비밀번호 비교
if (passwordEncoder.matches(password, userDetails.getPassword())) {
    Authentication authenticationResult = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    // 성공한 경우 SecurityContext에 저장
   SecurityContextHolder.getContext().setAuthentication(authenticationResult);
}
  1. 해당 Authentication 객체는 SecurityContextHolder의 SecurityContext에 저장되어 스레드 로컬에 보관된다. 이렇게 함으로써 인증된 사용자에 대한 정보를 어디서든지 쉽게 얻을 수 있도록 한다.

💡 UserDetailsService

  • username 정보는 여러가지가 있을 수 있으므로 커스텀한 메서드를 만들어야 한다.
  • 앞서 설명했듯이 이를 처리하는 부분은 UserDetailsService의 loadUserByUsername 메서드이다. 이 메서드를 오버라이드하여 아이디 또는 이메일로 사용자를 찾는 기능을 추가할 수 있다.
@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> new UserNotFoundException(SecurityExceptionCode.USER_NOT_FOUND));

        return createUserDetails(user);
    }

    // ...

    public UserDetails createUserDetails(User user) {

        return PrincipalDetails.builder()
                .username(String.valueOf(user.getId()))
                .password(user.getPassword())
                .authority(user.getAuthority().toString())
                .build();
    }
}

💡 UserDetails

  • Spring Security에서 제공하는 기본 UserDetails 구현체는 사용자의 아이디, 비밀번호, 권한 정보만을 다룬다.
  • 실제 애플리케이션에서 다양한 정보가 필요한 경우 유연하게 추가할 수 있다.
@Getter
public class PrincipalDetails implements UserDetails {

    private final String username;
    private final String password;
    private final String authority;
    private final Collection<GrantedAuthority> authorities;

    @Builder
    private PrincipalDetails(
            String username, String password, String authority, Collection<GrantedAuthority> authorities
    ) {
        this.username = username;
        this.password = password;
        this.authority = authority;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.singleton((GrantedAuthority) () -> authority);
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

✋ 여기서 잠깐

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // ...
    http
        .formLogin()
            .disable()
    // ...
    return http.build();
}
private Authentication authenticateUser(LoginRequest loginRequest) {
    UsernamePasswordAuthenticationToken authenticationToken = loginRequest.toAuthentication();
    return authenticationManagerBuilder.getObject().authenticate(authenticationToken);
}
  • 로그인폼 비활성화 시 AnonymousAuthenticationFilter가 실행되어 SecurityContextHolder를 익명 사용자로 설정한다.
  • AuthenticationManagerBuilder에서 AuthenticationManager를 얻고 AuthenticationManager를 사용하여 사용자를 실제로 인증한다. 인증이 성공하면 해당 사용자의 Authentication 객체를 반환한다.
  • 결과적으로, authenticateUser 메서드를 호출하면 해당 메서드 내에서 사용자의 인증이 진행되고, SecurityContextHolder에는 해당 사용자의 정보가 설정된다.

0개의 댓글