Skip to content

Commit

Permalink
feat: exception handling (#34)
Browse files Browse the repository at this point in the history
* feat: 전역 예외 핸들러 추가

* feat: ErrorResponse 추가

* feat: ErrorCode 추가

* feat: CustomException 추가

* feat: JwtFilter의 예외 처리 필터 추가

* refactor: 토큰 파싱 과정의 예외를 CustomException으로 변경

* remove: CustomException 던지므로 throws 제거

* refactor: parseAccessToken 내부에서 CustomException 던지므로 try-catch 제거

* typo: 오타 수정

* feat: Jwt 관련 ErrorCode 추가

* feat: JwtFilter의 예외 처리 필터 추가

* fix: 재발급 메서드에서 잡을 수 있도록 수정

* fix: try-catch로 만료 토큰의 경우 null을 반환

* fix: 토큰 조회 메서드 수정

기존 메서드는 데이터의 유무와 무관하게 항상 비어있는 Optional을 반환함.

사용하지 않는 Repository의 메서드는 제거함.

* fix: refreshToken을 accessToken으로 수정

accessToken을 받아야 하는 메서드인데 refreshToken을 받고 있었음.
  • Loading branch information
Sangwook02 authored Jan 31, 2024
1 parent 827bd86 commit 8dcf2e1
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand All @@ -71,7 +71,7 @@ public RefreshTokenDto retrieveRefreshToken(String refreshTokenValue) {

private Optional<RefreshToken> getRefreshTokenFromRedis(Long memberId) {
// TODO: CustomException으로 바꾸기
return refreshTokenRepository.findByMemberId(memberId);
return refreshTokenRepository.findById(memberId);
}

private RefreshTokenDto parseRefreshToken(String refreshTokenValue) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<RefreshToken, Long> {
Optional<RefreshToken> findByMemberId(Long aLong);
}
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, Long> {}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -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();
}
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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<ErrorResponse> 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<ErrorResponse> 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));
}
}
Original file line number Diff line number Diff line change
@@ -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())));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
}

Optional<AccessTokenDto> reissueAccessToken =
Optional.ofNullable(jwtService.reissueAccessTokenIfExpired(refreshTokenValue));
Optional.ofNullable(jwtService.reissueAccessTokenIfExpired(accessTokenValue));
RefreshTokenDto refreshTokenDto = jwtService.retrieveRefreshToken(refreshTokenValue);

// AT가 만료되었고, RT가 유효하면 AT, RT 재발급
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/gdschongik/gdsc/global/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down

0 comments on commit 8dcf2e1

Please sign in to comment.