From ac2b414385d9ade96dcba88e222fd9991a6efdef Mon Sep 17 00:00:00 2001 From: Seonghun Jeong Date: Wed, 23 Oct 2024 23:00:59 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20TokenService=EC=9D=98=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=BD=94=EB=93=9C=20=EB=8B=A4=EC=96=91=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sinitto/auth/service/TokenService.java | 31 +++++++++++++------ .../common/exception/AccessTokenExpired.java | 8 +++++ .../exception/ForceLogoutException.java | 8 +++++ .../exception/GlobalExceptionHandler.java | 27 +++++++++++++--- .../common/exception/RefreshTokenStolen.java | 8 +++++ 5 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/example/sinitto/common/exception/AccessTokenExpired.java create mode 100644 src/main/java/com/example/sinitto/common/exception/ForceLogoutException.java create mode 100644 src/main/java/com/example/sinitto/common/exception/RefreshTokenStolen.java diff --git a/src/main/java/com/example/sinitto/auth/service/TokenService.java b/src/main/java/com/example/sinitto/auth/service/TokenService.java index b8075cc2..5f7342e6 100644 --- a/src/main/java/com/example/sinitto/auth/service/TokenService.java +++ b/src/main/java/com/example/sinitto/auth/service/TokenService.java @@ -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; @@ -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()); + } if (claims.getExpiration().before(new Date())) { - throw new UnauthorizedException("토큰이 만료되었습니다. 재로그인이 필요합니다."); + throw new AccessTokenExpired("액세스 토큰이 만료되었습니다. 리프레시 토큰으로 다시 액세스 토큰을 발급받으세요."); } return claims.getSubject(); @@ -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("토큰이 만료되었습니다. 재로그인이 필요합니다."); + } + + if (!storedRefreshToken.equals(refreshToken)) { + throw new RefreshTokenStolen("이미 한번 사용된 리프레시 토큰입니다. 리프레시 토큰이 탈취되었을 가능성이 있습니다."); } redisTemplate.delete(email); diff --git a/src/main/java/com/example/sinitto/common/exception/AccessTokenExpired.java b/src/main/java/com/example/sinitto/common/exception/AccessTokenExpired.java new file mode 100644 index 00000000..1999fe5a --- /dev/null +++ b/src/main/java/com/example/sinitto/common/exception/AccessTokenExpired.java @@ -0,0 +1,8 @@ +package com.example.sinitto.common.exception; + +public class AccessTokenExpired extends RuntimeException { + + public AccessTokenExpired(String message) { + super(message); + } +} diff --git a/src/main/java/com/example/sinitto/common/exception/ForceLogoutException.java b/src/main/java/com/example/sinitto/common/exception/ForceLogoutException.java new file mode 100644 index 00000000..063cb2d5 --- /dev/null +++ b/src/main/java/com/example/sinitto/common/exception/ForceLogoutException.java @@ -0,0 +1,8 @@ +package com.example.sinitto.common.exception; + +public class ForceLogoutException extends RuntimeException { + + public ForceLogoutException(String message) { + super(message); + } +} diff --git a/src/main/java/com/example/sinitto/common/exception/GlobalExceptionHandler.java b/src/main/java/com/example/sinitto/common/exception/GlobalExceptionHandler.java index 223311e6..bbf55785 100644 --- a/src/main/java/com/example/sinitto/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/example/sinitto/common/exception/GlobalExceptionHandler.java @@ -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; @@ -49,12 +50,28 @@ public ResponseEntity handleBadRequestException(BadRequestExcepti return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(problemDetail); } - @ExceptionHandler(MultiStatusException.class) - public ResponseEntity handleMultiStatusException(MultiStatusException e) { + @ExceptionHandler(ForceLogoutException.class) + public ResponseEntity 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 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 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); } } diff --git a/src/main/java/com/example/sinitto/common/exception/RefreshTokenStolen.java b/src/main/java/com/example/sinitto/common/exception/RefreshTokenStolen.java new file mode 100644 index 00000000..2b894712 --- /dev/null +++ b/src/main/java/com/example/sinitto/common/exception/RefreshTokenStolen.java @@ -0,0 +1,8 @@ +package com.example.sinitto.common.exception; + +public class RefreshTokenStolen extends RuntimeException { + + public RefreshTokenStolen(String message) { + super(message); + } +}