Skip to content
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

Refactor: 예외 처리 관련 리팩토링 #92

Merged
merged 19 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b1b53c9
refactor: 콜백 도메인 예외 처리 리팩토링
zzoe2346 Oct 18, 2024
0cc344e
refactor: Auth 도메인 예외 처리 리팩토링
zzoe2346 Oct 20, 2024
ae3eb3f
refactor: Guard 도메인 예외 처리 리팩토링
zzoe2346 Oct 20, 2024
0ba9287
refactor: GuardGuideline 도메인 예외 처리 리팩토링
zzoe2346 Oct 20, 2024
3c5d0fb
refactor: HelloCall 도메인 예외 처리 리팩토링
zzoe2346 Oct 20, 2024
9485078
fix: 리팩토링후 콜백 Test 코드 수정안하여 생긴 테스트 실패 수정
zzoe2346 Oct 20, 2024
1b47824
refactor: Member 도메인 예외 처리 리팩토링
zzoe2346 Oct 22, 2024
3680639
refactor: Point 도메인 예외 처리 리팩토링
zzoe2346 Oct 22, 2024
a52eb35
refactor: Review 도메인 예외 처리 리팩토링
zzoe2346 Oct 22, 2024
1f606ab
refactor: Sinitto 도메인 예외 처리 리팩토링
zzoe2346 Oct 22, 2024
82deeaf
Merge branch 'Weekly' of https://github.com/kakao-tech-campus-2nd-ste…
zzoe2346 Oct 22, 2024
e73fa7b
refactor: 사용안되는 exception class, advice class 제거,
zzoe2346 Oct 22, 2024
079a7d3
refactor: HelloCallPriceService 에서 IllegalArgumentException -> BadReq…
zzoe2346 Oct 22, 2024
b7fb8ae
refactor: 포인트로그 Content 멘트 수정
zzoe2346 Oct 22, 2024
34781b6
refactor: 포인트로그 Content 멘트 최종 수정
zzoe2346 Oct 22, 2024
7700318
refactor: MultiStatusException->ConflictException
zzoe2346 Oct 23, 2024
ac2b414
refactor: TokenService의 응답코드 다양화
zzoe2346 Oct 23, 2024
d4bd2c3
merge: 보호자용 가이드라인 Weekly 머지로 인한 머지 수행
zzoe2346 Oct 23, 2024
a377d8f
refactor: 예외 클래스 이름 더 명확하게 수정
zzoe2346 Oct 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions src/main/java/com/example/sinitto/auth/service/TokenService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.example.sinitto.auth.service;

import com.example.sinitto.auth.dto.TokenResponse;
import com.example.sinitto.common.exception.UnauthorizedException;
import com.example.sinitto.common.exception.AccessTokenExpired;
import com.example.sinitto.common.exception.ForceLogoutException;
import com.example.sinitto.common.exception.RefreshTokenStolen;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -52,14 +55,19 @@ public String generateRefreshToken(String email) {


public String extractEmail(String token) {
var claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
Claims claims;
try {
claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
throw new ForceLogoutException(e.getMessage());
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ForceLogoutException(460)

원인: '알수없는 이유로 토큰이 잘못 넣어져 전달된 상황' 에러
클라이언트 유도: 로그아웃 이후 재로그인창까지 페이지 이동시키는 로직

Copy link
Contributor Author

@zzoe2346 zzoe2346 Oct 23, 2024

Choose a reason for hiding this comment

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

500번대 응답코드가 안나오게 수정해보았습니다. 혹시 try-catch 하는 위치가 별로라서 resolver 등 다른곳으로 옮기고 싶으시면 말씀주세요!

Copy link
Member

@GitJIHO GitJIHO Oct 23, 2024

Choose a reason for hiding this comment

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

제 생각에도 아마 여기가 베스트가 아닌가 싶네요...! 좋은 것 같습니다 ㅎㅎ


if (claims.getExpiration().before(new Date())) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

요부분이 쪼금 걸리네요... 프론트엔드 기준으로 생각했을 때
1. accessToken 만료 2. UnAuthorizedException 발생 3. UnAuthorizedException에 해당하는 httpStatus 전송 4. 프론트엔드에서 해당 httpStatus 받을 경우 refreshToken 전송하여 accessToken 재발급 요청
이런 과정이 될 것 같은데, 지금은 accessToken 만료될 때와 refreshToken 만료될 때 둘 다 UnAuthorizedException이 발생되어서, refreshToken이 만료되었을 때에도 계속 만료된 refreshToken을 요청하여 무한루프가 될 것 같습니다.
refreshToken이 만료될 경우에는 다른 Exception으로 처리하면 좋을 것 같습니다!

Copy link
Contributor Author

@zzoe2346 zzoe2346 Oct 23, 2024

Choose a reason for hiding this comment

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

그러네요! 말씀대로 무한루프가 발생이 되겠네요. 중요한 이슈;;; 이런건 어떻게 보셨는지 대단하시네요 👍 Forbidden 이나 BadReqeust 둘 중 하나가 로 정하면 적절할까 싶네요? (Forbidden이 좀더 이 상황에서 맞는거 같기도합니다)

아직 이 이슈 못보신 분들 보시고 이견없으시면 수정 바로 진행하겠습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

네네 forbidden이나 bad request로 해도 될 것 같긴 한데, refreshToken에 오류가 있는 경우 로그아웃과 똑같은 로직이 될 것 같아서(프론트엔드에서는 세션 저장소 등에 저장되어 있는 jwt 삭제, 백엔드는 redis에서 제거) 아에 custom http status로 해도 괜찮을 것 같습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ac2b414

수정사항 위 커밋에 있습니다. 간단한 코멘트도 달아놓았는데 천천히 확인해주시면 감사하겠습니다! 지호님이 디테일하게 수정사항 제안주신걸 토대로 수정하였습니다.

throw new UnauthorizedException("토큰이 만료되었습니다. 재로그인이 필요합니다.");
throw new AccessTokenExpired("액세스 토큰이 만료되었습니다. 리프레시 토큰으로 다시 액세스 토큰을 발급받으세요.");
}

return claims.getSubject();
Expand All @@ -69,8 +77,13 @@ public TokenResponse refreshAccessToken(String refreshToken) {
String email = extractEmail(refreshToken);

String storedRefreshToken = redisTemplate.opsForValue().get(email);
if (storedRefreshToken == null || !storedRefreshToken.equals(refreshToken)) {
throw new UnauthorizedException("만료되거나 이미 한번 사용된 리프레쉬 토큰입니다. 재로그인이 필요합니다.");

if (storedRefreshToken == null) {
throw new ForceLogoutException("토큰이 만료되었습니다. 재로그인이 필요합니다.");
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ForceLogoutException(460)

원인: '리프레쉬토큰이 만료된 경우' 에러
클라이언트 유도: 로그아웃 이후 재로그인창까지 페이지 이동시키는 로직


if (!storedRefreshToken.equals(refreshToken)) {
throw new RefreshTokenStolen("이미 한번 사용된 리프레시 토큰입니다. 리프레시 토큰이 탈취되었을 가능성이 있습니다.");
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

RefreshTokenStolen(462)

원인: '리프레쉬토큰이 이미 사용된 경우' 에러
클라이언트 유도: 리프레쉬 토큰이 탈취당했다는 경고창과 함께 관리자에게 문의하라는 메시지를 띄우는 페이지를 유도


redisTemplate.delete(email);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.sinitto.common.exception;

public class AccessTokenExpired extends RuntimeException {

public AccessTokenExpired(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.sinitto.common.exception;

public class ForceLogoutException extends RuntimeException {

public ForceLogoutException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.sinitto.common.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
Expand Down Expand Up @@ -49,12 +50,28 @@ public ResponseEntity<ProblemDetail> handleBadRequestException(BadRequestExcepti
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(problemDetail);
}

@ExceptionHandler(MultiStatusException.class)
public ResponseEntity<ProblemDetail> handleMultiStatusException(MultiStatusException e) {
@ExceptionHandler(ForceLogoutException.class)
public ResponseEntity<ProblemDetail> handleForceLogoutException(ForceLogoutException e) {

ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.MULTI_STATUS, e.getMessage());
problemDetail.setTitle("Multi Status");
return ResponseEntity.status(HttpStatus.MULTI_STATUS).body(problemDetail);
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(460), e.getMessage());
problemDetail.setTitle("Force Logout");
return ResponseEntity.status(HttpStatusCode.valueOf(460)).body(problemDetail);
}

@ExceptionHandler(AccessTokenExpired.class)
public ResponseEntity<ProblemDetail> handleAccessTokenExpired(AccessTokenExpired e) {

ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(461), e.getMessage());
problemDetail.setTitle("Access Token Expired");
return ResponseEntity.status(HttpStatusCode.valueOf(461)).body(problemDetail);
}

@ExceptionHandler(RefreshTokenStolen.class)
public ResponseEntity<ProblemDetail> handleRefreshTokenStolen(RefreshTokenStolen e) {

ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(462), e.getMessage());
problemDetail.setTitle("Refresh Token Stolen");
return ResponseEntity.status(HttpStatusCode.valueOf(462)).body(problemDetail);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.sinitto.common.exception;

public class RefreshTokenStolen extends RuntimeException {

public RefreshTokenStolen(String message) {
super(message);
}
}