Spring-Security

UserDetailsService & UserDetail (+repository 연동 예시)

neal89 2025. 5. 24. 12:04

1. UserDetailsService

역할:
UserDetailsService는 사용자의 **username(아이디)**를 기반으로 DB에서 사용자 정보를 조회하여, UserDetails 객체로 반환하는 인터페이스입니다.

주요 메서드:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
  • 입력: 로그인 폼에서 입력된 사용자 이름
  • 출력: 사용자 정보를 담은 UserDetails 객체
  • 예외: 사용자를 찾지 못하면 UsernameNotFoundException 발생

사용 예시:

@Bean
public UserDetailsService customUserDetailsService() {
    return username -> {
        if ("user".equals(username)) {
            return new User("user", "{noop}password",
                Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
        }
        throw new UsernameNotFoundException("User not found: " + username);
    };
}

그리고 이렇게 Security 설정에 등록해 사용합니다:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .formLogin(withDefaults())
        .userDetailsService(customUserDetailsService()); // 등록
    return http.build();
}

2. UserDetails

역할:
UserDetails는 Spring Security가 이해할 수 있도록 사용자 정보를 담는 객체입니다. 이 객체를 통해 인증 처리와 권한 판단이 가능합니다.

주요 메서드:

메서드 설명

getUsername() 사용자 이름 반환
getPassword() 비밀번호 반환
getAuthorities() 권한 리스트 반환 (예: ROLE_USER)
isAccountNonExpired() 계정 만료 여부 (true면 만료 안 됨)
isAccountNonLocked() 계정 잠금 여부 (true면 잠기지 않음)
isCredentialsNonExpired() 비밀번호 만료 여부 (true면 만료 안 됨)
isEnabled() 계정 활성화 여부 (true면 활성 상태)

커스텀 구현 예시:

public class CustomUserDetails implements UserDetails {
    private UserInfo userInfo;

    public CustomUserDetails(UserInfo userInfo) {
        this.userInfo = userInfo;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return userInfo.getRoles().stream()
            .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
            .collect(Collectors.toList());
    }

    @Override public String getPassword() { return userInfo.getPassword(); }
    @Override public String getUsername() { return userInfo.getUsername(); }
    @Override public boolean isAccountNonExpired() { return true; }
    @Override public boolean isAccountNonLocked() { return true; }
    @Override public boolean isCredentialsNonExpired() { return true; }
    @Override public boolean isEnabled() { return userInfo.isEnabled(); }
}

동작 순서 정리

  1. 사용자가 로그인하면, Spring Security는 UserDetailsService.loadUserByUsername()를 호출합니다.
  2. DB에서 해당 사용자의 정보를 조회한 후, UserDetails 객체로 변환합니다.
  3. Spring Security는 이 객체를 이용해 인증(Authentication) 과정을 처리하고,
  4. 인증이 성공하면 Authentication 객체를 만들어 SecurityContext에 저장합니다.

 

 


Repository 연동 기반 예시

✅ 1. UserDetailsService 구현체

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    // 생성자 주입
    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo userInfo = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));

        return new CustomUserDetails(userInfo);
    }
}

✅ 2. UserDetails 구현 클래스

public class CustomUserDetails implements UserDetails {

    private final UserInfo userInfo;

    public CustomUserDetails(UserInfo userInfo) {
        this.userInfo = userInfo;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return userInfo.getRoles().stream()
            .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
            .collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return userInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return userInfo.getUsername();
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return userInfo.isEnabled();
    }
}

✅ 3. UserRepository (JPA)

@Repository
public interface UserRepository extends JpaRepository<UserInfo, Long> {
    Optional<UserInfo> findByUsername(String username);
}

✅ 4. Security 설정 클래스

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final CustomUserDetailsService customUserDetailsService;

    public SecurityConfig(CustomUserDetailsService customUserDetailsService) {
        this.customUserDetailsService = customUserDetailsService;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin(Customizer.withDefaults())
            .userDetailsService(customUserDetailsService); // UserDetailsService 등록

        return http.build();
    }
}

✅ 5. UserInfo (Entity 예시)

@Entity
public class UserInfo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;
    private boolean enabled;

    @ElementCollection(fetch = FetchType.EAGER)
    private List<String> roles = new ArrayList<>();

    // getters, setters ...
}