
→ AuthenticationManager → AuthenticationProvider(주로 DaoAuthenticationProvider)가 처리
→ 내부에서 UserDetailsService.loadUserByUsername() 호출 + PasswordEncoder.matches()로 비밀번호 검증
→ JWT 필터/TokenProvider에서 토큰 검증(서명/만료 등)
→ 토큰에서 userId 꺼내 loadUserById()로 사용자 로딩
→ SecurityContextHolder에 Authentication 직접 넣음
즉, 로그인은 Provider 구조를 타고, JWT는 토큰 검증 후 인증 객체를 만들어 넣는 구조가 보통이다.
Spring Security 표준 계약은 UserDetailsService이고, 거기엔 기본적으로:
loadUserByUsername(String username)
만 존재한다.
하지만 JWT 인증에서는 토큰에서 userId를 꺼내 조회하는 경우가 많아서:
loadUserById(Long userId)
가 필요해진다.
그래서 확장 인터페이스로 “우리 앱의 계약”을 만든다:
public interface CustomUserDetailsService extends UserDetailsService {
UserDetails loadUserById(Long userId);
}
Spring Security 호환(Username 기반 메서드 유지)
JWT용 ID 기반 조회 계약을 타입으로 명확히 보장
구현체가 한 곳에서 두 방식을 모두 책임
로그인 흐름에서 보통 여기까지만 한다:
loadUserByUsername(email)에서 유저 조회 후 UserDetails 반환비밀번호 검증은 보통 Provider가 내부에서 한다:
DaoAuthenticationProvider가 PasswordEncoder.matches(raw, encoded)로 비교즉 비밀번호 비교 로직을 직접 짤 필요가 없다(기본 흐름에서는).
설정:
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
이 방식은 “시큐리티가 구성한 AuthenticationManager를 가져오는 방식”이다.
대부분의 기본 구성에서, UserDetailsService와 PasswordEncoder가 준비되어 있으면
내부적으로 DaoAuthenticationProvider 기반 매니저가 구성되어 로그인 인증을 처리한다.
다만 “자동”에 의존할수록 눈에 보이지 않아 불안할 수 있으니,
확인하려면 ProviderManager의 providers를 로그로 찍어서 검증할 수 있다.
JWT에서 userId가 필요함
컨트롤러에서 @AuthenticationPrincipal로 id 같은 추가 정보가 필요함
계정 상태 정책(삭제/정지/휴면 등)을 Security 상태로 매핑하고 싶음
CustomUserDetails에 담는 방식(가볍고 안전).@Override
public boolean isEnabled() {
return deletedAt == null;
}
다만 정책이 “삭제” 외에도 있으면 함께 반영하는 게 보통 더 현실적:
이 방식은 User 도메인에 따로 메서드를 만들어주지 않고 사용되는 메서드에 직접 사용하는 방식
규칙이 여기저기 흩어지지 않게, 도메인 언어로 캡슐화:
// User 도메인
public boolean isActive() {
return deletedAt == null && status == ACTIVE;
}
그리고 Security에서 매핑:
@Override
public boolean isEnabled() {
return user.isActive();
}
주의: 도메인에 Security 용어(isEnabled)를 박는 것보다 canLogin()/isActive() 같은 도메인 용어가 결합도가 낮다.
이 경고는 상위 메서드/패키지가 @NullMarked(기본 non-null)인데,
오버라이드 쪽이 null 계약을 명확히 표현하지 않을 때 분석기가 띄우는 케이스가 많다.
이 클래스는 로그인 전용이 아니라,
Spring Security에서 “인증 결과(Principal + Authorities)”를 담는 가장 흔한 Authentication 구현체다.
JWT에서는 비밀번호(credential)가 없으니 null로 두는 게 일반적:
return new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
Spring Security도 그걸 고려해서 인증 후 자격증명 지우기(erase)를 함
ProviderManager는 기본적으로 eraseCredentialsAfterAuthentication = true 라서
인증 성공 후 credentials를 지움
여기서 principal(UserDetails)까지 지우고 싶으면, CustomUserDetails가 CredentialsContainer를 구현하면 돼.
예:
public class CustomUserDetails implements UserDetails, CredentialsContainer {
private String password; // encoded
@Override
public void eraseCredentials() {
this.password = null;
}
}
이렇게 해두면 로그인 성공 후에 principal 내부의 password 해시도 null로 지워질 수 있어.
(“로그인” 흐름에서 Provider를 태울 때 특히 효과적)