Spring Boot JWT에 사용자 nickname 필드 추가하기

geoson·2025년 6월 23일

Spring & 백엔드

목록 보기
17/18

Spring Boot 프로젝트에서 기존 JWT 토큰에 사용자의 nickname 정보를 추가하는 과정과 마주친 트러블슈팅을 공유합니다.

📋 요구사항

기획팀에서 사용자 정보에 nickname이 필요하다는 요청이 들어왔습니다.

  • User 테이블에 nickname 컬럼 추가 (중복 허용)
  • JWT 토큰에 nickname 정보 포함
  • 프론트엔드에서 JWT로부터 nickname 추출 가능

🔧 구현 과정

1. User 엔티티 수정

먼저 User 엔티티에 nickname 필드를 추가했습니다.

@Getter
@Entity
@NoArgsConstructor
@Table(name = "users")
public class User extends Timestamped {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true)
    private String email;
    
    private String password;
    
    @Enumerated(EnumType.STRING)
    private UserRole userRole;
    
    private String nickname; // ✅ 새로 추가
    
    // 기존 생성자 유지 (호환성)
    public User(String email, String password, UserRole userRole) {
        this(email, password, userRole, null);
    }
    
    // nickname 포함 생성자 추가
    public User(String email, String password, UserRole userRole, String nickname) {
        this.email = email;
        this.password = password;
        this.userRole = userRole;
        this.nickname = nickname;
    }
    
    // fromAuthUser 메서드도 수정
    private User(Long id, String email, UserRole userRole, String nickname) {
        this.id = id;
        this.email = email;
        this.userRole = userRole;
        this.nickname = nickname;
    }

    public static User fromAuthUser(AuthUser authUser) {
        return new User(authUser.getId(), authUser.getEmail(), 
                       authUser.getUserRole(), authUser.getNickname());
    }
}

2. JWT 생성 로직 수정

JwtUtil 클래스의 토큰 생성 메서드에 nickname을 추가했습니다.

@Component
public class JwtUtil {
    
    public String createToken(Long userId, String email, UserRole userRole, String nickname) {
        Date date = new Date();

        return BEARER_PREFIX +
                Jwts.builder()
                        .setSubject(String.valueOf(userId))
                        .claim("email", email)
                        .claim("userRole", userRole)
                        .claim("nickname", nickname) // ✅ nickname 클레임 추가
                        .setExpiration(new Date(date.getTime() + TOKEN_TIME))
                        .setIssuedAt(date)
                        .signWith(key, signatureAlgorithm)
                        .compact();
    }
}

3. 회원가입/로그인 Request DTO 수정

회원가입 시 nickname을 받을 수 있도록 DTO를 수정했습니다.

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class SignupRequest {
    @NotBlank @Email
    private String email;
    
    @NotBlank
    private String password;
    
    @NotBlank
    private String userRole;
    
    @NotBlank
    private String nickname; // ✅ 추가
}

4. AuthUser DTO 수정

인증된 사용자 정보를 담는 AuthUser에도 nickname을 추가했습니다.

@Getter
public class AuthUser {
    private final Long id;
    private final String email;
    private final UserRole userRole;
    private final String nickname; // ✅ 추가

    public AuthUser(Long id, String email, UserRole userRole, String nickname) {
        this.id = id;
        this.email = email;
        this.userRole = userRole;
        this.nickname = nickname;
    }
}

5. AuthService 로직 수정

회원가입과 로그인 로직에서 nickname을 처리하도록 수정했습니다.

@Service
@RequiredArgsConstructor
public class AuthService {

    @Transactional
    public SignupResponse signup(SignupRequest signupRequest) {
        // 기존 validation 로직...
        
        User newUser = new User(
                signupRequest.getEmail(),
                encodedPassword,
                userRole,
                signupRequest.getNickname() // ✅ nickname 추가
        );
        User savedUser = userRepository.save(newUser);

        String bearerToken = jwtUtil.createToken(
                savedUser.getId(), 
                savedUser.getEmail(), 
                userRole, 
                savedUser.getNickname() // ✅ nickname 포함
        );

        return new SignupResponse(bearerToken);
    }

    public SigninResponse signin(SigninRequest signinRequest) {
        User user = userRepository.findByEmail(signinRequest.getEmail())
                .orElseThrow(() -> new InvalidRequestException("가입되지 않은 유저입니다."));

        // 비밀번호 검증...

        String bearerToken = jwtUtil.createToken(
                user.getId(), 
                user.getEmail(), 
                user.getUserRole(), 
                user.getNickname() // ✅ nickname 포함
        );

        return new SigninResponse(bearerToken);
    }
}

6. JWT 필터와 ArgumentResolver 수정

마지막으로 JWT 파싱과 AuthUser 생성 부분을 수정했습니다.

// JwtFilter.java
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // JWT 파싱 로직...
    
    httpRequest.setAttribute("userId", Long.parseLong(claims.getSubject()));
    httpRequest.setAttribute("email", claims.get("email"));
    httpRequest.setAttribute("userRole", claims.get("userRole"));
    httpRequest.setAttribute("nickname", claims.get("nickname")); // ✅ 추가
    
    // 나머지 로직...
}
// AuthUserArgumentResolver.java
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
    HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();

    Long userId = (Long) request.getAttribute("userId");
    String email = (String) request.getAttribute("email");
    UserRole userRole = UserRole.of((String) request.getAttribute("userRole"));
    String nickname = (String) request.getAttribute("nickname"); // ✅ 추가

    return new AuthUser(userId, email, userRole, nickname);
}

🚨 트러블슈팅

1. 생성자 오버로딩 문제

문제: User 엔티티에 nickname을 추가하면서 기존 생성자와 충돌

해결방법:

  • 기존 생성자는 유지하고 nickname 포함 생성자를 추가
  • 기존 생성자에서 새 생성자를 호출하도록 체이닝
public User(String email, String password, UserRole userRole) {
    this(email, password, userRole, null); // 기본값 null로 체이닝
}

2. 컴파일 에러 - 메서드 시그니처 변경

문제: createToken 메서드에 nickname 파라미터 추가 후 기존 호출부에서 에러

해결방법:

  • AuthService의 모든 createToken 호출부를 찾아서 nickname 파라미터 추가
  • IDE의 "Find Usages" 기능 활용

3. 기존 JWT 토큰 호환성 문제

문제: 이미 발급된 토큰에는 nickname 클레임이 없어서 파싱 에러

해결방법:

String nickname = (String) claims.get("nickname");
if (nickname == null) {
    nickname = ""; // 기본값 설정
}

4. 데이터베이스 스키마 불일치

문제: 기존 DB에는 nickname 컬럼이 없어서 애플리케이션 실행 시 에러

해결방법:

  • H2 DB: 애플리케이션 재시작으로 자동 생성
  • MySQL: spring.jpa.hibernate.ddl-auto=update 설정 또는 수동 DDL 실행

✅ 테스트 결과

회원가입 테스트

POST /auth/signup
{
    "email": "test@example.com",
    "password": "password123",
    "userRole": "USER",
    "nickname": "테스터"
}

JWT 토큰 디코딩 결과

{
    "sub": "1",
    "email": "test@example.com",
    "userRole": "USER",
    "nickname": "테스터",
    "exp": 1640995200,
    "iat": 1640991600
}

💡 배운 점

  1. 점진적 수정의 중요성: 한 번에 모든 파일을 수정하지 말고 단계별로 테스트하면서 진행
  2. 기존 코드 호환성 고려: 새로운 필드 추가 시 기존 기능이 깨지지 않도록 주의
  3. JWT 클레임 확장: JWT에 새로운 정보를 추가할 때는 기존 토큰과의 호환성 고려 필요
  4. 테스트의 중요성: 각 단계마다 기능이 정상 동작하는지 확인

이 포스팅이 JWT에 새로운 필드를 추가하는 과정에서 도움이 되었기를 바랍니다! 궁금한 점이 있다면 댓글로 남겨주세요. 😊

0개의 댓글