diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/dto/UserInfoResponse.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/dto/UserInfoResponse.java index 3462f12d..bf65e937 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/dto/UserInfoResponse.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/dto/UserInfoResponse.java @@ -1,20 +1,20 @@ package com.jootalkpia.auth_server.client.dto; -import com.jootalkpia.auth_server.user.domain.SocialType; +import com.jootalkpia.auth_server.user.domain.Platform; public record UserInfoResponse( Long socialId, - SocialType socialType, + Platform platform, String email, String socialNickname ) { public static UserInfoResponse of( final Long socialId, - final SocialType socialType, + final Platform platform, final String email, final String socialNickname ) { - return new UserInfoResponse(socialId, socialType, email, socialNickname); + return new UserInfoResponse(socialId, platform, email, socialNickname); } } diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/dto/UserLoginRequest.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/dto/UserLoginRequest.java index 82c4e0f0..588904b7 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/dto/UserLoginRequest.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/dto/UserLoginRequest.java @@ -1,6 +1,6 @@ package com.jootalkpia.auth_server.client.dto; -import com.jootalkpia.auth_server.user.domain.SocialType; +import com.jootalkpia.auth_server.user.domain.Platform; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -8,7 +8,7 @@ public record UserLoginRequest( @NotNull(message = "소셜 로그인 종류가 입력되지 않았습니다.") @Schema(description = "소셜로그인 타입", example = "KAKAO") - SocialType socialType, + Platform platform, @NotBlank(message = "redirectUri가 입력되지 않았습니다.") @Schema(description = "리다이텍트 uri 값", example = "http://localhost:5173/kakao/redirection") diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/kakao/KakaoSocialService.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/kakao/KakaoSocialService.java index 64ced85c..6b0aec17 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/kakao/KakaoSocialService.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/client/kakao/KakaoSocialService.java @@ -7,7 +7,7 @@ import com.jootalkpia.auth_server.client.service.SocialService; import com.jootalkpia.auth_server.exception.CustomException; import com.jootalkpia.auth_server.response.ErrorCode; -import com.jootalkpia.auth_server.user.domain.SocialType; +import com.jootalkpia.auth_server.user.domain.Platform; import feign.FeignException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -41,7 +41,7 @@ public UserInfoResponse login( throw new CustomException(ErrorCode.AUTHENTICATION_CODE_EXPIRED); } // Access Token으로 유저 정보 불러오기 - return getLoginDto(loginRequest.socialType(), getUserInfo(accessToken)); + return getLoginDto(loginRequest.platform(), getUserInfo(accessToken)); } private String getOAuth2Authentication( @@ -64,12 +64,12 @@ private KakaoUserResponse getUserInfo( } private UserInfoResponse getLoginDto( - final SocialType socialType, + final Platform platform, final KakaoUserResponse userResponse ) { return UserInfoResponse.of( userResponse.id(), - socialType, + platform, userResponse.kakaoAccount().email(), userResponse.kakaoAccount().profile().nickname() ); diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/jwt/TokenService.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/jwt/TokenService.java index f2f0cbb9..4b4db466 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/jwt/TokenService.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/jwt/TokenService.java @@ -1,6 +1,8 @@ package com.jootalkpia.auth_server.jwt; +import com.jootalkpia.auth_server.exception.CustomException; import com.jootalkpia.auth_server.redis.Token; +import com.jootalkpia.auth_server.response.ErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,4 +19,12 @@ public void saveRefreshToken(final Long userId, final String refreshToken) { Token.of(userId, refreshToken) ); } -} + + public Long findIdByRefreshToken(final String refreshToken) { + Token token = tokenRepository.findByRefreshToken(refreshToken) + .orElseThrow( + () -> new CustomException(ErrorCode.REFRESH_TOKEN_NOT_FOUND) + ); + return token.getId(); + } +} \ No newline at end of file diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/response/ErrorCode.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/response/ErrorCode.java index 947b75f1..1166e4a2 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/response/ErrorCode.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/response/ErrorCode.java @@ -13,7 +13,7 @@ public enum ErrorCode { MISSING_REQUIRED_HEADER("A40002", HttpStatus.BAD_REQUEST, "필수 헤더가 누락되었습니다."), MISSING_REQUIRED_PARAMETER("A40003", HttpStatus.BAD_REQUEST, "필수 파라미터가 누락되었습니다."), AUTHENTICATION_CODE_EXPIRED("A40004", HttpStatus.BAD_REQUEST, "인가 코드가 만료되었습니다."), - SOCIAL_TYPE_BAD_REQUEST("A40005", HttpStatus.BAD_REQUEST, "로그인 요청이 유효하지 않습니다."), + PLATFORM_BAD_REQUEST("A40005", HttpStatus.BAD_REQUEST, "로그인 요청이 유효하지 않습니다."), // 401 Unauthorized ACCESS_TOKEN_EXPIRED("A40100", HttpStatus.UNAUTHORIZED, "액세스 토큰이 만료되었습니다."), diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/controller/UserController.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/controller/UserController.java index ea6d8686..16704fd8 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/controller/UserController.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/controller/UserController.java @@ -1,10 +1,13 @@ package com.jootalkpia.auth_server.user.controller; import com.jootalkpia.auth_server.client.dto.UserLoginRequest; +import com.jootalkpia.auth_server.response.ApiResponseDto; +import com.jootalkpia.auth_server.user.dto.AccessTokenGetSuccess; import com.jootalkpia.auth_server.user.dto.LoginSuccessResponse; import com.jootalkpia.auth_server.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -24,4 +27,12 @@ public ResponseEntity login( ) { return ResponseEntity.ok().body(userService.create(authorizationCode, loginRequest)); } + + @Override + @GetMapping("api/v1/user/token-refresh") + public ResponseEntity refreshToken( + @RequestParam final String token + ) { + return ResponseEntity.ok().body(userService.refreshToken(token)); + } } diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/controller/UserControllerDocs.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/controller/UserControllerDocs.java index be7d9fe7..e4233874 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/controller/UserControllerDocs.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/controller/UserControllerDocs.java @@ -2,6 +2,7 @@ import com.jootalkpia.auth_server.client.dto.UserLoginRequest; +import com.jootalkpia.auth_server.user.dto.AccessTokenGetSuccess; import com.jootalkpia.auth_server.user.dto.LoginSuccessResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -29,4 +30,17 @@ ResponseEntity login( @RequestParam final String authorizationCode, @RequestBody final UserLoginRequest loginRequest ); -} + + @Operation(summary = "액세스 토큰 재발급") + @ApiResponses( + value = { + @ApiResponse(responseCode = "20001", description = "액세스 토큰 재발급 성공"), + @ApiResponse(responseCode = "40102", description = "리프레시 토큰이 유효하지 않습니다."), + @ApiResponse(responseCode = "40104", description = "해당 유저의 리프레시 토큰이 존재하지 않습니다."), + @ApiResponse(responseCode = "50000", description = "서버 내부 오류입니다.") + } + ) + ResponseEntity refreshToken( + @RequestParam final String refreshToken + ); +} \ No newline at end of file diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/SocialType.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/Platform.java similarity index 88% rename from src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/SocialType.java rename to src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/Platform.java index e97d6d19..05ea8744 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/SocialType.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/Platform.java @@ -5,7 +5,7 @@ @Getter @AllArgsConstructor -public enum SocialType { +public enum Platform { KAKAO("KAKAO"), ; private final String type; diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/User.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/User.java index 633a4c31..9ace2314 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/User.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/domain/User.java @@ -24,28 +24,33 @@ public class User extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long Id; + private Long userId; private Long socialId; private String email; @Enumerated(EnumType.STRING) - private SocialType socialType; + private Platform platform; private String nickname; + private String profileImage; + + public static User of( final Long socialId, final String email, - final SocialType socialType, - final String socialNickname + final Platform platform, + final String socialNickname, + final String profileImage ) { return User.builder() .socialId(socialId) .email(email) - .socialType(socialType) + .platform(platform) .nickname(socialNickname) + .profileImage(profileImage) .build(); } } diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/dto/AccessTokenGetSuccess.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/dto/AccessTokenGetSuccess.java new file mode 100644 index 00000000..04bc725d --- /dev/null +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/dto/AccessTokenGetSuccess.java @@ -0,0 +1,12 @@ +package com.jootalkpia.auth_server.user.dto; + +public record AccessTokenGetSuccess( + + String accessToken +) { + public static AccessTokenGetSuccess of( + final String accessToken + ) { + return new AccessTokenGetSuccess(accessToken); + } +} \ No newline at end of file diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/repository/UserRepository.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/repository/UserRepository.java index 87111b6a..c5cf0d3a 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/repository/UserRepository.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/repository/UserRepository.java @@ -2,7 +2,7 @@ import com.jootalkpia.auth_server.exception.CustomException; import com.jootalkpia.auth_server.response.ErrorCode; -import com.jootalkpia.auth_server.user.domain.SocialType; +import com.jootalkpia.auth_server.user.domain.Platform; import com.jootalkpia.auth_server.user.domain.User; import feign.Param; import java.util.Optional; @@ -11,15 +11,14 @@ public interface UserRepository extends JpaRepository, UserRepositoryCustom { - @Query("SELECT u FROM User u WHERE u.socialId = :socialId AND u.socialType = :socialType") - Optional findUserBySocialTypeAndSocialId(@Param("socialId") Long socialId, - @Param("socialType") SocialType socialType); + @Query("SELECT u FROM User u WHERE u.socialId = :socialId AND u.platform = :platform") + Optional findUserByPlatformAndSocialId(@Param("socialId") Long socialId, + @Param("platform") Platform platform); - Optional findUserById(Long id); + Optional findByUserId(Long userId); - default User findUserByIdOrThrow(Long id) { - return findUserById(id) + default User findByUserIdOrThrow(Long id) { + return findByUserId(id) .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); } -} - +} \ No newline at end of file diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/repository/UserRepositoryCustom.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/repository/UserRepositoryCustom.java index 9555079e..009fde22 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/repository/UserRepositoryCustom.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/repository/UserRepositoryCustom.java @@ -1,9 +1,9 @@ package com.jootalkpia.auth_server.user.repository; -import com.jootalkpia.auth_server.user.domain.SocialType; +import com.jootalkpia.auth_server.user.domain.Platform; import com.jootalkpia.auth_server.user.domain.User; import java.util.Optional; public interface UserRepositoryCustom { - Optional findUserBySocialTypeAndSocialId(final Long socialId, final SocialType socialType); + Optional findUserByPlatformAndSocialId(final Long socialId, final Platform platform); } diff --git a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/service/UserService.java b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/service/UserService.java index d56d8638..b52592e5 100644 --- a/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/service/UserService.java +++ b/src/backend/auth_server/src/main/java/com/jootalkpia/auth_server/user/service/UserService.java @@ -1,5 +1,7 @@ package com.jootalkpia.auth_server.user.service; +import static com.jootalkpia.auth_server.jwt.JwtValidationType.EXPIRED_JWT_TOKEN; + import com.jootalkpia.auth_server.client.dto.UserInfoResponse; import com.jootalkpia.auth_server.client.dto.UserLoginRequest; import com.jootalkpia.auth_server.client.kakao.KakaoSocialService; @@ -8,8 +10,9 @@ import com.jootalkpia.auth_server.jwt.TokenService; import com.jootalkpia.auth_server.response.ErrorCode; import com.jootalkpia.auth_server.security.UserAuthentication; -import com.jootalkpia.auth_server.user.domain.SocialType; +import com.jootalkpia.auth_server.user.domain.Platform; import com.jootalkpia.auth_server.user.domain.User; +import com.jootalkpia.auth_server.user.dto.AccessTokenGetSuccess; import com.jootalkpia.auth_server.user.dto.LoginSuccessResponse; import com.jootalkpia.auth_server.user.dto.TokenDto; import com.jootalkpia.auth_server.user.repository.UserRepository; @@ -32,18 +35,18 @@ public LoginSuccessResponse create( TokenDto tokenDto = getTokenDto(user); - return LoginSuccessResponse.of(user.getNickname(), user.getId(), tokenDto); + return LoginSuccessResponse.of(user.getNickname(), user.getUserId(), tokenDto); } public UserInfoResponse getUserInfoResponse( final String authorizationCode, final UserLoginRequest loginRequest ) { - switch (loginRequest.socialType()) { + switch (loginRequest.platform()) { case KAKAO: return kakaoSocialService.login(authorizationCode, loginRequest); default: - throw new CustomException(ErrorCode.SOCIAL_TYPE_BAD_REQUEST); + throw new CustomException(ErrorCode.PLATFORM_BAD_REQUEST); } } @@ -51,17 +54,18 @@ public User createUser(final UserInfoResponse userResponse) { User user = User.of( userResponse.socialId(), userResponse.email(), - userResponse.socialType(), - userResponse.socialNickname()+"#"+userResponse.socialId() + userResponse.platform(), + userResponse.socialNickname()+"#"+userResponse.socialId(), + "https://github.com/user-attachments/assets/7e8fc602-6c3e-47bc-a8f4-6a84784a68da" ); return userRepository.save(user); } public User getBySocialId( final Long socialId, - final SocialType socialType + final Platform platform ) { - User user = userRepository.findUserBySocialTypeAndSocialId(socialId, socialType).orElseThrow( + User user = userRepository.findUserByPlatformAndSocialId(socialId, platform).orElseThrow( () -> new CustomException(ErrorCode.USER_NOT_FOUND) ); return user; @@ -71,8 +75,7 @@ public TokenDto getTokenByUserId( final Long id ) { // 사용자 정보 가져오기 - User user = userRepository.findById(id) - .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + userRepository.findByUserIdOrThrow(id); UserAuthentication userAuthentication = new UserAuthentication(id.toString(), null); @@ -84,15 +87,38 @@ public TokenDto getTokenByUserId( ); } + public AccessTokenGetSuccess refreshToken( + final String refreshToken + ) { + if (jwtTokenProvider.validateToken(refreshToken) == EXPIRED_JWT_TOKEN) { + // 리프레시 토큰이 만료된 경우 + throw new CustomException(ErrorCode.REFRESH_TOKEN_EXPIRED); + } + + Long userId = jwtTokenProvider.getUserFromJwt(refreshToken); + if (!userId.equals(tokenService.findIdByRefreshToken(refreshToken))) { + throw new CustomException(ErrorCode.TOKEN_INCORRECT_ERROR); + } + + // 사용자 정보 가져오기 + userRepository.findByUserIdOrThrow(userId); + + UserAuthentication userAuthentication = new UserAuthentication(userId.toString(), null); + + return AccessTokenGetSuccess.of( + jwtTokenProvider.issueAccessToken(userAuthentication) + ); + } + private TokenDto getTokenDto( final User user ) { - return getTokenByUserId(user.getId()); + return getTokenByUserId(user.getUserId()); } private User getUser(final UserInfoResponse userResponse) { - if (isExistingUser(userResponse.socialId(), userResponse.socialType())) { - return getBySocialId(userResponse.socialId(), userResponse.socialType()); + if (isExistingUser(userResponse.socialId(), userResponse.platform())) { + return getBySocialId(userResponse.socialId(), userResponse.platform()); } else { return createUser(userResponse); } @@ -100,8 +126,9 @@ private User getUser(final UserInfoResponse userResponse) { private boolean isExistingUser( final Long socialId, - final SocialType socialType + final Platform platform ) { - return userRepository.findUserBySocialTypeAndSocialId(socialId, socialType).isPresent(); + return userRepository.findUserByPlatformAndSocialId(socialId, platform).isPresent(); } } +