로그인에 성공하며 토큰이 뜨는 것을 확인할 수 있다.
Postman에 JWT AccessToken 저장한 후 테스트를 진행한다.

정상적으로 지출이 등록된 것을 확인할 수 있다.
등록한 지출을 조회하려고 GET Test를 했는데

서버 에러가 발생했다.
해당 코드에서 오류가 발생했는데, 로그를 확인하니
기존 코드에서 @AuthenticationPrincipal 어노테이션 때문에 발생한 것 같다.
해당 어노테이션은 SecurityContextHolder에 저장된 인증 객체의 principal을 가져와서 사용하는 것이다.
@AuthenticationPrincipal@AuthenticationPrincipal은 Spring Security의 Authentication 객체에서 현재 인증된 사용자의 정보를 직접 가져올 수 있게 해주는 어노테이션이다.
Authentication 객체는 사용자가 로그인 시 저장된 인증 정보를 담고 있으며, 이를 통해 로그인한 사용자의 이름, 권한, 역할 등을 가져올 수 있다.
@AuthenticationPrincipal을 사용하려면 보통 UserDetailsService를 사용하여 사용자 정보를 로드하는 방식과 함께 사용한다.
참고
https://pixx.tistory.com/285#Spring%20%1DSecurity%EC%9D%98%20%EC%9D%B8%EC%A6%9D%20%EA%B3%BC%EC%A0%95-1
참고 사이트에서 인증 과정을 쉽게 설명해서 도움을 많이 받았다.
User 타입이 Spring Security의 UserDetails가 아닌 JPA 엔티티 User이기 때문에
스프링이 자동으로 Principal을 엔티티로 변환하지 못해서 오류가 발생한 것이다.
따라서 Spring Security의 UserDetails를 사용할 수 있게 CustomUser DTO를 만들어줘야 한다.
CustomUser DTO@Getter
@NoArgsConstructor
@AllArgsConstructor
public class CustomUserDetails implements UserDetails {
private Long id;
private String email;
private String password;
private Collection<? extends GrantedAuthority> authorities;
@Override
public String getUsername() {
return email; // email 반환
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities; // 실제 권한
}
@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그리고 UserDetailsService를 구현한 CustomUserDetailsService도 작성한다.
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email){
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
return new CustomUserDetails(
user.getId(),
user.getEmail(),
user.getPassword(),
List.of()
);
}
}
JwtAuthenticationFilter 수정그리고 JwtAuthenticationFilter에서 기존에는
String email = jwtProvider.getEmailFromToken(token);
// 토큰에서 이메일 추출해
// DB에서 사용자 조회,
userRepository.findByEmail(email).ifPresent(user -> {
// 인증 객체 생성하여
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
user, null, null // 권한은 MVP 단계에서 생략
);
// 요청 세부 정보 추가 (IP, 세션ID 등)
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request
)
);
// 인증 객체를 SecurityContext에 등록
SecurityContextHolder.getContext().setAuthentication(authentication);
});
// 다음 필터로 요청 전달
filterChain.doFilter(request, response);
}
User 엔티티를 Principal을 넣었는데
CustomUserDetails를 Principal로 넣도록 수정했다.
userRepository.findByEmail(email).ifPresent(user -> {
// 인증 객체 생성하여
CustomUserDetails customUserDetails =
new CustomUserDetails(
user.getId(),
user.getEmail(),
user.getPassword(),
List.of()
);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
customUserDetails,
null,
customUserDetails.getAuthorities()
);
정상적으로 조회가 되는 것을 확인할 수 있다.
지출 삭제 후 다시 지출 목록을 읽어보면

잘 삭제가 된 것을 확인할 수 있다.