diff --git a/src/main/java/com/gdschongik/gdsc/domain/auth/application/JwtService.java b/src/main/java/com/gdschongik/gdsc/domain/auth/application/JwtService.java index c5877110a..73811cbe9 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/auth/application/JwtService.java +++ b/src/main/java/com/gdschongik/gdsc/domain/auth/application/JwtService.java @@ -44,7 +44,7 @@ private void saveRefreshTokenToRedis(RefreshTokenDto refreshTokenDto) { public AccessTokenDto retrieveAccessToken(String accessTokenValue) { try { return jwtUtil.parseAccessToken(accessTokenValue); - } catch (Exception e) { + } catch (ExpiredJwtException e) { return null; } } @@ -71,7 +71,7 @@ public RefreshTokenDto retrieveRefreshToken(String refreshTokenValue) { private Optional getRefreshTokenFromRedis(Long memberId) { // TODO: CustomException으로 바꾸기 - return refreshTokenRepository.findByMemberId(memberId); + return refreshTokenRepository.findById(memberId); } private RefreshTokenDto parseRefreshToken(String refreshTokenValue) { diff --git a/src/main/java/com/gdschongik/gdsc/domain/auth/dao/RefreshTokenRepository.java b/src/main/java/com/gdschongik/gdsc/domain/auth/dao/RefreshTokenRepository.java index 0d81979e7..53d4bae5f 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/auth/dao/RefreshTokenRepository.java +++ b/src/main/java/com/gdschongik/gdsc/domain/auth/dao/RefreshTokenRepository.java @@ -1,9 +1,6 @@ package com.gdschongik.gdsc.domain.auth.dao; import com.gdschongik.gdsc.domain.auth.domain.RefreshToken; -import java.util.Optional; import org.springframework.data.repository.CrudRepository; -public interface RefreshTokenRepository extends CrudRepository { - Optional findByMemberId(Long aLong); -} +public interface RefreshTokenRepository extends CrudRepository {} diff --git a/src/main/java/com/gdschongik/gdsc/global/config/WebSecurityConfig.java b/src/main/java/com/gdschongik/gdsc/global/config/WebSecurityConfig.java index d6dea291b..a1e47e596 100644 --- a/src/main/java/com/gdschongik/gdsc/global/config/WebSecurityConfig.java +++ b/src/main/java/com/gdschongik/gdsc/global/config/WebSecurityConfig.java @@ -2,10 +2,12 @@ import static org.springframework.security.config.Customizer.*; +import com.fasterxml.jackson.databind.ObjectMapper; import com.gdschongik.gdsc.domain.auth.application.JwtService; import com.gdschongik.gdsc.domain.member.dao.MemberRepository; import com.gdschongik.gdsc.global.security.CustomSuccessHandler; import com.gdschongik.gdsc.global.security.CustomUserService; +import com.gdschongik.gdsc.global.security.JwtExceptionFilter; import com.gdschongik.gdsc.global.security.JwtFilter; import com.gdschongik.gdsc.global.util.CookieUtil; import lombok.RequiredArgsConstructor; @@ -26,6 +28,7 @@ public class WebSecurityConfig { private final MemberRepository memberRepository; private final JwtService jwtService; private final CookieUtil cookieUtil; + private final ObjectMapper objectMapper; @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { @@ -41,6 +44,7 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti .successHandler(customSuccessHandler(jwtService, cookieUtil))); httpSecurity.addFilterBefore(jwtFilter(jwtService, cookieUtil), UsernamePasswordAuthenticationFilter.class); + httpSecurity.addFilterBefore(jwtExceptionFilter(objectMapper), JwtFilter.class); return httpSecurity.build(); } @@ -59,4 +63,9 @@ public CustomSuccessHandler customSuccessHandler(JwtService jwtService, CookieUt public JwtFilter jwtFilter(JwtService jwtService, CookieUtil cookieUtil) { return new JwtFilter(jwtService, cookieUtil); } + + @Bean + public JwtExceptionFilter jwtExceptionFilter(ObjectMapper objectMapper) { + return new JwtExceptionFilter(objectMapper); + } } diff --git a/src/main/java/com/gdschongik/gdsc/global/exception/CustomException.java b/src/main/java/com/gdschongik/gdsc/global/exception/CustomException.java new file mode 100644 index 000000000..fc1b0f5a1 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/global/exception/CustomException.java @@ -0,0 +1,14 @@ +package com.gdschongik.gdsc.global.exception; + +import lombok.Getter; + +@Getter +public class CustomException extends RuntimeException { + + private final ErrorCode errorCode; + + public CustomException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java new file mode 100644 index 000000000..a9ff9e2ab --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java @@ -0,0 +1,18 @@ +package com.gdschongik.gdsc.global.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ErrorCode { + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러입니다."), + + // Jwt + INVALID_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 JWT 토큰입니다."), + EXPIRED_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 JWT 토큰입니다."); + + private final HttpStatus status; + private final String message; +} diff --git a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorResponse.java b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorResponse.java new file mode 100644 index 000000000..e818b1075 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorResponse.java @@ -0,0 +1,7 @@ +package com.gdschongik.gdsc.global.exception; + +public record ErrorResponse(String errorCodeName, String errorMessage) { + public static ErrorResponse of(ErrorCode errorCode) { + return new ErrorResponse(errorCode.name(), errorCode.getMessage()); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/global/exception/GlobalExceptionHandler.java b/src/main/java/com/gdschongik/gdsc/global/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..42cd4a1ed --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,25 @@ +package com.gdschongik.gdsc.global.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomException(CustomException e) { + log.error("CustomException : {}", e.getMessage(), e); + return ResponseEntity.status(e.getErrorCode().getStatus()).body(ErrorResponse.of(e.getErrorCode())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + log.error("INTERNAL_SERVER_ERROR : {}", e.getMessage(), e); + return ResponseEntity.status(ErrorCode.INTERNAL_SERVER_ERROR.getStatus()) + .body(ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR)); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/global/security/JwtExceptionFilter.java b/src/main/java/com/gdschongik/gdsc/global/security/JwtExceptionFilter.java new file mode 100644 index 000000000..c5d9028c0 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/global/security/JwtExceptionFilter.java @@ -0,0 +1,34 @@ +package com.gdschongik.gdsc.global.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gdschongik.gdsc.global.exception.CustomException; +import com.gdschongik.gdsc.global.exception.ErrorResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.filter.OncePerRequestFilter; + +@Slf4j +@RequiredArgsConstructor +public class JwtExceptionFilter extends OncePerRequestFilter { + + private final ObjectMapper objectMapper; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + try { + filterChain.doFilter(request, response); + } catch (CustomException e) { + log.error("JWTException : {}", e.getMessage(), e); + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json"); + response.setStatus(e.getErrorCode().getStatus().value()); + response.getWriter().write(objectMapper.writeValueAsString(ErrorResponse.of(e.getErrorCode()))); + } + } +} diff --git a/src/main/java/com/gdschongik/gdsc/global/security/JwtFilter.java b/src/main/java/com/gdschongik/gdsc/global/security/JwtFilter.java index 011dc9ee6..355ab70e9 100644 --- a/src/main/java/com/gdschongik/gdsc/global/security/JwtFilter.java +++ b/src/main/java/com/gdschongik/gdsc/global/security/JwtFilter.java @@ -50,7 +50,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } Optional reissueAccessToken = - Optional.ofNullable(jwtService.reissueAccessTokenIfExpired(refreshTokenValue)); + Optional.ofNullable(jwtService.reissueAccessTokenIfExpired(accessTokenValue)); RefreshTokenDto refreshTokenDto = jwtService.retrieveRefreshToken(refreshTokenValue); // AT가 만료되었고, RT가 유효하면 AT, RT 재발급 diff --git a/src/main/java/com/gdschongik/gdsc/global/util/JwtUtil.java b/src/main/java/com/gdschongik/gdsc/global/util/JwtUtil.java index bc8dacdc6..de557dfdd 100644 --- a/src/main/java/com/gdschongik/gdsc/global/util/JwtUtil.java +++ b/src/main/java/com/gdschongik/gdsc/global/util/JwtUtil.java @@ -6,6 +6,8 @@ import com.gdschongik.gdsc.domain.auth.dto.RefreshTokenDto; import com.gdschongik.gdsc.domain.member.domain.MemberRole; import com.gdschongik.gdsc.global.common.constant.JwtConstant; +import com.gdschongik.gdsc.global.exception.CustomException; +import com.gdschongik.gdsc.global.exception.ErrorCode; import com.gdschongik.gdsc.global.property.JwtProperty; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; @@ -83,7 +85,7 @@ public AccessTokenDto parseAccessToken(String accessTokenValue) throws ExpiredJw } catch (ExpiredJwtException e) { throw e; } catch (Exception e) { - return null; + throw new CustomException(ErrorCode.INVALID_JWT_TOKEN); } }