Skip to content

Commit 1f65bf5

Browse files
committed
refactor: 전역 예외처리 리팩토링 및 ErrorCode enum 통합 적용
- ErrorCode enum 전면 도입: 상태 코드(HttpStatus) 및 메시지를 일원화하여 관리 - MEMBER_NOT_FOUND 오류에 입력 이메일 정보 포함 (detail 메시지) - 모든 에러 응답에 status 코드 포함되도록 구조 통일
1 parent ef45579 commit 1f65bf5

File tree

10 files changed

+123
-33
lines changed

10 files changed

+123
-33
lines changed

src/main/java/com/yourssu/roomescape/auth/AuthService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import com.yourssu.roomescape.member.Member;
44
import com.yourssu.roomescape.exception.MemberNotFoundException;
5+
import com.yourssu.roomescape.exception.ErrorCode;
56
import com.yourssu.roomescape.member.MemberRepository;
67
import com.yourssu.roomescape.util.JwtTokenProvider;
78
import org.springframework.stereotype.Service;
89

10+
911
@Service
1012
public class AuthService {
1113

@@ -19,15 +21,15 @@ public AuthService(MemberRepository memberRepository, JwtTokenProvider jwtTokenP
1921

2022
public AuthResponse login(AuthRequest request) {
2123
Member member = memberRepository.findByEmailAndPassword(request.email(), request.password())
22-
.orElseThrow(() -> new MemberNotFoundException("이메일 또는 비밀번호가 틀렸습니다."));
24+
.orElseThrow(() -> new MemberNotFoundException(ErrorCode.INVALID_LOGIN, "이메일: " + request.email()));
2325
String token = jwtTokenProvider.createToken(member);
2426
return new AuthResponse(token);
2527
}
2628

2729
public String getNameFromToken(String token) {
2830
String email = jwtTokenProvider.getEmail(token);
2931
Member member = memberRepository.findByEmail(email)
30-
.orElseThrow(() -> new MemberNotFoundException("토큰에 해당하는 사용자를 찾을 수 없습니다."));
32+
.orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND, "이메일: " + email));
3133
return member.getName();
3234
}
3335
}

src/main/java/com/yourssu/roomescape/auth/LoginMemberArgumentResolver.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.yourssu.roomescape.auth;
22

3+
import com.yourssu.roomescape.exception.ErrorCode;
34
import com.yourssu.roomescape.exception.MemberNotFoundException;
4-
import com.yourssu.roomescape.exception.UnauthenticatedException;
55
import com.yourssu.roomescape.member.Member;
66
import com.yourssu.roomescape.member.MemberRepository;
77
import com.yourssu.roomescape.util.CookieUtil;
@@ -40,7 +40,7 @@ public Object resolveArgument(@NotNull MethodParameter parameter, ModelAndViewCo
4040
String token = CookieUtil.extractTokenFromCookies(request.getCookies());
4141
String email = jwtTokenProvider.getEmail(token);
4242
Member member = memberRepository.findByEmail(email)
43-
.orElseThrow(() -> new MemberNotFoundException("이메일로 회원을 찾을 수 없습니다: " + email));
43+
.orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND, "이메일: " + email));
4444

4545
return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole());
4646
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.yourssu.roomescape.exception;
2+
3+
import lombok.Getter;
4+
import org.springframework.http.HttpStatus;
5+
6+
@Getter
7+
public enum ErrorCode {
8+
9+
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버에 문제가 생겼습니다."),
10+
11+
// Auth
12+
COOKIE_NOT_FOUND(HttpStatus.UNAUTHORIZED, "쿠키가 존재하지 않습니다."),
13+
TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "쿠키에 토큰이 존재하지 않습니다."),
14+
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "토큰이 만료되었습니다."),
15+
UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "지원하지 않는 토큰 형식입니다."),
16+
MALFORMED_TOKEN(HttpStatus.UNAUTHORIZED, "손상된 JWT 토큰입니다."),
17+
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 JWT 토큰입니다."),
18+
19+
// Member
20+
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자의 정보를 찾을 수 없습니다."),
21+
INVALID_LOGIN(HttpStatus.UNAUTHORIZED, "이메일 또는 비밀번호가 틀렸습니다."),
22+
NOT_ADMIN(HttpStatus.FORBIDDEN, "관리자 권한이 필요합니다."),
23+
24+
// Reservation
25+
RESERVATION_NOT_FOUND(HttpStatus.NOT_FOUND, "예약 정보를 찾을 수 없습니다."),
26+
NO_PERMISSION_FOR_RESERVATION(HttpStatus.FORBIDDEN, "해당 예약에 대한 권한이 없습니다."),
27+
RESERVATION_ALREADY_EXISTS(HttpStatus.CONFLICT, "예약 정보가 이미 존재합니다."),
28+
29+
// Time
30+
TIME_NOT_FOUND(HttpStatus.NOT_FOUND, "시간 정보를 찾을 수 없습니다."),
31+
32+
// Theme
33+
THEME_NOT_FOUND(HttpStatus.NOT_FOUND, "테마 정보를 찾을 수 없습니다.");
34+
35+
private final HttpStatus status;
36+
private final String message;
37+
38+
ErrorCode(HttpStatus status, String message) {
39+
this.status = status;
40+
this.message = message;
41+
}
42+
43+
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.yourssu.roomescape.exception;
22

3-
public record ErrorResponse(String message) {
4-
3+
public record ErrorResponse(int status, String message) {
4+
public ErrorResponse(ErrorCode errorCode) {
5+
this(errorCode.getStatus().value(), errorCode.getMessage());
6+
}
57
}
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
package com.yourssu.roomescape.exception;
22

3-
import org.springframework.http.HttpStatus;
43
import org.springframework.http.ResponseEntity;
54
import org.springframework.web.bind.annotation.ControllerAdvice;
65
import org.springframework.web.bind.annotation.ExceptionHandler;
76

8-
import java.util.Map;
9-
107
@ControllerAdvice
118
public class ExceptionController {
129

1310
@ExceptionHandler(MemberNotFoundException.class)
14-
public ResponseEntity<ErrorResponse> handleMemberNotFound(MemberNotFoundException ex) {
15-
return ResponseEntity.status(HttpStatus.NOT_FOUND)
16-
.body(new ErrorResponse(ex.getMessage()));
11+
public ResponseEntity<ErrorResponse> handleMemberNotFound(MemberNotFoundException e) {
12+
return ResponseEntity
13+
.status(e.getErrorCode().getStatus())
14+
.body(new ErrorResponse(e.getErrorCode()));
15+
}
16+
17+
@ExceptionHandler(UnauthenticatedException.class)
18+
public ResponseEntity<ErrorResponse> handleUnauthenticated(UnauthenticatedException e) {
19+
return ResponseEntity
20+
.status(e.getErrorCode().getStatus())
21+
.body(new ErrorResponse(e.getErrorCode()));
1722
}
1823

1924
@ExceptionHandler(Exception.class)
2025
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
2126
ex.printStackTrace();
22-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
23-
.body(new ErrorResponse("서버 오류가 발생했습니다."));
27+
return ResponseEntity
28+
.status(ErrorCode.INTERNAL_SERVER_ERROR.getStatus())
29+
.body(new ErrorResponse(ErrorCode.INTERNAL_SERVER_ERROR));
2430
}
25-
26-
@ExceptionHandler(UnauthenticatedException.class)
27-
public ResponseEntity<?> handleUnauthenticated(UnauthenticatedException e) {
28-
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("message", e.getMessage()));
29-
}
30-
3131
}
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package com.yourssu.roomescape.exception;
22

3+
import lombok.Getter;
4+
5+
@Getter
36
public class MemberNotFoundException extends RuntimeException {
4-
public MemberNotFoundException(String message) {
5-
super(message);
6-
}
77

8+
private final ErrorCode errorCode;
9+
10+
public MemberNotFoundException(ErrorCode errorCode, String detail) {
11+
super(errorCode.getMessage() + " " + detail);
12+
this.errorCode = errorCode;
13+
}
814
}
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package com.yourssu.roomescape.exception;
22

3+
import lombok.Getter;
4+
5+
@Getter
36
public class UnauthenticatedException extends RuntimeException {
4-
public UnauthenticatedException(String message) {
5-
super(message);
7+
private final ErrorCode errorCode;
8+
9+
public UnauthenticatedException(ErrorCode errorCode) {
10+
super(errorCode.getMessage());
11+
this.errorCode = errorCode;
612
}
13+
714
}

src/main/java/com/yourssu/roomescape/reservation/ReservationService.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.yourssu.roomescape.reservation;
22

33
import com.yourssu.roomescape.auth.LoginMember;
4+
import com.yourssu.roomescape.exception.ErrorCode;
45
import com.yourssu.roomescape.exception.MemberNotFoundException;
56
import com.yourssu.roomescape.member.Member;
67
import com.yourssu.roomescape.member.MemberRepository;
@@ -46,9 +47,9 @@ public ReservationService(
4647
public ReservationResponse save(ReservationRequest request, LoginMember loginMember) {
4748
Member member = (request.name() != null && !request.name().isBlank())
4849
? memberRepository.findByName(request.name())
49-
.orElseThrow(() -> new MemberNotFoundException("이름이 일치하는 회원이 없습니다."))
50+
.orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND, "이름: "+ request.name()))
5051
: memberRepository.findByEmail(loginMember.email())
51-
.orElseThrow(() -> new MemberNotFoundException("이메일이 일치하는 회원이 없습니다."));
52+
.orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND, "이메일: " + loginMember.email()));
5253

5354
Time time = timeRepository.findById(request.time()).orElseThrow();
5455
Theme theme = themeRepository.findById(request.theme()).orElseThrow();
@@ -95,7 +96,7 @@ public List<MyReservationResponse> findMyReservations(LoginMember loginMember) {
9596
@Transactional
9697
public WaitingResponse createWaiting(WaitingRequest request, LoginMember loginMember) {
9798
Member member = memberRepository.findByEmail(loginMember.email())
98-
.orElseThrow(() -> new MemberNotFoundException("이메일이 일치하는 회원이 없습니다."));
99+
.orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND, "이메일"+loginMember.email()));
99100

100101
Time time = timeRepository.findById(request.time()).orElseThrow();
101102
Theme theme = themeRepository.findById(request.theme()).orElseThrow();

src/main/java/com/yourssu/roomescape/util/CookieUtil.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.yourssu.roomescape.util;
22

3+
import com.yourssu.roomescape.exception.ErrorCode;
34
import com.yourssu.roomescape.exception.UnauthenticatedException;
45
import jakarta.servlet.http.Cookie;
56

@@ -12,7 +13,7 @@ private CookieUtil() {
1213

1314
public static String extractTokenFromCookies(Cookie[] cookies) {
1415
if (cookies == null) {
15-
throw new UnauthenticatedException("쿠키가 없습니다.");
16+
throw new UnauthenticatedException(ErrorCode.COOKIE_NOT_FOUND);
1617
}
1718

1819
for (Cookie cookie : cookies) {
@@ -21,6 +22,6 @@ public static String extractTokenFromCookies(Cookie[] cookies) {
2122
}
2223
}
2324

24-
throw new UnauthenticatedException("'token' 이름의 쿠키가 없습니다.");
25+
throw new UnauthenticatedException(ErrorCode.TOKEN_NOT_FOUND);
2526
}
2627
}

src/main/java/com/yourssu/roomescape/util/JwtTokenProvider.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
package com.yourssu.roomescape.util;
22

33
import com.yourssu.roomescape.config.JwtProperties;
4+
import com.yourssu.roomescape.exception.ErrorCode;
5+
import com.yourssu.roomescape.exception.UnauthenticatedException;
46
import com.yourssu.roomescape.member.Member;
5-
import io.jsonwebtoken.*;
7+
import io.jsonwebtoken.Claims;
8+
import io.jsonwebtoken.ExpiredJwtException;
9+
import io.jsonwebtoken.Jwts;
10+
import io.jsonwebtoken.MalformedJwtException;
11+
import io.jsonwebtoken.UnsupportedJwtException;
12+
import io.jsonwebtoken.security.SignatureException;
613
import io.jsonwebtoken.security.Keys;
714
import org.springframework.stereotype.Component;
815

16+
import javax.crypto.SecretKey;
917
import java.security.Key;
1018
import java.util.Date;
1119

@@ -35,11 +43,11 @@ public String createToken(Member member) {
3543
}
3644

3745
public String getEmail(String token) {
38-
return getClaims(token).getSubject();
46+
return parseClaims(token).getSubject();
3947
}
4048

4149
public String getRole(String token) {
42-
return getClaims(token).get("role", String.class);
50+
return parseClaims(token).get("role", String.class);
4351
}
4452

4553
private Claims getClaims(String token) {
@@ -48,5 +56,25 @@ private Claims getClaims(String token) {
4856
.build()
4957
.parseSignedClaims(token)
5058
.getPayload();
59+
private Claims parseClaims(String token) {
60+
if (token == null || token.isBlank()) {
61+
throw new UnauthenticatedException(ErrorCode.TOKEN_NOT_FOUND);
62+
}
63+
64+
try {
65+
return Jwts.parser()
66+
.verifyWith((SecretKey) key)
67+
.build()
68+
.parseSignedClaims(token)
69+
.getPayload();
70+
} catch (ExpiredJwtException e) {
71+
throw new UnauthenticatedException(ErrorCode.EXPIRED_TOKEN);
72+
} catch (UnsupportedJwtException e) {
73+
throw new UnauthenticatedException(ErrorCode.UNSUPPORTED_TOKEN);
74+
} catch (MalformedJwtException e) {
75+
throw new UnauthenticatedException(ErrorCode.MALFORMED_TOKEN);
76+
} catch (SignatureException | IllegalArgumentException e) {
77+
throw new UnauthenticatedException(ErrorCode.INVALID_TOKEN);
78+
}
5179
}
5280
}

0 commit comments

Comments
 (0)