diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/controller/AuthController.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/controller/AuthController.java
index 85d8adfe..0d052ab1 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/controller/AuthController.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/controller/AuthController.java
@@ -16,7 +16,9 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@@ -28,29 +30,29 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/auth")
+@Slf4j
public class AuthController {
- //사용자 로그인
- //사용자 로그아웃
- //액세스 토큰 및 리프레시 토큰 발급
- //토큰 갱신
+ // 사용자 로그인
+ // 사용자 로그아웃
+ // 액세스 토큰 및 리프레시 토큰 발급
+ // 토큰 갱신
private final AuthService authService;
-
/**
* 구글 로그인 API
*
- * @param authorizationHeader Authorization 헤더에는 구글 ID 토큰이 포함됩니다.
+ * @param googleIdTokenHeader Google-ID-Token 헤더에는 구글 ID 토큰이 포함됩니다.
* @return 성공 시 JwtDTO를 포함하는 SuccessResponseDTO
* @throws CustomException 구글 ID 토큰 검증에 실패한 경우 예외를 던집니다.
*/
@Operation(
summary = "로그인",
description = "구글 소셜 로그인을 통해 로그인을 수행합니다.
"
- + "Authorization 헤더에는 구글 ID 토큰을 입력해야 합니다.
"
+ + "Google-ID-Token 헤더에는 구글 ID 토큰을 입력해야 합니다. 예시: Bearer 차돌이짱귀여워
"
+ "로그인 성공 시에는 JwtDTO를 포함하는 SuccessResponseDTO를 반환합니다.",
parameters = @Parameter(
- name = "Authorization",
+ name = "Google-ID-Token",
description = "Google ID Token",
in = ParameterIn.HEADER,
required = true
@@ -60,22 +62,23 @@ public class AuthController {
@ApiResponse(responseCode = "200", description = "로그인 성공"),
@ApiResponse(responseCode = "401", description = "구글 ID 토큰이 유효하지 않음",
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class))),
- @ApiResponse(responseCode = "400", description = "Authorization 헤더의 토큰이 유효하지 않음",
+ @ApiResponse(responseCode = "400", description = "Google-ID-Token 헤더의 토큰이 유효하지 않음",
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class))),
- @ApiResponse(responseCode = "404", description = "등록되지 않은 사용자 (code: -104)",
+ @ApiResponse(responseCode = "404", description = "등록되지 않은 사용자 (code: U-104)",
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class)))
})
@PostMapping("/google/login")
- public ResponseEntity> googleLogin(@RequestHeader("Authorization") String authorizationHeader) {
+ public ResponseEntity> googleLogin(@RequestHeader("Google-ID-Token") String googleIdTokenHeader) {
try {
- JwtDTO jwtResponse = authService.googleLogin(authorizationHeader);
+ JwtDTO jwtResponse = authService.googleLogin(googleIdTokenHeader);
SuccessResponseDTO response = SuccessResponseDTO.builder()
.status(CustomSuccessCode.GOOGLE_LOGIN_SUCCESS.getHttpStatus().value())
.message(CustomSuccessCode.GOOGLE_LOGIN_SUCCESS.getMessage())
.data(jwtResponse)
.build();
return ResponseEntity.ok(response);
- } catch (GeneralSecurityException | IOException e) {
+ } catch (GeneralSecurityException | IOException | IllegalArgumentException e) {
+ log.error("Google ID Token verification failed: {}", e.getMessage(), e);
throw new CustomException(CustomErrorCode.GOOGLE_TOKEN_VERIFICATION_FAILED);
}
}
@@ -83,7 +86,7 @@ public ResponseEntity> googleLogin(@RequestHeader("Au
/**
* 구글 회원가입 API
*
- * @param authorizationHeader Authorization 헤더에는 구글 ID 토큰이 포함됩니다.
+ * @param googleIdTokenHeader Google-ID-Token 헤더에는 구글 ID 토큰이 포함됩니다.
* @param requestUserInfo 회원가입 요청 정보가 포함된 DTO
* @return 성공 시 JwtDTO를 포함하는 SuccessResponseDTO
* @throws CustomException 구글 ID 토큰 검증에 실패한 경우 예외를 던집니다.
@@ -91,10 +94,10 @@ public ResponseEntity> googleLogin(@RequestHeader("Au
@Operation(
summary = "회원 가입",
description = "구글 소셜 로그인을 통해 회원가입을 수행합니다.
"
- + "Authorization 헤더에는 구글 ID 토큰을 입력하고, 요청 본문에는 닉네임 등의 추가 정보를 포함해야 합니다.
"
+ + "Google-ID-Token 헤더에는 구글 ID 토큰을 입력하고, 요청 본문에는 닉네임 등의 추가 정보를 포함해야 합니다.
"
+ "회원가입 성공 시에는 JwtDTO를 포함하는 SuccessResponseDTO를 반환합니다.",
parameters = @Parameter(
- name = "Authorization",
+ name = "Google-ID-Token",
description = "Google ID Token",
in = ParameterIn.HEADER,
required = true
@@ -104,24 +107,25 @@ public ResponseEntity> googleLogin(@RequestHeader("Au
@ApiResponse(responseCode = "201", description = "회원가입 성공"),
@ApiResponse(responseCode = "401", description = "구글 ID 토큰이 유효하지 않음",
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class))),
- @ApiResponse(responseCode = "400", description = "Authorization 헤더의 토큰이 유효하지 않음 또는 요청 본문이 잘못됨",
+ @ApiResponse(responseCode = "400", description = "Google-ID-Token 헤더의 토큰이 유효하지 않음 또는 요청 본문이 잘못됨",
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class))),
- @ApiResponse(responseCode = "409", description = "이미 등록된 사용자 (code: -105)",
+ @ApiResponse(responseCode = "409", description = "이미 등록된 사용자 (code: U-105)",
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class)))
})
@PostMapping("/google/join")
- public ResponseEntity> googleJoin(@RequestHeader("Authorization") String authorizationHeader,
- @RequestBody GoogleJoinDTO requestUserInfo) {
+ public ResponseEntity> googleJoin(@RequestHeader("Google-ID-Token") String googleIdTokenHeader,
+ @Valid @RequestBody GoogleJoinDTO requestUserInfo) {
try {
- JwtDTO jwtResponse = authService.googleJoin(authorizationHeader, requestUserInfo);
+ JwtDTO jwtResponse = authService.googleJoin(googleIdTokenHeader, requestUserInfo);
SuccessResponseDTO response = SuccessResponseDTO.builder()
.status(CustomSuccessCode.GOOGLE_SIGNUP_SUCCESS.getHttpStatus().value())
.message(CustomSuccessCode.GOOGLE_SIGNUP_SUCCESS.getMessage())
.data(jwtResponse)
.build();
return ResponseEntity.status(HttpStatus.CREATED).body(response);
- } catch (GeneralSecurityException | IOException e) {
+ } catch (GeneralSecurityException | IOException | IllegalArgumentException e) {
+ log.error("Google ID Token verification failed: {}", e.getMessage(), e);
throw new CustomException(CustomErrorCode.GOOGLE_TOKEN_VERIFICATION_FAILED);
}
}
-}
+}
\ No newline at end of file
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/dto/request/GoogleJoinDTO.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/dto/request/GoogleJoinDTO.java
index a70d873a..250dad40 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/dto/request/GoogleJoinDTO.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/dto/request/GoogleJoinDTO.java
@@ -2,13 +2,10 @@
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotEmpty;
-import jakarta.validation.constraints.NotNull;
import lombok.Getter;
-import org.springframework.validation.annotation.Validated;
+
@Getter
-@Validated // 유효성 검사를 위해 추가
@Schema(description = "Google Join Info")
public class GoogleJoinDTO {
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AuthService.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AuthService.java
index deea2c19..e2718d99 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AuthService.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AuthService.java
@@ -5,6 +5,7 @@
import com.tico.pomoro_do.domain.user.dto.request.GoogleJoinDTO;
import com.tico.pomoro_do.domain.user.dto.response.JwtDTO;
import com.tico.pomoro_do.domain.user.entity.User;
+import com.tico.pomoro_do.global.common.enums.TokenType;
import com.tico.pomoro_do.global.common.enums.UserRole;
import java.io.IOException;
@@ -13,13 +14,16 @@
public interface AuthService {
// 구글 ID 토큰 무결성 검사
- GoogleUserInfoDTO verifyGoogleIdToken(String idToken) throws GeneralSecurityException, IOException;
+ GoogleUserInfoDTO verifyGoogleIdToken(String idToken) throws GeneralSecurityException, IOException, IllegalArgumentException;
+
+ // 토큰 형태 검사
+ String extractToken(String header, TokenType tokenType);
// 구글 로그인
- JwtDTO googleLogin(String authorizationHeader) throws GeneralSecurityException, IOException;
+ JwtDTO googleLogin(String idTokenHeader) throws GeneralSecurityException, IOException;
// 구글 회원가입
- JwtDTO googleJoin(String authorizationHeader, GoogleJoinDTO request) throws GeneralSecurityException, IOException;
+ JwtDTO googleJoin(String idTokenHeader, GoogleJoinDTO request) throws GeneralSecurityException, IOException;
// User 생성
User createUser(String username, String nickname, String profileImageUrl, UserRole role);
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AuthServiceImpl.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AuthServiceImpl.java
index 2da5c1f3..9543c4ce 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AuthServiceImpl.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AuthServiceImpl.java
@@ -12,8 +12,8 @@
import com.tico.pomoro_do.domain.user.repository.UserRepository;
import com.tico.pomoro_do.global.auth.jwt.JWTUtil;
import com.tico.pomoro_do.global.common.enums.SocialProvider;
+import com.tico.pomoro_do.global.common.enums.TokenType;
import com.tico.pomoro_do.global.common.enums.UserRole;
-import com.tico.pomoro_do.global.common.enums.UserStatus;
import com.tico.pomoro_do.global.exception.CustomErrorCode;
import com.tico.pomoro_do.global.exception.CustomException;
import lombok.RequiredArgsConstructor;
@@ -57,14 +57,14 @@ public class AuthServiceImpl implements AuthService {
* @throws CustomException 구글 ID 토큰이 유효하지 않은 경우 예외
*/
@Override
- public GoogleUserInfoDTO verifyGoogleIdToken(String idToken) throws GeneralSecurityException, IOException {
+ public GoogleUserInfoDTO verifyGoogleIdToken(String idToken) throws GeneralSecurityException, IOException, IllegalArgumentException {
NetHttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new GsonFactory();
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Collections.singletonList(clientId))
.build();
- GoogleIdToken googleIdToken = verifier.verify(idToken);
+ GoogleIdToken googleIdToken = verifier.verify(idToken); // 검증 실패시 IllegalArgumentException를 던짐
if (googleIdToken != null) {
GoogleIdToken.Payload payload = googleIdToken.getPayload();
return GoogleUserInfoDTO.builder()
@@ -74,7 +74,7 @@ public GoogleUserInfoDTO verifyGoogleIdToken(String idToken) throws GeneralSecur
.pictureUrl((String) payload.get("picture"))
.build();
} else {
- throw new CustomException(CustomErrorCode.GOOGLE_TOKEN_INVALID);
+ throw new CustomException(CustomErrorCode.GOOGLE_TOKEN_VERIFICATION_FAILED);
}
}
@@ -94,23 +94,33 @@ public JwtDTO createJwtTokens(String email, String role) {
}
/**
- * Authorization 헤더에서 토큰 추출
+ * 토큰 관련 헤더에서 토큰 값을 추출합니다.
*
- * @param authorizationHeader Authorization 헤더
- * @return 추출된 토큰
- * @throws CustomException Authorization 헤더가 유효하지 않은 경우 예외
+ * @param header 토큰 헤더 (예: "Bearer ")
+ * @param tokenType 토큰의 타입 (Google ID 토큰 또는 JWT)
+ * @return 추출된 토큰 값
+ * @throws CustomException 토큰 헤더가 유효하지 않은 경우 예외 발생
+ * - 헤더가 null이거나 비어있는 경우
+ * - 헤더 형식이 "Bearer " 형식이 아닌 경우
+ * - 토큰 타입이 Google ID 토큰인데 헤더 형식이 맞지 않는 경우
*/
- private String extractToken(String authorizationHeader) {
- if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
- return authorizationHeader.substring(7);
+ @Override
+ public String extractToken(String header, TokenType tokenType) {
+
+ if (header == null || header.isEmpty() || !header.startsWith("Bearer ")) {
+ CustomErrorCode errorCode = tokenType.equals(TokenType.GOOGLE)
+ ? CustomErrorCode.INVALID_GOOGLE_TOKEN_HEADER
+ : CustomErrorCode.INVALID_AUTHORIZATION_HEADER;
+ throw new CustomException(errorCode);
}
- throw new CustomException(CustomErrorCode.INVALID_AUTHORIZATION_HEADER);
+
+ return header.substring(7);
}
/**
* 구글 ID 토큰으로 로그인 처리
*
- * @param authorizationHeader Authorization 헤더에 포함된 구글 ID 토큰
+ * @param idTokenHeader Google-ID-Token 헤더에 포함된 구글 ID 토큰
* @return JwtDTO를 포함하는 ResponseEntity
* @throws GeneralSecurityException 구글 ID 토큰 검증 중 발생하는 보안 예외
* @throws IOException IO 예외
@@ -118,8 +128,9 @@ private String extractToken(String authorizationHeader) {
*/
@Override
@Transactional
- public JwtDTO googleLogin(String authorizationHeader) throws GeneralSecurityException, IOException {
- String idToken = extractToken(authorizationHeader);
+ public JwtDTO googleLogin(String idTokenHeader) throws GeneralSecurityException, IOException {
+
+ String idToken = extractToken(idTokenHeader, TokenType.GOOGLE);
GoogleUserInfoDTO userInfo = verifyGoogleIdToken(idToken);
if (!userRepository.existsByUsername(userInfo.getEmail())) {
@@ -132,7 +143,7 @@ public JwtDTO googleLogin(String authorizationHeader) throws GeneralSecurityExce
/**
* 구글 ID 토큰으로 회원가입 처리
*
- * @param authorizationHeader Authorization 헤더에 포함된 구글 ID 토큰
+ * @param idTokenHeader Google-ID-Token 헤더에 포함된 구글 ID 토큰
* @param requestUserInfo GoogleJoinDTO 객체
* @return JwtDTO를 포함하는 ResponseEntity
* @throws GeneralSecurityException 구글 ID 토큰 검증 중 발생하는 보안 예외
@@ -141,8 +152,8 @@ public JwtDTO googleLogin(String authorizationHeader) throws GeneralSecurityExce
*/
@Override
@Transactional
- public JwtDTO googleJoin(String authorizationHeader, GoogleJoinDTO requestUserInfo) throws GeneralSecurityException, IOException {
- String idToken = extractToken(authorizationHeader);
+ public JwtDTO googleJoin(String idTokenHeader, GoogleJoinDTO requestUserInfo) throws GeneralSecurityException, IOException {
+ String idToken = extractToken(idTokenHeader, TokenType.GOOGLE);
GoogleUserInfoDTO userInfo = verifyGoogleIdToken(idToken);
if (userRepository.existsByUsername(userInfo.getEmail())) {
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/base/ErrorResponseDTO.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/base/ErrorResponseDTO.java
index 13919327..8f4ce27d 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/base/ErrorResponseDTO.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/base/ErrorResponseDTO.java
@@ -1,5 +1,6 @@
package com.tico.pomoro_do.global.base;
+import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;
@@ -7,11 +8,12 @@
public class ErrorResponseDTO {
private int status;
private String name;
- private int code;
+ private String code;
private String message;
+// private final String data = ""; // 응답 데이터
@Builder
- public ErrorResponseDTO(int status, String name, int code, String message) {
+ public ErrorResponseDTO(int status, String name, String code, String message) {
this.status = status;
this.name = name;
this.code = code;
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/base/SuccessResponseDTO.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/base/SuccessResponseDTO.java
index ff78e92c..6befbe3d 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/base/SuccessResponseDTO.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/base/SuccessResponseDTO.java
@@ -10,6 +10,8 @@ public class SuccessResponseDTO {
@Schema(description = "상태 코드", nullable = false, example = "200")
private final int status; // 응답 코드 200번대
+ @Schema(description = "성공 코드", nullable = false, example = "Success")
+ private final String code = "SUCCESS";
@Schema(description = "상태 메세지", nullable = false, example = "성공하였습니다.")
private final String message; // 메시지
@Schema(description = "해당 API의 응답 데이터", nullable = false)
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/common/enums/TokenType.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/common/enums/TokenType.java
new file mode 100644
index 00000000..a1e51e90
--- /dev/null
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/common/enums/TokenType.java
@@ -0,0 +1,8 @@
+package com.tico.pomoro_do.global.common.enums;
+
+public enum TokenType {
+
+ GOOGLE,
+
+ JWT
+}
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/config/SwaggerConfig.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/config/SwaggerConfig.java
index 826ca8e7..e4a55ed0 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/config/SwaggerConfig.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/config/SwaggerConfig.java
@@ -53,7 +53,7 @@ public OpenAPI openAPI() {
// 인증 요청 방식에 HEADER 추가
SecurityScheme securityScheme = new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
- .scheme("bearer")
+ .scheme(BEARER_TOKEN_PREFIX)
.bearerFormat("JWT")
.in(SecurityScheme.In.HEADER)
.name("Authorization");
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/CustomErrorCode.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/CustomErrorCode.java
index 62d0f93e..cf2638db 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/CustomErrorCode.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/CustomErrorCode.java
@@ -16,36 +16,48 @@ public enum CustomErrorCode {
/* 409 CONFLICT : Resource의 현재 상태와 충돌. 보통 중복된 데이터 존재 */
//유저 관련 에러: -100번대
- EMAIL_EXIST(HttpStatus.BAD_REQUEST, -100, "이미 사용 중인 이메일입니다."),
- NICKNAME_NULL(HttpStatus.BAD_REQUEST, -101, "닉네임을 입력해주세요."),
- USER_NOT_FOUND(HttpStatus.NOT_FOUND, -102, "가입된 사용자가 아닙니다."),
- PROFILE_UPLOAD_FAILED(HttpStatus.FORBIDDEN, -103, "프로필 이미지 변경에 실패했습니다."),
- USER_ALREADY_REGISTERED(HttpStatus.CONFLICT, -104, "이미 등록된 사용자입니다."),
+ EMAIL_EXIST(HttpStatus.BAD_REQUEST, "U-100", "이미 사용 중인 이메일입니다."),
+ NICKNAME_NULL(HttpStatus.BAD_REQUEST, "U-101", "닉네임을 입력해주세요."),
+ USER_NOT_FOUND(HttpStatus.NOT_FOUND, "U-102", "가입된 사용자가 아닙니다."),
+ PROFILE_UPLOAD_FAILED(HttpStatus.FORBIDDEN, "U-103", "프로필 이미지 변경에 실패했습니다."),
+ USER_ALREADY_REGISTERED(HttpStatus.CONFLICT, "U-104", "이미 등록된 사용자입니다."),
//Token 관련 에러 -200번대
- ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, -201, "액세스 토큰이 만료되었습니다."),
- REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, -202, "리프레시 토큰이 만료되었습니다."),
- INVALID_ACCESS_TOKEN(HttpStatus.FORBIDDEN, -203, "액세스 토큰이 유효하지 않습니다."),
- INVALID_REFRESH_TOKEN(HttpStatus.FORBIDDEN, -204, "리프레시 토큰이 유효하지 않습니다."),
- MISSING_ACCESS_TOKEN(HttpStatus.BAD_REQUEST, -205, "액세스 토큰이 없습니다."),
- MISSING_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, -206, "리프레시 토큰이 없습니다."),
- MISSING_REFRESH_TOKEN_IN_DB(HttpStatus.BAD_REQUEST, -207, "DB에 리프레시 토큰이 없습니다."),
- REFRESH_TOKEN_MISMATCH(HttpStatus.BAD_REQUEST, -208, "데이터베이스의 리프레시 토큰과 일치하지 않습니다."),
- INVALID_AUTHORIZATION_HEADER(HttpStatus.BAD_REQUEST, -209, "AUTHORIZATION 헤더의 토큰이 유효하지 않습니다."),
- UNAUTHORIZED_ACCESS(HttpStatus.UNAUTHORIZED, -210, "인증되지 않은 접근입니다."),
+ ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "T-201", "액세스 토큰이 만료되었습니다."),
+ REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "T-202", "리프레시 토큰이 만료되었습니다."),
+ INVALID_ACCESS_TOKEN(HttpStatus.FORBIDDEN, "T-203", "액세스 토큰이 유효하지 않습니다."),
+ INVALID_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "T-204", "리프레시 토큰이 유효하지 않습니다."),
+ MISSING_ACCESS_TOKEN(HttpStatus.BAD_REQUEST, "T-205", "액세스 토큰이 없습니다."),
+ MISSING_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "T-206", "리프레시 토큰이 없습니다."),
+ MISSING_REFRESH_TOKEN_IN_DB(HttpStatus.BAD_REQUEST, "T-207", "DB에 리프레시 토큰이 없습니다."),
+ REFRESH_TOKEN_MISMATCH(HttpStatus.BAD_REQUEST, "T-208", "데이터베이스의 리프레시 토큰과 일치하지 않습니다."),
+ INVALID_AUTHORIZATION_HEADER(HttpStatus.BAD_REQUEST, "T-209", "AUTHORIZATION 헤더의 토큰이 유효하지 않습니다."),
+ UNAUTHORIZED_ACCESS(HttpStatus.UNAUTHORIZED, "T-210", "인증되지 않은 접근입니다."),
//구글 토큰 관련 에러: -300번대
- GOOGLE_TOKEN_VERIFICATION_FAILED(HttpStatus.UNAUTHORIZED, -300, "구글 ID 토큰 검증에 실패했습니다."),
- GOOGLE_TOKEN_INVALID(HttpStatus.UNAUTHORIZED, -301, "구글 ID 토큰이 유효하지 않습니다."),
+ GOOGLE_TOKEN_VERIFICATION_FAILED(HttpStatus.UNAUTHORIZED, "G-300", "구글 ID 토큰 검증에 실패했습니다."),
+ INVALID_GOOGLE_TOKEN_HEADER(HttpStatus.BAD_REQUEST, "G-301", "GOOGLE_ID_TOKEN 헤더의 토큰이 유효하지 않습니다."),
//관리자 관련 에러: -400번대
- NOT_AN_ADMIN(HttpStatus.FORBIDDEN, -400, "관리자 권한이 없습니다."),
- INVALID_ADMIN_EMAIL(HttpStatus.BAD_REQUEST, -401, "허용되지 않은 관리자 이메일입니다."),
- ADMIN_EMAIL_ONLY(HttpStatus.FORBIDDEN, -402, "관리자 이메일만 접근할 수 있습니다."),
- ADMIN_LOGIN_FAILED(HttpStatus.BAD_REQUEST, -403, "관리자 정보가 일치하지 않습니다. 관리자 로그인에 실패했습니다.");
+ NOT_AN_ADMIN(HttpStatus.FORBIDDEN, "A-400", "관리자 권한이 없습니다."),
+ INVALID_ADMIN_EMAIL(HttpStatus.BAD_REQUEST, "A-401", "허용되지 않은 관리자 이메일입니다."),
+ ADMIN_EMAIL_ONLY(HttpStatus.FORBIDDEN, "A-402", "관리자 이메일만 접근할 수 있습니다."),
+ ADMIN_LOGIN_FAILED(HttpStatus.BAD_REQUEST, "A-403", "관리자 정보가 일치하지 않습니다. 관리자 로그인에 실패했습니다."),
+
+ //Validation 관련 에러: -500번대
+ VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "V-500", "유효성 검증에 실패했습니다."),
+ MISSING_REQUEST_BODY(HttpStatus.BAD_REQUEST, "V-501", "요청 본문이 누락되었습니다."),
+ MISSING_REQUEST_HEADER(HttpStatus.BAD_REQUEST, "V-502", "요청 헤더가 누락되었습니다."),
+
+ //서버 내부 관련 에러: -900번대
+ INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S-900", "서버 내부 오류가 발생했습니다."),
+ RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "S-901", "요청한 자원을 찾을 수 없습니다."),
+ INVALID_ARGUMENT(HttpStatus.BAD_REQUEST, "S-902", "유효하지 않은 인수입니다."),
+ ACCESS_DENIED(HttpStatus.FORBIDDEN, "S-903", "접근이 거부되었습니다.");
+
private final HttpStatus httpStatus; // HttpStatus
- private final int code; // -100
+ private final String code; // U-100
private final String message; // 설명
}
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/ErrorResponseEntity.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/ErrorResponseEntity.java
index 632f3d61..647a2ce0 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/ErrorResponseEntity.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/ErrorResponseEntity.java
@@ -10,14 +10,17 @@
@Schema(description = "Error Response")
public class ErrorResponseEntity {
//Custom Error 내용을 담을 Response Entity를 생성한다.
+
@Schema(description = "HTTP 상태 코드")
private int status;
@Schema(description = "에러 이름")
private String name;
@Schema(description = "커스텀 에러 코드")
- private int code;
+ private String code;
@Schema(description = "에러 메시지")
private String message;
+// @Schema(description = "응답 데이터: 빈값")
+// private final String data = ""; // 응답 데이터
public static ResponseEntity toResponseEntity(CustomErrorCode e){
return ResponseEntity
@@ -35,7 +38,7 @@ public static ResponseEntity toResponseEntity(CustomErrorCo
//{
// "status": 404,
// "name": "USER_NOT_FOUND",
- // "code": -1000,
+ // "code": "USER-100“,
// "message": "사용자를 찾을 수 없습니다."
//}
diff --git a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/GlobalExceptionHandler.java b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/GlobalExceptionHandler.java
index 0a7c5920..4b87413f 100644
--- a/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/GlobalExceptionHandler.java
+++ b/backend/pomoro-do/src/main/java/com/tico/pomoro_do/global/exception/GlobalExceptionHandler.java
@@ -1,17 +1,134 @@
package com.tico.pomoro_do.global.exception;
+import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
@ControllerAdvice // 모든 @Controller 즉, 전역에서 발생할 수 있는 예외를 잡아 처리한다.
public class GlobalExceptionHandler {
//Controller 전역에서 발생하는 Custom Error를 잡아줄 Handler를 생성한다.
+
+ // CustomException 처리
@ExceptionHandler(CustomException.class) //발생한 CustomException 예외를 잡아서 하나의 메소드에서 공통 처리한다.
protected ResponseEntity handleCustomException(CustomException e) {
return ErrorResponseEntity.toResponseEntity(e.getErrorCode());
}
+ // Validation 오류 처리
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) {
+ Map errors = new HashMap<>();
+ for (FieldError error : ex.getBindingResult().getFieldErrors()) {
+ errors.put(error.getField(), error.getDefaultMessage());
+ }
+
+ ErrorResponseEntity errorResponse = ErrorResponseEntity.builder()
+ .status(HttpStatus.BAD_REQUEST.value())
+ .name("Validation Error")
+ .code(CustomErrorCode.VALIDATION_FAILED.getCode())
+ .message(errors.toString())
+ .build();
+
+ return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
+ }
+
+ // 요청 본문이 잘못된 경우 처리
+ @ExceptionHandler(HttpMessageNotReadableException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
+ ErrorResponseEntity errorResponse = ErrorResponseEntity.builder()
+ .status(HttpStatus.BAD_REQUEST.value())
+ .name("Bad Request")
+ .code(CustomErrorCode.MISSING_REQUEST_BODY.getCode())
+ .message("Required request body is missing.")
+ .build();
+
+ return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
+ }
+
+
+ // 요청 헤더가 누락된 경우 처리
+ @ExceptionHandler(MissingRequestHeaderException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ResponseEntity handleMissingRequestHeaderException(MissingRequestHeaderException ex) {
+ ErrorResponseEntity errorResponse = ErrorResponseEntity.builder()
+ .status(HttpStatus.BAD_REQUEST.value())
+ .name("Bad Request")
+ .code(CustomErrorCode.MISSING_REQUEST_HEADER.getCode())
+ .message("Required request header is missing: " + ex.getHeaderName())
+ .build();
+
+ return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
+ }
+
+ // ElementNotFound 예외 처리
+ @ExceptionHandler(NoSuchElementException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public ResponseEntity handleNoSuchElementException(NoSuchElementException ex) {
+ ErrorResponseEntity errorResponse = ErrorResponseEntity.builder()
+ .status(HttpStatus.NOT_FOUND.value())
+ .name("Not Found")
+ .code(CustomErrorCode.RESOURCE_NOT_FOUND.getCode())
+ .message("The requested resource was not found.")
+ .build();
+
+ return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
+ }
+
+ // IllegalArgumentException 처리
+ @ExceptionHandler(IllegalArgumentException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex) {
+ ErrorResponseEntity errorResponse = ErrorResponseEntity.builder()
+ .status(HttpStatus.BAD_REQUEST.value())
+ .name("Bad Request")
+ .code(CustomErrorCode.INVALID_ARGUMENT.getCode())
+ .message(ex.getMessage())
+ .build();
+
+ return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
+ }
+
+ // 권한이 없는 접근 시 처리
+ @ExceptionHandler(AccessDeniedException.class)
+ @ResponseStatus(HttpStatus.FORBIDDEN)
+ public ResponseEntity handleAccessDeniedException(AccessDeniedException ex) {
+ ErrorResponseEntity errorResponse = ErrorResponseEntity.builder()
+ .status(HttpStatus.FORBIDDEN.value())
+ .name("Forbidden")
+ .code(CustomErrorCode.ACCESS_DENIED.getCode())
+ .message("Access to the resource is denied.")
+ .build();
+
+ return new ResponseEntity<>(errorResponse, HttpStatus.FORBIDDEN);
+ }
+
+ // 기타 모든 예외 처리
+ @ExceptionHandler(Exception.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public ResponseEntity handleException(Exception ex) {
+ ErrorResponseEntity errorResponse = ErrorResponseEntity.builder()
+ .status(HttpStatus.INTERNAL_SERVER_ERROR.value())
+ .name("Internal Server Error")
+ .code(CustomErrorCode.INTERNAL_SERVER_ERROR.getCode())
+ .message("An unexpected error occurred: " + ex.getMessage())
+ .build();
+
+ return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
//@ControllerAdvice + @ExceptionHandler
//모든 컨트롤러에서 발생하는 CustomException을 catch한다.
}
\ No newline at end of file