-
Notifications
You must be signed in to change notification settings - Fork 1
[#15] Feat: 로그인/회원가입 API 구현 #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
aed1024
a7f8588
ad3339d
4be2b80
21bca84
ca06760
3de62b8
fa99ee7
50e32ed
fbf446f
eff3587
4cf359f
5e148e9
c0f2e49
61cc90f
3972c9d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -29,8 +29,14 @@ dependencies { | |||||
| implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | ||||||
|
|
||||||
| // Spring Security | ||||||
| // implementation 'org.springframework.boot:spring-boot-starter-security' | ||||||
| // testImplementation 'org.springframework.security:spring-security-test' | ||||||
| implementation 'org.springframework.boot:spring-boot-starter-security' | ||||||
| testImplementation 'org.springframework.security:spring-security-test' | ||||||
|
|
||||||
| // Jwt | ||||||
| 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' | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
- implementation 'org.springframework.boot:spring-boot-configuration-processor'
+ annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| // Spring Web | ||||||
| implementation 'org.springframework.boot:spring-boot-starter-web' | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||
| package com.example.triptalk.domain.user.controller; | ||||||
|
|
||||||
| import com.example.triptalk.domain.user.dto.AuthRequest; | ||||||
| import com.example.triptalk.domain.user.dto.AuthResponse; | ||||||
| import com.example.triptalk.domain.user.service.AuthService; | ||||||
| import com.example.triptalk.global.apiPayload.ApiResponse; | ||||||
| import io.swagger.v3.oas.annotations.Operation; | ||||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||||
| import lombok.RequiredArgsConstructor; | ||||||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||||||
| import org.springframework.security.core.userdetails.UserDetails; | ||||||
| import org.springframework.web.bind.annotation.*; | ||||||
|
|
||||||
| @Tag(name = "Auth", description = "인증 관련 API") | ||||||
| @RestController | ||||||
| @RequestMapping("/api/auth") | ||||||
| @RequiredArgsConstructor | ||||||
| public class AuthController { | ||||||
|
|
||||||
| private final AuthService authService; | ||||||
|
|
||||||
| @Operation(summary = "회원가입", description = "이메일, 비밀번호, 닉네임으로 회원가입합니다.") | ||||||
| @PostMapping("/signup") | ||||||
| public ApiResponse<AuthResponse.SignUpDTO> signUp(@RequestBody AuthRequest.SignUpDTO request) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 요청 DTO 유효성 검증을 추가하세요. 요청 본문에 대한 입력 검증이 없습니다. 다음 diff를 적용하세요: - public ApiResponse<AuthResponse.SignUpDTO> signUp(@RequestBody AuthRequest.SignUpDTO request) {
+ public ApiResponse<AuthResponse.SignUpDTO> signUp(@Valid @RequestBody AuthRequest.SignUpDTO request) {또한 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| return ApiResponse.onSuccess(authService.signUp(request)); | ||||||
| } | ||||||
|
|
||||||
| @Operation(summary = "로그인", description = "이메일과 비밀번호로 로그인하여 토큰을 발급받습니다.") | ||||||
| @PostMapping("/login") | ||||||
| public ApiResponse<AuthResponse.TokenDTO> login(@RequestBody AuthRequest.LoginDTO request) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
| return ApiResponse.onSuccess(authService.login(request)); | ||||||
| } | ||||||
|
|
||||||
| @Operation(summary = "토큰 재발급", description = "Refresh Token으로 Access Token을 재발급받습니다.") | ||||||
| @PostMapping("/refresh") | ||||||
| public ApiResponse<AuthResponse.TokenDTO> reissue(@RequestBody AuthRequest.ReissueDTO request) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
| return ApiResponse.onSuccess(authService.reissue(request)); | ||||||
| } | ||||||
|
|
||||||
| @Operation(summary = "로그아웃", description = "로그아웃하여 Refresh Token을 삭제합니다.") | ||||||
| @PostMapping("/logout") | ||||||
| public ApiResponse<Void> logout(@AuthenticationPrincipal UserDetails userDetails) { | ||||||
| authService.logout(userDetails.getUsername()); | ||||||
| return ApiResponse.onSuccess(null); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.example.triptalk.domain.user.converter; | ||
|
|
||
| import com.example.triptalk.domain.user.dto.AuthRequest; | ||
| import com.example.triptalk.domain.user.dto.AuthResponse; | ||
| import com.example.triptalk.domain.user.entity.User; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class AuthConverter { | ||
|
|
||
| public User toUser(AuthRequest.SignUpDTO request, String encodedPassword) { | ||
| return User.builder() | ||
| .email(request.getEmail()) | ||
| .password(encodedPassword) | ||
| .nickName(request.getNickName()) | ||
| .completedTravelCount(0) | ||
| .plannedTravelCount(0) | ||
| .build(); | ||
| } | ||
|
|
||
| public AuthResponse.SignUpDTO toSignUpResponse(User user) { | ||
| return AuthResponse.SignUpDTO.builder() | ||
| .userId(user.getId()) | ||
| .email(user.getEmail()) | ||
| .nickName(user.getNickName()) | ||
| .build(); | ||
| } | ||
|
|
||
| public AuthResponse.TokenDTO toTokenResponse(String accessToken, String refreshToken) { | ||
| return AuthResponse.TokenDTO.builder() | ||
| .accessToken(accessToken) | ||
| .refreshToken(refreshToken) | ||
| .build(); | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.example.triptalk.domain.user.dto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.AllArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.NoArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class AuthRequest { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @AllArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "회원가입 요청") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static class SignUpDTO { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "이메일", example = "[email protected]") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String email; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "비밀번호", example = "password123") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String password; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "닉네임", example = "여행자") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String nickName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+14
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 입력 검증 어노테이션이 누락되었습니다. 회원가입 요청 DTO에 필수 필드에 대한 검증 어노테이션이 없어 빈 값이나 잘못된 형식의 데이터가 전달될 수 있습니다. 이는 보안 및 데이터 무결성에 영향을 줄 수 있습니다. 다음 diff를 적용하여 검증 로직을 추가하세요: +import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
public class AuthRequest {
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "회원가입 요청")
public static class SignUpDTO {
+ @NotBlank(message = "이메일은 필수입니다")
+ @Email(message = "유효한 이메일 형식이어야 합니다")
@Schema(description = "이메일", example = "[email protected]")
private String email;
+ @NotBlank(message = "비밀번호는 필수입니다")
+ @Size(min = 8, message = "비밀번호는 최소 8자 이상이어야 합니다")
@Schema(description = "비밀번호", example = "password123")
private String password;
+ @NotBlank(message = "닉네임은 필수입니다")
@Schema(description = "닉네임", example = "여행자")
private String nickName;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @AllArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "로그인 요청") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static class LoginDTO { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "이메일", example = "[email protected]") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String email; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "비밀번호", example = "password123") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String password; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+29
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로그인 DTO에도 입력 검증이 필요합니다. @Getter
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "로그인 요청")
public static class LoginDTO {
+ @NotBlank(message = "이메일은 필수입니다")
+ @Email(message = "유효한 이메일 형식이어야 합니다")
@Schema(description = "이메일", example = "[email protected]")
private String email;
+ @NotBlank(message = "비밀번호는 필수입니다")
@Schema(description = "비밀번호", example = "password123")
private String password;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @AllArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "토큰 재발급 요청") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static class ReissueDTO { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "리프레시 토큰") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String refreshToken; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토큰 재발급 DTO에도 검증이 필요합니다. @Getter
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "토큰 재발급 요청")
public static class ReissueDTO {
+ @NotBlank(message = "리프레시 토큰은 필수입니다")
@Schema(description = "리프레시 토큰")
private String refreshToken;
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package com.example.triptalk.domain.user.dto; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| public class AuthResponse { | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @AllArgsConstructor | ||
| @Schema(description = "토큰 응답") | ||
| public static class TokenDTO { | ||
| @Schema(description = "액세스 토큰") | ||
| private String accessToken; | ||
|
|
||
| @Schema(description = "리프레시 토큰") | ||
| private String refreshToken; | ||
| } | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @AllArgsConstructor | ||
| @Schema(description = "회원가입 응답") | ||
| public static class SignUpDTO { | ||
| @Schema(description = "사용자 ID", example = "1") | ||
| private Long userId; | ||
|
|
||
| @Schema(description = "이메일", example = "[email protected]") | ||
| private String email; | ||
|
|
||
| @Schema(description = "닉네임", example = "여행자") | ||
| private String nickName; | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package com.example.triptalk.domain.user.entity; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.*; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @Builder | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @AllArgsConstructor | ||
| public class RefreshToken { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @Column(nullable = false, unique = true) | ||
| private String token; | ||
|
|
||
| @Column(nullable = false) | ||
| private Long userId; | ||
|
|
||
| @Column(nullable = false) | ||
| private LocalDateTime expiryDate; | ||
|
|
||
| public boolean isExpired() { | ||
| return LocalDateTime.now().isAfter(expiryDate); | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.example.triptalk.domain.user.repository; | ||
|
|
||
| import com.example.triptalk.domain.user.entity.RefreshToken; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> { | ||
| Optional<RefreshToken> findByToken(String token); | ||
| Optional<RefreshToken> findByUserId(Long userId); | ||
| void deleteByUserId(Long userId); | ||
| } | ||
|
Comment on lines
+8
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 삭제 메서드에 필수 어노테이션이 누락되었습니다.
다음 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 |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.example.triptalk.domain.user.service; | ||
|
|
||
| import com.example.triptalk.domain.user.dto.AuthRequest; | ||
| import com.example.triptalk.domain.user.dto.AuthResponse; | ||
|
|
||
| public interface AuthService { | ||
|
|
||
| AuthResponse.SignUpDTO signUp(AuthRequest.SignUpDTO request); | ||
|
|
||
| AuthResponse.TokenDTO login(AuthRequest.LoginDTO request); | ||
|
|
||
| AuthResponse.TokenDTO reissue(AuthRequest.ReissueDTO request); | ||
|
|
||
| void logout(String email); | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| package com.example.triptalk.domain.user.service; | ||
|
|
||
| import com.example.triptalk.domain.user.converter.AuthConverter; | ||
| import com.example.triptalk.domain.user.dto.AuthRequest; | ||
| import com.example.triptalk.domain.user.dto.AuthResponse; | ||
| import com.example.triptalk.domain.user.entity.RefreshToken; | ||
| import com.example.triptalk.domain.user.entity.User; | ||
| import com.example.triptalk.domain.user.repository.RefreshTokenRepository; | ||
| import com.example.triptalk.domain.user.repository.UserRepository; | ||
| import com.example.triptalk.global.apiPayload.code.status.ErrorStatus; | ||
| import com.example.triptalk.global.apiPayload.exception.handler.ErrorHandler; | ||
| import com.example.triptalk.global.security.jwt.JwtTokenProvider; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Transactional | ||
| public class AuthServiceImpl implements AuthService { | ||
|
|
||
| private final UserRepository userRepository; | ||
| private final RefreshTokenRepository refreshTokenRepository; | ||
| private final JwtTokenProvider jwtTokenProvider; | ||
| private final PasswordEncoder passwordEncoder; | ||
| private final AuthConverter authConverter; | ||
|
|
||
| @Override | ||
| public AuthResponse.SignUpDTO signUp(AuthRequest.SignUpDTO request) { | ||
| // 이메일 중복 체크 | ||
| if (userRepository.existsByEmail(request.getEmail())) { | ||
| throw new ErrorHandler(ErrorStatus.AUTH_DUPLICATE_EMAIL); | ||
| } | ||
|
|
||
| // 비밀번호 인코딩 후 유저 생성 | ||
| String encodedPassword = passwordEncoder.encode(request.getPassword()); | ||
| User user = authConverter.toUser(request, encodedPassword); | ||
| User savedUser = userRepository.save(user); | ||
|
|
||
| return authConverter.toSignUpResponse(savedUser); | ||
| } | ||
|
|
||
| @Override | ||
| public AuthResponse.TokenDTO login(AuthRequest.LoginDTO request) { | ||
| // 유저 조회 | ||
| User user = userRepository.findByEmail(request.getEmail()) | ||
| .orElseThrow(() -> new ErrorHandler(ErrorStatus.USER_NOT_FOUND)); | ||
|
|
||
| // 비밀번호 검증 | ||
| if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { | ||
| throw new ErrorHandler(ErrorStatus.AUTH_INVALID_PASSWORD); | ||
| } | ||
|
|
||
| // 토큰 생성 | ||
| String accessToken = jwtTokenProvider.createAccessToken(user.getEmail()); | ||
| String refreshToken = jwtTokenProvider.createRefreshToken(user.getEmail()); | ||
|
|
||
| // RefreshToken 저장 | ||
| saveRefreshToken(user.getId(), refreshToken); | ||
|
|
||
| return authConverter.toTokenResponse(accessToken, refreshToken); | ||
| } | ||
|
|
||
| @Override | ||
| public AuthResponse.TokenDTO reissue(AuthRequest.ReissueDTO request) { | ||
| // RefreshToken 유효성 검증 | ||
| if (!jwtTokenProvider.validateToken(request.getRefreshToken())) { | ||
| throw new ErrorHandler(ErrorStatus.AUTH_INVALID_TOKEN); | ||
| } | ||
|
|
||
| // DB에서 RefreshToken 조회 | ||
| RefreshToken refreshToken = refreshTokenRepository.findByToken(request.getRefreshToken()) | ||
| .orElseThrow(() -> new ErrorHandler(ErrorStatus.AUTH_TOKEN_NOT_FOUND)); | ||
|
|
||
| // 만료 여부 확인 | ||
| if (refreshToken.isExpired()) { | ||
| refreshTokenRepository.delete(refreshToken); | ||
| throw new ErrorHandler(ErrorStatus.AUTH_EXPIRED_TOKEN); | ||
| } | ||
|
|
||
| // 유저 조회 | ||
| User user = userRepository.findById(refreshToken.getUserId()) | ||
| .orElseThrow(() -> new ErrorHandler(ErrorStatus.USER_NOT_FOUND)); | ||
|
|
||
| // 새로운 토큰 생성 | ||
| String newAccessToken = jwtTokenProvider.createAccessToken(user.getEmail()); | ||
| String newRefreshToken = jwtTokenProvider.createRefreshToken(user.getEmail()); | ||
|
|
||
| // 기존 RefreshToken 삭제 후 새로운 토큰 저장 | ||
| refreshTokenRepository.delete(refreshToken); | ||
| saveRefreshToken(user.getId(), newRefreshToken); | ||
|
Comment on lines
+92
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 중복된 삭제 로직을 제거하세요. Line 93에서 기존 RefreshToken을 삭제한 후, Line 94의 다음 중 하나를 선택하여 수정하세요: 옵션 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 방식으로 변경 현재
🤖 Prompt for AI Agents |
||
|
|
||
| return authConverter.toTokenResponse(newAccessToken, newRefreshToken); | ||
| } | ||
|
|
||
| @Override | ||
| public void logout(String email) { | ||
| User user = userRepository.findByEmail(email) | ||
| .orElseThrow(() -> new ErrorHandler(ErrorStatus.USER_NOT_FOUND)); | ||
| refreshTokenRepository.deleteByUserId(user.getId()); | ||
| } | ||
|
|
||
| private void saveRefreshToken(Long userId, String token) { | ||
| // 기존 토큰 삭제 | ||
| refreshTokenRepository.deleteByUserId(userId); | ||
|
|
||
| // 새 토큰 저장 | ||
| RefreshToken refreshToken = RefreshToken.builder() | ||
| .token(token) | ||
| .userId(userId) | ||
| .expiryDate(LocalDateTime.now().plusSeconds( | ||
| jwtTokenProvider.getRefreshTokenExpiration() / 1000)) | ||
|
Comment on lines
+114
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Search for getRefreshTokenExpiration method definition and refreshTokenExpiration property
rg -n -A5 -B5 "getRefreshTokenExpiration|refreshTokenExpiration" --type javaRepository: 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 확인된 문제: 만료 시간 계산 시 단위 변환이 명확하지 않음
다만 다음과 같이 개선하면 코드 의도가 더욱 명확해집니다:
🤖 Prompt for AI Agents |
||
| .build(); | ||
|
|
||
| refreshTokenRepository.save(refreshToken); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: TripTalk/BE_SpringBoot
Length of output: 74
JJWT 의존성을 최신 버전으로 업데이트하세요.
현재 0.12.3 버전이 사용 중이나, Maven Central의 최신 안정 버전은 0.12.6입니다. 보안 패치 및 버그 수정을 포함한 최신 버전으로 업데이트하는 것을 권장합니다.
🤖 Prompt for AI Agents