Skip to content

Conversation

@Yujin1219
Copy link
Member

@Yujin1219 Yujin1219 commented Dec 9, 2025

#️⃣ 연관된 이슈

📝 작업 내용

  • JWT 기반 인증 시스템 구현
    • Access Token(1시간) , Refresh Token(2주)
    • RefreshToken 엔티티 생성
    • JWT 토큰 생성/검증 Provider 구현
    • Spring Security 인증 필터 및 설정 추가
    • 회원가입, 로그인, 토큰 재발급, 로그아웃 API 구현
    • 비밀번호 BCrypt 암호화 적용

📌 공유 사항

✅ 체크리스트

  • Reviewer에 팀원들을 선택 했나요?
  • Assignees에 본인을 선택 했나요?
  • Merge 하려는 브랜치가 올바르게 설정되어 있나요?
  • 컨벤션을 지키고 있나요?
  • 로컬에서 실행했을 때 에러가 발생하지 않나요?
  • 불필요한 주석이 제거되었나요?
  • 코드 스타일이 일관적인가요?

스크린샷 (선택)

💬 리뷰 요구사항 (선택)

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요? or 변경 사항 등

Summary by CodeRabbit

Release Notes

  • New Features

    • 이메일과 비밀번호로 회원가입 및 로그인 가능
    • JWT 기반의 안전한 토큰 인증 시스템 구현
    • 토큰 갱신 기능으로 지속적인 세션 유지
    • 사용자 로그아웃 기능 제공
  • Chores

    • Spring Security 및 JWT 라이브러리 의존성 추가
    • JPA 엔티티 변경 감시 기능 활성화

✏️ Tip: You can customize this high-level summary in your review settings.

@Yujin1219 Yujin1219 self-assigned this Dec 9, 2025
@Yujin1219 Yujin1219 added the ✨ feature 새로운 기능 개발 label Dec 9, 2025
@Yujin1219 Yujin1219 linked an issue Dec 9, 2025 that may be closed by this pull request
4 tasks
@Yujin1219 Yujin1219 merged commit 592a405 into develop Dec 9, 2025
2 checks passed
@Yujin1219 Yujin1219 deleted the feat/#15 branch December 9, 2025 17:40
@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Walkthrough

Spring Security 및 JWT 기반 인증 시스템을 구현하기 위해 의존성을 추가하고, 회원가입, 로그인, 토큰 갱신, 로그아웃 기능을 처리하는 컨트롤러, 서비스, DTO, 엔티티를 도입합니다. 보안 필터 체인과 토큰 유효성 검사 로직을 설정합니다.

Changes

코호트 / 파일(들) 변경 요약
빌드 및 의존성 설정
build.gradle
Spring Security, JWT 라이브러리(jjwt-api, jjwt-impl, jjwt-jackson), spring-boot-configuration-processor 의존성 활성화
애플리케이션 설정
src/main/java/com/example/triptalk/TriptalkApplication.java
\@EnableJpaAuditing 어노테이션 추가로 JPA 감사 기능 활성화
보안 구성
src/main/java/com/example/triptalk/global/config/SecurityConfig.java
BCrypt 기반 PasswordEncoder 빈과 JWT 기반 무상태 보안 필터 체인 구성 추가
JWT 토큰 처리
src/main/java/com/example/triptalk/global/security/jwt/JwtTokenProvider.java, JwtProperties.java, JwtAuthenticationFilter.java
JWT 생성, 검증, 추출 로직 및 요청 필터링 기능 구현
사용자 상세 정보
src/main/java/com/example/triptalk/global/security/CustomUserDetails.java, CustomUserDetailsService.java
Spring Security UserDetails 구현 및 이메일 기반 사용자 조회 서비스 추가
인증 컨트롤러
src/main/java/com/example/triptalk/domain/user/controller/AuthController.java
회원가입, 로그인, 토큰 갱신, 로그아웃 엔드포인트 정의
인증 서비스
src/main/java/com/example/triptalk/domain/user/service/AuthService.java, AuthServiceImpl.java
회원가입, 로그인, 토큰 갱신, 로그아웃 비즈니스 로직 구현
인증 DTO
src/main/java/com/example/triptalk/domain/user/dto/AuthRequest.java, AuthResponse.java
회원가입/로그인 요청 및 토큰/회원가입 응답 DTO 정의
인증 변환기
src/main/java/com/example/triptalk/domain/user/converter/AuthConverter.java
DTO와 엔티티 간 변환 메서드(toUser, toSignUpResponse, toTokenResponse) 제공
Refresh Token 관리
src/main/java/com/example/triptalk/domain/user/entity/RefreshToken.java, src/main/java/com/example/triptalk/domain/user/repository/RefreshTokenRepository.java
RefreshToken 엔티티 및 토큰 조회/삭제 기능을 지원하는 리포지토리 추가
사용자 리포지토리 확장
src/main/java/com/example/triptalk/domain/user/repository/UserRepository.java
이메일 중복 확인 메서드(existsByEmail) 추가
에러 상태 코드
src/main/java/com/example/triptalk/global/apiPayload/code/status/ErrorStatus.java
인증 관련 오류 상수(중복 이메일, 잘못된 비밀번호/토큰, 만료된 토큰) 추가

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.95% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 주요 변경 사항인 JWT 기반 인증 시스템과 회원가입/로그인 API 구현을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 주요 섹션을 충분히 포함하고 있으며, 작업 내용이 구체적으로 설명되어 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#15

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (9)
src/main/java/com/example/triptalk/TriptalkApplication.java (1)

7-7: @EnableJpaAuditing을 별도의 설정 클래스로 분리하는 것을 권장합니다.

메인 애플리케이션 클래스에 @EnableJpaAuditing을 선언하면 @WebMvcTest, @DataJpaTest 등 슬라이스 테스트에서 문제가 발생할 수 있습니다. JPA Auditing이 활성화되지만 AuditingHandler 빈이 로드되지 않아 테스트가 실패할 수 있습니다.

별도의 설정 클래스로 분리하는 것을 권장합니다:

// src/main/java/com/example/triptalk/global/config/JpaAuditingConfig.java
package com.example.triptalk.global.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {
}

그 후 메인 클래스에서 @EnableJpaAuditing을 제거하세요.

src/main/java/com/example/triptalk/global/security/jwt/JwtProperties.java (1)

12-16: 설정 속성에 유효성 검증 추가를 권장합니다.

JWT 설정은 보안에 중요한 요소이므로, 애플리케이션 시작 시 잘못된 설정을 조기에 감지할 수 있도록 유효성 검증을 추가하는 것이 좋습니다. 설정이 누락되면 런타임에 예상치 못한 오류가 발생할 수 있습니다.

+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Positive;
+import org.springframework.validation.annotation.Validated;
+
 @Getter
 @Setter
 @Component
+@Validated
 @ConfigurationProperties(prefix = "jwt")
 public class JwtProperties {
+    @NotBlank(message = "JWT secret은 필수입니다")
     private String secret;
+
+    @Positive(message = "Access token 만료 시간은 양수여야 합니다")
     private long accessTokenExpiration;
+
+    @Positive(message = "Refresh token 만료 시간은 양수여야 합니다")
     private long refreshTokenExpiration;
 }
src/main/java/com/example/triptalk/domain/user/entity/RefreshToken.java (2)

22-23: userId 컬럼에 인덱스 추가를 권장합니다.

RefreshTokenRepository에서 deleteByUserId(Long userId) 메서드가 사용될 것으로 예상됩니다. 사용자별 토큰 조회/삭제 성능을 위해 userId 컬럼에 인덱스를 추가하는 것이 좋습니다.

 @Entity
 @Getter
 @Builder
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
 @AllArgsConstructor
+@Table(indexes = @Index(name = "idx_refresh_token_user_id", columnList = "userId"))
 public class RefreshToken {

28-30: isExpired() 메서드의 시간대(timezone) 처리를 확인하세요.

LocalDateTime.now()는 시스템 기본 시간대를 사용합니다. 서버 배포 환경의 시간대가 다를 경우 예상치 못한 동작이 발생할 수 있습니다. 일관된 시간 처리를 위해 Instant 또는 명시적 시간대 사용을 고려해보세요.

-import java.time.LocalDateTime;
+import java.time.Instant;

 // ...

     @Column(nullable = false)
-    private LocalDateTime expiryDate;
+    private Instant expiryDate;

     public boolean isExpired() {
-        return LocalDateTime.now().isAfter(expiryDate);
+        return Instant.now().isAfter(expiryDate);
     }

또는 현재 구조를 유지하면서 명시적 시간대를 사용:

public boolean isExpired() {
    return LocalDateTime.now(ZoneOffset.UTC).isAfter(expiryDate);
}
src/main/java/com/example/triptalk/global/security/CustomUserDetails.java (1)

34-52: 계정 상태 체크 메서드들이 하드코딩되어 있습니다.

현재 모든 계정 상태 확인 메서드(isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled)가 true를 반환하도록 하드코딩되어 있습니다.

초기 구현으로는 적절하지만, 향후 계정 잠금, 만료, 비활성화 등의 기능이 필요할 경우 User 엔티티에 해당 필드를 추가하고 실제 상태를 반환하도록 개선하는 것을 고려해보세요.

src/main/java/com/example/triptalk/global/security/jwt/JwtAuthenticationFilter.java (1)

50-52: 예외 처리 개선을 고려해보세요.

현재는 예외 발생 시 로그만 남기고 필터 체인을 계속 진행합니다. 선택적으로, 클라이언트에게 명확한 에러 응답(예: 401 Unauthorized)을 반환하는 것을 고려해볼 수 있습니다.

다만 현재 구현도 인증 실패 시 SecurityContext에 인증 정보가 설정되지 않아 이후 필터/컨트롤러에서 자동으로 401 처리되므로, 현재 구현도 충분히 동작합니다.

src/main/java/com/example/triptalk/global/security/jwt/JwtTokenProvider.java (2)

21-23: 서명 키 생성을 최적화하세요.

getSigningKey()가 호출될 때마다 새로운 SecretKey 객체를 생성합니다. 이 키는 변경되지 않으므로 필드로 캐싱하여 성능을 개선할 수 있습니다.

다음 diff를 적용하세요:

 @Component
 @RequiredArgsConstructor
 @Getter
 public class JwtTokenProvider {
 
     private final JwtProperties jwtProperties;
+    private SecretKey signingKey;
 
-    // SecretKey 생성
-    private SecretKey getSigningKey() {
-        return Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8));
+    private SecretKey getSigningKey() {
+        if (signingKey == null) {
+            signingKey = Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8));
+        }
+        return signingKey;
     }

63-73: 토큰 검증 실패 시 원인을 구분하세요.

현재 validateToken() 메서드는 모든 예외를 동일하게 처리하여 false를 반환합니다. 하지만 AuthServiceImplreissue() 메서드(Line 70-72, 79-82)에서는 만료된 토큰과 유효하지 않은 토큰을 구분하여 처리해야 합니다.

현재 구현에서는 validateToken()false를 반환하면 AUTH_INVALID_TOKEN 에러를 발생시키고, 이후 DB 조회에서 토큰 만료 여부를 확인합니다. 이는 논리적으로 작동하지만, 만료된 토큰도 서명이 유효하다면 validateToken()을 통과해야 더 명확한 에러 메시지를 제공할 수 있습니다.

더 명확한 에러 처리를 위해 다음과 같이 개선할 수 있습니다:

-    // 토큰 유효성 검증(서명, 만료시간)
-    public boolean validateToken(String token) {
-        try {
-            Jwts.parser()
-                    .verifyWith(getSigningKey())
-                    .build()
-                    .parseSignedClaims(token);
-            return true;
-        } catch (JwtException | IllegalArgumentException e) {
-            return false;
-        }
-    }
+    // 토큰 서명 유효성 검증 (만료 여부는 별도 확인)
+    public boolean validateTokenSignature(String token) {
+        try {
+            Jwts.parser()
+                    .verifyWith(getSigningKey())
+                    .build()
+                    .parseSignedClaims(token);
+            return true;
+        } catch (ExpiredJwtException e) {
+            // 만료된 토큰이지만 서명은 유효함
+            return true;
+        } catch (JwtException | IllegalArgumentException e) {
+            return false;
+        }
+    }
+
+    public boolean isTokenExpired(String token) {
+        try {
+            Jwts.parser()
+                    .verifyWith(getSigningKey())
+                    .build()
+                    .parseSignedClaims(token);
+            return false;
+        } catch (ExpiredJwtException e) {
+            return true;
+        } catch (JwtException | IllegalArgumentException e) {
+            throw new IllegalArgumentException("Invalid token");
+        }
+    }

그리고 AuthServiceImpl.reissue()에서 이를 활용하도록 수정해야 합니다.

src/main/java/com/example/triptalk/domain/user/service/AuthServiceImpl.java (1)

47-65: 로그인 로직이 올바르게 구현되었습니다.

사용자 인증, 토큰 생성, 리프레시 토큰 저장이 적절하게 처리되었습니다. 다만, 프로덕션 환경에서는 무차별 대입 공격을 방지하기 위해 로그인 시도 횟수 제한(rate limiting)을 고려하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a65c9df and 3972c9d.

📒 Files selected for processing (18)
  • build.gradle (1 hunks)
  • src/main/java/com/example/triptalk/TriptalkApplication.java (1 hunks)
  • src/main/java/com/example/triptalk/domain/user/controller/AuthController.java (1 hunks)
  • src/main/java/com/example/triptalk/domain/user/converter/AuthConverter.java (1 hunks)
  • src/main/java/com/example/triptalk/domain/user/dto/AuthRequest.java (1 hunks)
  • src/main/java/com/example/triptalk/domain/user/dto/AuthResponse.java (1 hunks)
  • src/main/java/com/example/triptalk/domain/user/entity/RefreshToken.java (1 hunks)
  • src/main/java/com/example/triptalk/domain/user/repository/RefreshTokenRepository.java (1 hunks)
  • src/main/java/com/example/triptalk/domain/user/repository/UserRepository.java (1 hunks)
  • src/main/java/com/example/triptalk/domain/user/service/AuthService.java (1 hunks)
  • src/main/java/com/example/triptalk/domain/user/service/AuthServiceImpl.java (1 hunks)
  • src/main/java/com/example/triptalk/global/apiPayload/code/status/ErrorStatus.java (1 hunks)
  • src/main/java/com/example/triptalk/global/config/SecurityConfig.java (1 hunks)
  • src/main/java/com/example/triptalk/global/security/CustomUserDetails.java (1 hunks)
  • src/main/java/com/example/triptalk/global/security/CustomUserDetailsService.java (1 hunks)
  • src/main/java/com/example/triptalk/global/security/jwt/JwtAuthenticationFilter.java (1 hunks)
  • src/main/java/com/example/triptalk/global/security/jwt/JwtProperties.java (1 hunks)
  • src/main/java/com/example/triptalk/global/security/jwt/JwtTokenProvider.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/main/java/com/example/triptalk/domain/user/repository/UserRepository.java (1)
src/main/java/com/example/triptalk/domain/user/service/UserServiceImpl.java (2)
  • Service (13-30)
  • Override (20-29)
src/main/java/com/example/triptalk/domain/user/repository/RefreshTokenRepository.java (3)
src/main/java/com/example/triptalk/domain/tripPlan/repository/TripHighlightRepository.java (2)
  • TripHighlightRepository (8-10)
  • findByTripPlanId (9-9)
src/main/java/com/example/triptalk/domain/tripPlan/repository/TripTransportationRepository.java (1)
  • TripTransportationRepository (8-10)
src/main/java/com/example/triptalk/domain/tripPlan/repository/TripAccommodationRepository.java (1)
  • TripAccommodationRepository (8-10)
src/main/java/com/example/triptalk/domain/user/converter/AuthConverter.java (3)
src/main/java/com/example/triptalk/domain/user/dto/AuthRequest.java (1)
  • AuthRequest (8-45)
src/main/java/com/example/triptalk/domain/user/dto/AuthResponse.java (1)
  • AuthResponse (8-36)
src/main/java/com/example/triptalk/domain/user/dto/UserResponse.java (2)
  • Getter (15-29)
  • Getter (10-76)
src/main/java/com/example/triptalk/domain/user/controller/AuthController.java (2)
src/main/java/com/example/triptalk/domain/user/dto/AuthRequest.java (1)
  • AuthRequest (8-45)
src/main/java/com/example/triptalk/domain/user/dto/AuthResponse.java (1)
  • AuthResponse (8-36)
src/main/java/com/example/triptalk/domain/user/service/AuthServiceImpl.java (3)
src/main/java/com/example/triptalk/domain/user/dto/AuthRequest.java (1)
  • AuthRequest (8-45)
src/main/java/com/example/triptalk/domain/user/dto/AuthResponse.java (1)
  • AuthResponse (8-36)
src/main/java/com/example/triptalk/global/apiPayload/exception/handler/ErrorHandler.java (1)
  • ErrorHandler (6-10)
src/main/java/com/example/triptalk/global/security/jwt/JwtTokenProvider.java (1)
src/main/java/com/example/triptalk/global/apiPayload/exception/ExceptionAdvice.java (1)
  • RequiredArgsConstructor (26-120)
🔇 Additional comments (12)
src/main/java/com/example/triptalk/domain/user/repository/UserRepository.java (1)

13-14: LGTM!

existsByEmail 메서드는 Spring Data JPA의 파생 쿼리 네이밍 컨벤션을 올바르게 따르고 있으며, 이메일 중복 체크에 효율적인 방식입니다. findByEmail().isPresent() 대신 existsBy를 사용하면 불필요한 엔티티 로딩 없이 SELECT COUNT 또는 EXISTS 쿼리로 최적화됩니다.

src/main/java/com/example/triptalk/domain/user/dto/AuthResponse.java (1)

8-36: LGTM!

DTO 구조가 깔끔하고 Lombok 어노테이션과 Swagger 문서화가 잘 적용되어 있습니다. 정적 내부 클래스로 관련 DTO들을 그룹화한 패턴은 유지보수성을 높여줍니다.

src/main/java/com/example/triptalk/global/config/SecurityConfig.java (1)

19-44: 보안 설정이 적절하게 구성되었습니다.

JWT 기반 stateless 인증을 위한 Spring Security 설정이 올바르게 구현되었습니다. CSRF 비활성화, 세션 관리 정책, 엔드포인트 권한 설정, 필터 체인 순서 모두 적절합니다.

src/main/java/com/example/triptalk/domain/user/service/AuthService.java (1)

6-15: 인증 서비스 인터페이스가 명확하게 정의되었습니다.

회원가입, 로그인, 토큰 재발급, 로그아웃의 핵심 인증 기능들이 간결하게 정의되어 있습니다.

src/main/java/com/example/triptalk/global/security/CustomUserDetailsService.java (1)

11-23: UserDetailsService 구현이 적절합니다.

Spring Security와의 통합을 위한 사용자 조회 로직이 올바르게 구현되었습니다.

src/main/java/com/example/triptalk/domain/user/converter/AuthConverter.java (1)

8-35: 컨버터 구현이 깔끔하게 작성되었습니다.

DTO와 엔티티 간 변환 로직이 명확하고 올바르게 구현되었습니다. 빌더 패턴을 활용하여 가독성이 좋습니다.

src/main/java/com/example/triptalk/global/security/CustomUserDetails.java (1)

15-57: UserDetails 구현이 올바르게 작성되었습니다.

Spring Security의 UserDetails 인터페이스를 적절하게 구현하여 도메인 User 엔티티를 감싸고 있습니다.

src/main/java/com/example/triptalk/global/security/jwt/JwtAuthenticationFilter.java (2)

26-55: JWT 인증 필터가 올바르게 구현되었습니다.

토큰 추출, 검증, SecurityContext 설정 로직이 적절하게 구현되어 있습니다. 예외 처리도 포함되어 있어 안정적입니다.


58-64: 토큰 추출 로직이 명확합니다.

Authorization 헤더에서 Bearer 토큰을 추출하는 로직이 올바르게 구현되었습니다.

src/main/java/com/example/triptalk/global/apiPayload/code/status/ErrorStatus.java (1)

23-28: 인증 관련 에러 코드가 적절하게 추가되었습니다.

HTTP 상태 코드와 에러 메시지가 명확하고 일관성 있게 정의되었습니다.

src/main/java/com/example/triptalk/domain/user/service/AuthServiceImpl.java (2)

32-44: 회원가입 로직이 올바르게 구현되었습니다.

이메일 중복 체크, 비밀번호 인코딩, 사용자 저장이 적절하게 처리되었습니다.


100-104: 로그아웃 로직이 올바르게 구현되었습니다.

사용자의 모든 리프레시 토큰을 삭제하여 로그아웃을 처리합니다.

Comment on lines +36 to +38
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check latest JJWT version on Maven Central
curl -s "https://search.maven.org/solrsearch/select?q=g:io.jsonwebtoken+AND+a:jjwt-api&rows=1&wt=json" | jq '.response.docs[0].latestVersion'

Repository: TripTalk/BE_SpringBoot

Length of output: 74


JJWT 의존성을 최신 버전으로 업데이트하세요.

현재 0.12.3 버전이 사용 중이나, Maven Central의 최신 안정 버전은 0.12.6입니다. 보안 패치 및 버그 수정을 포함한 최신 버전으로 업데이트하는 것을 권장합니다.

🤖 Prompt for AI Agents
In build.gradle around lines 36 to 38, the JJWT dependencies are pinned to
0.12.3; update all three coordinates (io.jsonwebtoken:jjwt-api, jjwt-impl,
jjwt-jackson) to the latest stable 0.12.6 to pick up security and bug fixes,
then refresh/resolve Gradle dependencies (e.g., gradle --refresh-dependencies or
IDE sync) and run tests to verify no regressions.

implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
implementation 'org.springframework.boot:spring-boot-configuration-processor'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

spring-boot-configuration-processorannotationProcessor로 선언해야 합니다.

spring-boot-configuration-processor는 컴파일 시점에 @ConfigurationProperties 클래스에 대한 메타데이터를 생성하는 어노테이션 프로세서입니다. implementation 대신 annotationProcessor로 선언해야 올바르게 동작합니다.

-	implementation 'org.springframework.boot:spring-boot-configuration-processor'
+	annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
implementation 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
🤖 Prompt for AI Agents
In build.gradle around line 39, the dependency declaration uses implementation
'org.springframework.boot:spring-boot-configuration-processor' but this library
is an annotation processor and must be declared as annotationProcessor; replace
the implementation configuration with annotationProcessor
'org.springframework.boot:spring-boot-configuration-processor' (optionally also
add a compileOnly entry if IDEs need the classes at compile time).


@Operation(summary = "회원가입", description = "이메일, 비밀번호, 닉네임으로 회원가입합니다.")
@PostMapping("/signup")
public ApiResponse<AuthResponse.SignUpDTO> signUp(@RequestBody AuthRequest.SignUpDTO request) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

요청 DTO 유효성 검증을 추가하세요.

요청 본문에 대한 입력 검증이 없습니다. @Valid 어노테이션을 추가하여 DTO 필드의 유효성을 검증해야 합니다.

다음 diff를 적용하세요:

-    public ApiResponse<AuthResponse.SignUpDTO> signUp(@RequestBody AuthRequest.SignUpDTO request) {
+    public ApiResponse<AuthResponse.SignUpDTO> signUp(@Valid @RequestBody AuthRequest.SignUpDTO request) {

또한 AuthRequest.SignUpDTO 클래스에 적절한 검증 어노테이션(예: @NotBlank, @Email, @Size 등)을 추가해야 합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public ApiResponse<AuthResponse.SignUpDTO> signUp(@RequestBody AuthRequest.SignUpDTO request) {
public ApiResponse<AuthResponse.SignUpDTO> signUp(@Valid @RequestBody AuthRequest.SignUpDTO request) {
🤖 Prompt for AI Agents
In src/main/java/com/example/triptalk/domain/user/controller/AuthController.java
around line 24, the signUp endpoint is missing request body validation; add the
@Valid annotation to the method parameter (e.g., public
ApiResponse<AuthResponse.SignUpDTO> signUp(@Valid @RequestBody
AuthRequest.SignUpDTO request)) so Spring triggers validation, and then update
AuthRequest.SignUpDTO to include appropriate Jakarta/javax validation
annotations on its fields (e.g., @NotBlank, @Email, @Size, @NotNull as
applicable) to enforce input constraints and enable automatic 400 responses on
invalid input.


@Operation(summary = "로그인", description = "이메일과 비밀번호로 로그인하여 토큰을 발급받습니다.")
@PostMapping("/login")
public ApiResponse<AuthResponse.TokenDTO> login(@RequestBody AuthRequest.LoginDTO request) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

요청 DTO 유효성 검증을 추가하세요.

로그인 요청에 대한 입력 검증이 없습니다.

다음 diff를 적용하세요:

-    public ApiResponse<AuthResponse.TokenDTO> login(@RequestBody AuthRequest.LoginDTO request) {
+    public ApiResponse<AuthResponse.TokenDTO> login(@Valid @RequestBody AuthRequest.LoginDTO request) {
🤖 Prompt for AI Agents
In src/main/java/com/example/triptalk/domain/user/controller/AuthController.java
around line 30, the login endpoint accepts the DTO without any validation; add
request validation by annotating the controller parameter with @Valid (and
import javax/jakarta.validation.Valid) and ensure the AuthRequest.LoginDTO
fields have proper javax/jakarta.validation annotations (e.g., @NotBlank,
@Email, @Size) in its class; either add a BindingResult parameter to return a
formatted ApiResponse on validation failure or rely on an existing global
MethodArgumentNotValidException handler to convert validation errors into
ApiResponse; also add @Validated at the controller class level if needed.


@Operation(summary = "토큰 재발급", description = "Refresh Token으로 Access Token을 재발급받습니다.")
@PostMapping("/refresh")
public ApiResponse<AuthResponse.TokenDTO> reissue(@RequestBody AuthRequest.ReissueDTO request) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

요청 DTO 유효성 검증을 추가하세요.

토큰 재발급 요청에 대한 입력 검증이 없습니다.

다음 diff를 적용하세요:

-    public ApiResponse<AuthResponse.TokenDTO> reissue(@RequestBody AuthRequest.ReissueDTO request) {
+    public ApiResponse<AuthResponse.TokenDTO> reissue(@Valid @RequestBody AuthRequest.ReissueDTO request) {
🤖 Prompt for AI Agents
In src/main/java/com/example/triptalk/domain/user/controller/AuthController.java
around line 36, the reissue endpoint accepts the ReissueDTO without validation;
add request validation by annotating the parameter with @Valid (i.e., public
ApiResponse<AuthResponse.TokenDTO> reissue(@Valid @RequestBody
AuthRequest.ReissueDTO request)), import javax.validation.Valid, and annotate
the controller class with @Validated if not present. Update
AuthRequest.ReissueDTO to declare explicit validation constraints on its fields
(e.g., @NotBlank for token strings, @NotNull where appropriate) and import the
corresponding javax.validation annotations. Rely on Spring’s
MethodArgumentNotValidException to return 400 or add an exception handler if you
want custom error responses.

Comment on lines +41 to +44
public static class ReissueDTO {
@Schema(description = "리프레시 토큰")
private String refreshToken;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

토큰 재발급 DTO에도 검증이 필요합니다.

     @Getter
     @NoArgsConstructor
     @AllArgsConstructor
     @Schema(description = "토큰 재발급 요청")
     public static class ReissueDTO {
+        @NotBlank(message = "리프레시 토큰은 필수입니다")
         @Schema(description = "리프레시 토큰")
         private String refreshToken;
     }
🤖 Prompt for AI Agents
In src/main/java/com/example/triptalk/domain/user/dto/AuthRequest.java around
lines 41-44, the ReissueDTO lacks validation on its refreshToken field; annotate
the field with a javax.validation constraint such as @NotBlank (with an
appropriate message) to enforce non-empty tokens, import the constraint, and
ensure any controller consuming this DTO uses @Valid on the request body so
validation is triggered.

Comment on lines +8 to +12
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
Optional<RefreshToken> findByToken(String token);
Optional<RefreshToken> findByUserId(Long userId);
void deleteByUserId(Long userId);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

삭제 메서드에 필수 어노테이션이 누락되었습니다.

deleteByUserId 메서드는 커스텀 삭제 쿼리이므로 @Modifying@Transactional 어노테이션이 필요합니다. 이 어노테이션들이 없으면 삭제 작업이 제대로 실행되지 않을 수 있습니다.

다음 diff를 적용하세요:

 package com.example.triptalk.domain.user.repository;

 import com.example.triptalk.domain.user.entity.RefreshToken;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.transaction.annotation.Transactional;

 import java.util.Optional;

 public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
     Optional<RefreshToken> findByToken(String token);
     Optional<RefreshToken> findByUserId(Long userId);
+    
+    @Modifying
+    @Transactional
     void deleteByUserId(Long userId);
 }
🤖 Prompt for AI Agents
In
src/main/java/com/example/triptalk/domain/user/repository/RefreshTokenRepository.java
around lines 8 to 12, the deleteByUserId method is a modifying query but is
missing the required @Modifying and @Transactional annotations; update the
repository by annotating the deleteByUserId method with @Modifying and
@Transactional (or mark the repository/class transactional) and add the
corresponding imports (org.springframework.data.jpa.repository.Modifying and
org.springframework.transaction.annotation.Transactional) so the delete
operation executes correctly.

Comment on lines +92 to +94
// 기존 RefreshToken 삭제 후 새로운 토큰 저장
refreshTokenRepository.delete(refreshToken);
saveRefreshToken(user.getId(), newRefreshToken);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

중복된 삭제 로직을 제거하세요.

Line 93에서 기존 RefreshToken을 삭제한 후, Line 94의 saveRefreshToken() 메서드 내부(Line 108)에서 다시 동일한 삭제 작업을 수행합니다. 이는 불필요한 중복입니다.

다음 중 하나를 선택하여 수정하세요:

옵션 1: reissue()에서 삭제 제거 (권장)

         // 새로운 토큰 생성
         String newAccessToken = jwtTokenProvider.createAccessToken(user.getEmail());
         String newRefreshToken = jwtTokenProvider.createRefreshToken(user.getEmail());
 
-        // 기존 RefreshToken 삭제 후 새로운 토큰 저장
-        refreshTokenRepository.delete(refreshToken);
+        // 새로운 토큰 저장 (saveRefreshToken이 기존 토큰 삭제 처리)
         saveRefreshToken(user.getId(), newRefreshToken);

옵션 2: saveRefreshToken()을 upsert 방식으로 변경

현재 deleteByUserIdsave하는 패턴보다는, 기존 토큰이 있으면 업데이트하고 없으면 생성하는 방식이 더 효율적일 수 있습니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/example/triptalk/domain/user/service/AuthServiceImpl.java
around lines 92-94 (and note saveRefreshToken's internal deletion at line ~108),
the refresh token is being deleted twice: once in reissue() (line 93) and again
inside saveRefreshToken(). Remove the redundant delete from reissue() so
saveRefreshToken() remains the single place handling replacement, or
alternatively implement an upsert in saveRefreshToken() (replace
deleteByUserId+save with findByUserId and update if present, otherwise create) —
preferred: delete the explicit refreshTokenRepository.delete(refreshToken) call
in reissue(), leaving saveRefreshToken() to handle persistence consistently.

Comment on lines +114 to +115
.expiryDate(LocalDateTime.now().plusSeconds(
jwtTokenProvider.getRefreshTokenExpiration() / 1000))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for getRefreshTokenExpiration method definition and refreshTokenExpiration property
rg -n -A5 -B5 "getRefreshTokenExpiration|refreshTokenExpiration" --type java

Repository: TripTalk/BE_SpringBoot

Length of output: 4415


🏁 Script executed:

#!/bin/bash
# Read JwtProperties to see if there are any comments
cat -n src/main/java/com/example/triptalk/global/security/jwt/JwtProperties.java

echo "---"

# Search for JWT configuration in properties/yaml files
find . -type f \( -name "application*.yml" -o -name "application*.yaml" -o -name "application*.properties" \) -exec cat {} \; | grep -A2 -B2 "jwt:" || echo "No JWT config found in standard locations"

Repository: TripTalk/BE_SpringBoot

Length of output: 667


확인된 문제: 만료 시간 계산 시 단위 변환이 명확하지 않음

getRefreshTokenExpiration()은 밀리초를 반환합니다 (JwtTokenProvider의 Date 생성자 사용법으로 확인). AuthServiceImpl에서 /1000으로 나누어 초 단위로 변환하는 것은 LocalDateTime.plusSeconds()의 요구사항에 맞게 올바르게 동작합니다.

다만 다음과 같이 개선하면 코드 의도가 더욱 명확해집니다:

  • JwtProperties의 refreshTokenExpiration 필드에 /** 밀리초(ms) 단위 */ 주석 추가
  • AuthServiceImpl 115줄의 /1000 위에 // 밀리초를 초 단위로 변환 주석 추가
  • 또는 명시적인 단위 변환 메서드(예: Duration.ofMillis(ms).toSeconds()) 사용 검토
🤖 Prompt for AI Agents
In src/main/java/com/example/triptalk/domain/user/service/AuthServiceImpl.java
around lines 114-115, the expiry calculation uses
jwtTokenProvider.getRefreshTokenExpiration()/1000 which is correct but unclear
about units; update to make intent explicit by either adding a comment above the
division: "// 밀리초를 초 단위로 변환", or better replace the expression with an explicit
conversion using Duration (e.g.,
Duration.ofMillis(jwtTokenProvider.getRefreshTokenExpiration()).toSeconds()),
and also add a Javadoc/comment on JwtProperties.refreshTokenExpiration
indicating it is in milliseconds (/** 밀리초(ms) 단위 */) so the unit is clear across
the codebase.

Comment on lines +52 to +60
public String getEmailFromToken(String token) {
Claims claims = Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();

return claims.getSubject();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

null 체크를 추가하세요.

token 파라미터에 대한 null 체크가 없습니다. null이 전달되면 예외가 발생할 수 있습니다.

다음 diff를 적용하세요:

     public String getEmailFromToken(String token) {
+        if (token == null || token.trim().isEmpty()) {
+            throw new IllegalArgumentException("Token cannot be null or empty");
+        }
         Claims claims = Jwts.parser()
                 .verifyWith(getSigningKey())
                 .build()
                 .parseSignedClaims(token)
                 .getPayload();
 
         return claims.getSubject();
     }

validateToken() 및 토큰 생성 메서드에도 동일한 검증을 추가하는 것이 좋습니다.

🤖 Prompt for AI Agents
In src/main/java/com/example/triptalk/global/security/jwt/JwtTokenProvider.java
around lines 52 to 60, add a null/empty check for the token parameter before
parsing: if token is null or blank, throw an IllegalArgumentException (or a
project-specific auth exception) with a clear message instead of attempting to
parse; then proceed to parse when non-empty. Also apply the same null/empty
validation to validateToken() and any token-creation methods so they
consistently reject or handle null/empty tokens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feature 새로운 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Feat: 로그인/회원가입 API 구현

2 participants