/**
* JWT 관련 작업 중 발생한 오류 응답을 나타내는 클래스입니다.
*/
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class JwtErrorResponse {
/** 오류가 발생한 타임스탬프입니다. */
private String timestamp;
/** 오류와 연관된 HTTP 상태 코드입니다. */
private int status;
/** 상태 코드에 따른 HTTP 오류 메시지입니다. */
private String error;
/** 오류에 대한 상세 메시지입니다. */
private String message;
/** 오류가 발생한 요청 경로입니다. */
private String path;
/**
* 주어진 세부 정보로 새로운 JwtErrorResponse 객체를 생성합니다.
*
* @param timestamp 오류 발생 시간대
* @param status HTTP 상태 코드
* @param error 상태 코드에 따른 HTTP 오류 메시지
* @param message 오류에 대한 상세 메시지
* @param path 오류가 발생한 요청 경로
*/
private JwtErrorResponse(String timestamp, int status, String error, String message, String path) {
this.timestamp = timestamp;
this.status = status;
this.error = error;
this.message = message;
this.path = path;
}
/**
* 현재 시간을 기준으로 초기화된 JwtErrorResponse 객체를 생성합니다.
*
* @param status HTTP 상태 코드
* @param error 상태 코드에 따른 HTTP 오류 메시지
* @param message 오류에 대한 상세 메시지
* @param path 오류가 발생한 요청 경로
* @return 새로 생성된 JwtErrorResponse 객체
*/
public static JwtErrorResponse of(int status, String error, String message, String path) {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return new JwtErrorResponse(timestamp, status, error, message, path);
}
}
/**
* JWT 인증이 실패할 때 접근 거부를 처리하는 핸들러 클래스입니다.
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
/**
* 접근이 거부되었을 때 호출되는 메서드입니다.
*
* 이 메서드는 클라이언트가 권한이 없는 리소스에 접근하려고 할 때 호출됩니다.
* HTTP 응답 코드 403(Forbidden)와 함께 예외 정보를 포함한 JSON 응답을 전송합니다.
*
* @param request HTTP 요청 객체
* @param response HTTP 응답 객체
* @param accessDeniedException 접근 거부 예외
* @throws IOException 입출력 예외
* @throws ServletException 서블릿 예외
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
JwtErrorResponse responseBody = JwtErrorResponse.of(
HttpStatus.FORBIDDEN.value(),
HttpStatus.FORBIDDEN.getReasonPhrase(),
"권한이 없습니다.",
request.getRequestURI());
response.getWriter().write(JsonUtils.convertObjectToJson(responseBody));
}
}
responseBody는 JwtAccessDeniedHandler 클래스의 handle 메서드에서 접근 거부 상황에 대한 정보를 클라이언트에게 JSON 형식으로 전달하기 위해 사용된다.
HttpStatus.FORBIDDEN.value() : 403 (Forbidden)을 설정하여 클라이언트가 권한이 없음을 명확하게 알림
HttpStatus.FORBIDDEN.getReasonPhrase() : 상태 코드에 해당하는 기본 오류 메시지를 저장
request.getRequestURI() : 클라이언트가 어느 경로로 요청을 시도하다가 거부되었는지 알 수 있게 함
response.getWriter().write(JsonUtils.convertObjectToJson(responseBody)); : 위에서 생성한 JSON 객체를 문자열로 변환하여 응답 본문에 기록한다.
package com.study.api.jwt.handler;
import com.study.common.exception.JwtErrorResponse;
import com.study.common.utill.JsonUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* JWT 인증 과정에서 발생하는 예외를 처리하는 핸들러 클래스로 인증되지 않은 상태로 보호된 리소스에 접근하려고 할 때 호출됩니다.
*/
@Component
public class JwtAuthenticationEntryPointHandler implements AuthenticationEntryPoint {
/**
* 인증 과정에서 예외가 발생했을 때 호출되는 메서드입니다.
*
* 이 메서드는 클라이언트가 인증되지 않은 상태로 보호된 리소스에 접근하려고 할 때 호출됩니다.
* HTTP 응답 코드 401(Unauthorized)와 함께 예외 정보를 포함한 JSON 응답을 전송합니다.
*
* @param request HTTP 요청 객체
* @param response HTTP 응답 객체
* @param authException 인증 예외
* @throws IOException 입출력 예외
* @throws ServletException 서블릿 예외
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
JwtErrorResponse responseBody = JwtErrorResponse.of(
HttpStatus.UNAUTHORIZED.value(),
HttpStatus.UNAUTHORIZED.getReasonPhrase(),
"인증 과정에 문제가 발생했습니다.",
request.getRequestURI());
String errorResponse = JsonUtils.convertObjectToJson(responseBody);
response.getWriter().write(errorResponse);
}
}