1. 개념 설명 (Concept)
- 6일차: In-Memory Authentication (메모리에 계정 저장)
- 7일차: Database Authentication (DB에 사용자 정보 저장 → UserDetailsService 구현)
흐름:
- 클라이언트가 /login 요청 보냄.
- AuthenticationManager가 UserDetailsService를 호출.
- UserDetailsService는 DB에서 사용자 검색.
- 비밀번호 검증(PasswordEncoder) → 성공 시 Authentication 객체 반환.
- Spring Security FilterChain이 인증 상태를 SecurityContext에 저장.
2. 올바른 용어 (Terminology)
- Authentication (인증): 사용자가 누구인지 확인.
- Authorization (인가): 인증된 사용자가 무엇을 할 수 있는지 확인.
- UserDetailsService (사용자 정보 서비스): DB에서 사용자 로딩.
- PasswordEncoder (비밀번호 암호화기): 비밀번호 검증 담당.
- SecurityContext (보안 컨텍스트): 현재 인증된 사용자 정보를 담는 저장소.
3. 잘못 쓰기 쉬운 표현 + 실제 맥락 (Misuse & Context)
- ❌ “DB 인증이 JWT다” → JWT는 토큰 기반 인증 방식이고, DB 인증은 그 **전단계(사용자 검증 로직)**임.
- ❌ “Spring이 알아서 DB에 저장한다” → 실제로는 JPA/Repository에서 직접 User 조회해야 함.
4. 면접 예시 (Interview Q&A)
Q1. Spring Security에서 DB 기반 인증 시 필수로 구현해야 하는 인터페이스는?
→ A. UserDetailsService 인터페이스이며, loadUserByUsername() 메서드에서 DB 조회를 구현합니다.
Q2. 비밀번호 검증은 어디서 처리되나요?
→ **A. PasswordEncoder**가 사용자 입력 비밀번호와 DB 저장된 해시를 비교합니다.
5. 검증 요약 (Self-Check)
- 가정: DB(MySQL)에 users 테이블이 있고, username/password가 저장되어 있음.
- 확인: UserDetailsService가 DB에서 유저 검색하는지, PasswordEncoder를 적용했는지.
- 리스크: 비밀번호를 평문 저장하면 보안 취약 → 반드시 BCryptPasswordEncoder 사용.
6. 실습 코드 예제 (Java)
User 엔티티
// 사용자 정보를 DB에 저장하는 엔티티
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 자동 증가 PK
private Long id;
@Column(unique = true, nullable = false) // username은 unique해야 함
private String username;
@Column(nullable = false) // 반드시 저장해야 함
private String password;
@Column(nullable = false)
private String role; // ROLE_USER, ROLE_ADMIN 등
}
UserRepository
// JPA Repository로 DB에서 User 엔티티 조회
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
CustomUserDetails
// Spring Security가 이해할 수 있는 UserDetails 구현체
public class CustomUserDetails implements UserDetails {
private final User user;
public CustomUserDetails(User user) {
this.user = user;
}
// 권한 반환
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
}
// 로그인 아이디
@Override
public String getUsername() {
return user.getUsername();
}
// 로그인 비밀번호
@Override
public String getPassword() {
return user.getPassword();
}
// 계정 상태 관련 (여기서는 true로 처리)
@Override public boolean isAccountNonExpired() { return true; }
@Override public boolean isAccountNonLocked() { return true; }
@Override public boolean isCredentialsNonExpired() { return true; }
@Override public boolean isEnabled() { return true; }
}
CustomUserDetailsService
// DB에서 유저 정보 조회하는 서비스
@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 {
// DB에서 유저 검색
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("사용자 없음: " + username));
// UserDetails 객체로 변환 후 반환
return new CustomUserDetails(user);
}
}
SecurityConfig
// Spring Security 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomUserDetailsService customUserDetailsService;
public SecurityConfig(CustomUserDetailsService customUserDetailsService) {
this.customUserDetailsService = customUserDetailsService;
}
// 비밀번호 암호화기 등록
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// AuthenticationManager 설정
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.build();
}
// 보안 필터 체인 설정
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 테스트용: CSRF 끄기
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(login -> login
.loginPage("/login") // 커스텀 로그인 페이지 가능
.permitAll()
)
.logout(logout -> logout.permitAll());
return http.build();
}
}
7. 추가 학습 과제 (Next Task)
👉 DB 인증을 완전히 이해하려면:
- 비밀번호 해싱 실습하기 → BCryptPasswordEncoder().encode("1234") 출력해보고 DB에 저장.
- 로그인 시 DB에 저장된 해시랑 입력값 검증되는지 로그로 확인.
- 나중에 JWT랑 연결할 때, 지금 만든 AuthenticationManager와 UserDetailsService가 그대로 재활용됨.





'2025 Dev Log > 2025.Backend' 카테고리의 다른 글
| [#3][다시, 처음부터 #7] DB 연동 인증 (2) | 2025.09.27 |
|---|---|
| [#3][다시, 처음부터 #6]JWT를 활용한 실전 적용과 필터 기반 인증 흐름 (2) | 2025.09.26 |
| [#3][다시, 처음부터 #5]JWT(Json Web Token) 기반 인증 (0) | 2025.09.23 |
| [#3][다시, 처음부터 #4]서버의 동작 방식과 상태 관리 (0) | 2025.09.17 |
| [#3][다시, 처음부터 #3]HTTP 상태 코드(HTTP Status Code) 와 API 응답 설계 (0) | 2025.09.11 |