
프론트에서 username 수정 로직을 완성 후 이상한 부분을 발견했다.
아무리 변경이 완료되고 DB에 username 수정이 성공적으로 반영되어도, 프론트 화면에는 username이 계속 이메일로 보이는 상황이 발생했다.

문제를 먼저 짚고 넘어가자면, 수정 실패가 아닌
현재 나는 UserDetails를 구현한 PrincipalUser를 커스텀해서 사용 중인데, 내부 로직이 이렇게 되어있다.
@Override
public String getUsername() {
// Spring Security 입장에서 username = 로그인 식별자
return this.email;
}
즉, Spring Security에서 principal.getUsername()은 “닉네임”이 아니라 “로그인 식별자”를 의미한다.
나는 이메일 로그인을 쓰고 있으니 getUsername()이 이메일을 리턴하는 건 정상이다.
내 경험상 간단히 말하면 프로젝트에 필요한 인증 메타(예: userId/roles 등)를 한 곳에서 관리하기 위해서다.
기본 UserDetails를 써도 되지만, 프로젝트가 커질수록 커스텀이 편했다.
문제는 principal 응답의 username이 사실 UserDetails.getUsername()(=email) 기반으로 내려가고 있었는데, 프론트가 그걸 닉네임(username)으로 믿고 그대로 사용해버린 것.
백엔드 테스트에서는 “DB에 username이 수정되었는지”만 확인하니 이상을 못 느꼈는데,
실제로 화면에서 보여줄 때는 principal(UserDetails)의 getUsername()을 쓰고 있었기 때문에 계속 이메일이 튀어나온다(당연함).

이번 문제는 결국 “username”이라는 같은 단어가
으로 의미가 달라서 생긴 혼동이었다.
그래서 해결 방향성은 크게 2가지였다.
노가다 vs 논리적 영역 확장의 순간이었다.

이 방법은 개념 충돌 자체를 없애는 “정공법”이다.
DB 컬럼/엔티티/DTO/프론트 타입까지 전부 nickname으로 통일하면 이후에 헷갈릴 일이 거의 없다.
근데 당장 선택하지 않은 이유는 단순하다.
영향 범위가 너무 큼: 이미 프로젝트 진행도가 50%를 넘은 상태에서 엔티티 필드명, DTO, 프론트 타입, 화면 바인딩, 테스트 코드까지 줄줄이 수정해야 한다.
현재 단계에서 “작동은 되는데 이름만 바꾸는 리팩토링”에 시간을 크게 쓰고 싶지 않았다.
즉, 장기적으로는 가장 깔끔하지만, 지금은 비용이 큰 선택지였다.
이 방법은 Security가 기대하는 규약을 건드리지 않으면서,
프론트가 원하는 데이터를 안정적으로 제공하는 쪽이다.
결국 “principal.getUsername() = email”은 버그가 아니라 설계대로 동작이고,
UI에서 보여줄 데이터까지 principal에 기대는 게 오해의 시작이었다.
그래서 나는 principal은 인증 주체로만 두고, 화면용 데이터는 DTO로 ‘정제’해서 내려주는 방식으로 정리했다.
public record PrincipalDto(
Long userId,
String email,
String username, // 여기에는 도메인 닉네임을 담는다
String profileImgUrl,
Boolean emailVerified,
ArtistStatus artistStatus,
List<String> roles
) {}
@Transactional(readOnly = true)
public ApiRespDto<PrincipalDto> getPrincipalDto(PrincipalUser principal) {
if (principal == null) {
return new ApiRespDto<>("failed", "인증되지 않은 사용자입니다.", null);
}
User user = userRepository.findById(principal.getUserId())
.orElseThrow(() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다."));
PrincipalDto dto = new PrincipalDto(
user.getUserId(),
user.getEmail(),
user.getUsername(), // 닉네임은 엔티티에서 꺼낸다
user.getProfileImgUrl(),
user.getEmailVerified(),
user.getArtistStatus(),
principal.getRoles()
);
return new ApiRespDto<>("success", "", dto);
}
이렇게 하면 프론트는 principal.username이 의미상 닉네임으로 확정되고,
SecurityContext의 username = email 충돌이 사라진다.
또한 보안 principal(UserDetails)을 그대로 응답에 노출하면 불필요한 필드/보안 정보가 섞일 수 있어 DTO 정제가 안전하다.

Entity명 username은 잘 알고 쓰자.
헷갈릴 거면 그냥 마음 편하게 다른 이름 쓰자
(나는 다음 프로젝트에서 username은 무조건 nickname으로 설계할거다)