Skip to content

Commit

Permalink
🐛 fix: JwtExceptionFilter 응답 간소화, ResponseUtil 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
kimday0326 committed Jan 22, 2024
1 parent 6a1a150 commit 6f83bc0
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public enum SecurityErrorCode implements BaseErrorCode {
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "4012", "권한이 없습니다."),
TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "4013", "만료된 토큰입니다."),
SIGNATURE_ERROR(HttpStatus.UNAUTHORIZED, "4014", "무결하지 않은 토큰입니다."),
TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "4015", "토큰이 존재하지 않습니다.");
TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "4015", "토큰이 존재하지 않습니다."),
INTERNAL_SECURITY_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "5001", "서버 에러가 발생했습니다. 관리자에게 문의해주세요.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.sponus.sponusbe.auth.jwt.filter;

import static com.sponus.sponusbe.auth.jwt.util.JsonUtil.*;
import static com.sponus.sponusbe.auth.jwt.util.ResponseUtil.*;
import static org.springframework.http.HttpStatus.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
Expand All @@ -18,7 +16,9 @@
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sponus.sponusbe.auth.jwt.dto.JwtPair;
import com.sponus.sponusbe.auth.jwt.util.HttpResponseUtil;
import com.sponus.sponusbe.auth.jwt.util.JwtUtil;
import com.sponus.sponusbe.auth.user.CustomUserDetails;
import com.sponus.sponusbe.global.common.ApiResponse;
Expand Down Expand Up @@ -46,12 +46,7 @@ public Authentication attemptAuthentication(
try {
requestBody = getBody(request);
} catch (IOException e) {
try {
setErrorResponse(response, BAD_REQUEST);
return null;
} catch (IOException ex) {
return null;
}
throw new AuthenticationServiceException("Error occurred while parsing request body");
}

logger.info("[*] Request Body : " + requestBody);
Expand Down Expand Up @@ -81,7 +76,7 @@ protected void successfulAuthentication(
jwtUtil.createJwtRefreshToken(customUserDetails)
);

setSuccessResponse(response, jwtPair);
HttpResponseUtil.setSuccessResponse(response, HttpStatus.CREATED, jwtPair);
}

@Override
Expand All @@ -90,7 +85,7 @@ protected void unsuccessfulAuthentication(
@NonNull HttpServletResponse response,
@NonNull AuthenticationException failed) throws IOException {
logger.info("[*] Login Fail");

// TODO : 예외처리 정리
String errorMessage;
if (failed instanceof BadCredentialsException) {
errorMessage = "Bad credentials";
Expand All @@ -100,46 +95,33 @@ protected void unsuccessfulAuthentication(
errorMessage = "Account is disabled";
} else if (failed instanceof UsernameNotFoundException) {
errorMessage = "Account not found";
} else if (failed instanceof AuthenticationServiceException) {
errorMessage = "Error occurred while parsing request body";
} else {
errorMessage = "Authentication failed";
}

setFailureResponse(response, errorMessage);
}

private static void setFailureResponse(
@NonNull HttpServletResponse response,
@NonNull String errorMessage) throws
IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);

response.getWriter().print(
HttpResponseUtil.setErrorResponse(
response, HttpStatus.UNAUTHORIZED,
ApiResponse.onFailure(
String.valueOf(HttpStatus.UNAUTHORIZED.value()),
HttpStatus.UNAUTHORIZED.name(),
HttpStatus.BAD_REQUEST.name(),
errorMessage
).toJsonString()
)
);

closeWriter(response);
}

private static void setSuccessResponse(
@NonNull HttpServletResponse response,
@NonNull JwtPair jwtPair) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(200);
public Map<String, Object> getBody(HttpServletRequest request) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
String line;

response.getWriter().print(
ApiResponse.onSuccess(jwtPair).toJsonString()
);
try (BufferedReader bufferedReader = request.getReader()) {
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
}

closeWriter(response);
}
String requestBody = stringBuilder.toString();
ObjectMapper objectMapper = new ObjectMapper();

private static void closeWriter(HttpServletResponse response) throws IOException {
response.getWriter().flush();
response.getWriter().close();
return objectMapper.readValue(requestBody, Map.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;

import com.sponus.sponusbe.auth.jwt.exception.CustomExpiredJwtException;
import com.sponus.sponusbe.auth.jwt.exception.SecurityCustomException;
import com.sponus.sponusbe.auth.jwt.exception.SecurityErrorCode;
import com.sponus.sponusbe.auth.jwt.util.JwtUtil;
import com.sponus.sponusbe.auth.jwt.util.RedisUtil;

Expand Down Expand Up @@ -41,8 +42,7 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut
);
} catch (ExpiredJwtException e) {
log.warn("[*] case : accessToken expired");

throw new CustomExpiredJwtException();
throw new SecurityCustomException(SecurityErrorCode.TOKEN_EXPIRED);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,46 @@
package com.sponus.sponusbe.auth.jwt.filter;

import static com.sponus.sponusbe.auth.jwt.exception.SecurityErrorCode.*;
import static com.sponus.sponusbe.auth.jwt.util.ResponseUtil.*;
import static org.springframework.http.HttpStatus.*;

import java.io.IOException;

import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;

import com.sponus.sponusbe.auth.jwt.exception.CustomExpiredJwtException;
import com.sponus.sponusbe.auth.jwt.exception.CustomMalformedException;
import com.sponus.sponusbe.auth.jwt.exception.CustomNoTokenException;
import com.sponus.sponusbe.auth.jwt.exception.CustomSignatureException;
import com.sponus.sponusbe.global.common.ApiResponse;
import com.sponus.sponusbe.auth.jwt.exception.SecurityCustomException;
import com.sponus.sponusbe.auth.jwt.exception.SecurityErrorCode;
import com.sponus.sponusbe.auth.jwt.util.HttpResponseUtil;
import com.sponus.sponusbe.global.common.BaseErrorCode;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class JwtExceptionFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain)
throws ServletException, IOException {
@NonNull FilterChain filterChain) throws IOException {
// TODO : entrypoint로 처리하도록 변경
try {
filterChain.doFilter(request, response);
} catch (CustomExpiredJwtException e) {
logger.warn(e.getMessage());
setErrorResponse(response, TOKEN_EXPIRED.getHttpStatus());
} catch (CustomMalformedException e) {
logger.warn(e.getMessage());
setErrorResponse(response, INVALID_FORM_TOKEN.getHttpStatus());
} catch (CustomNoTokenException e) {
logger.warn(e.getMessage());
setErrorResponse(response, NO_TOKEN.getHttpStatus());
} catch (CustomSignatureException e) {
logger.warn(e.getMessage());
setErrorResponse(response, SIGNATURE_ERROR.getHttpStatus());
} catch (SecurityCustomException e) {
log.warn(">>>>> SecurityCustomException : ", e);
BaseErrorCode errorCode = e.getErrorCode();
HttpResponseUtil.setErrorResponse(
response,
errorCode.getHttpStatus(),
errorCode.getErrorResponse()
);
} catch (Exception e) {
logger.warn(e.getMessage());
logger.warn(">>>>> Internal Server Error : ", e);

response.getWriter().print(
ApiResponse.onFailure(
INTERNAL_SERVER_ERROR.name(),
INTERNAL_SERVER_ERROR.getReasonPhrase(),
e.getMessage()
).toJsonString()
log.error(">>>>> Exception : ", e);
HttpResponseUtil.setErrorResponse(
response,
HttpStatus.INTERNAL_SERVER_ERROR,
SecurityErrorCode.INTERNAL_SECURITY_ERROR.getErrorResponse()
);
}
}
Expand Down
24 changes: 7 additions & 17 deletions src/main/java/com/sponus/sponusbe/auth/jwt/filter/JwtFilter.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sponus.sponusbe.auth.jwt.filter;

import static com.sponus.sponusbe.auth.jwt.util.ResponseUtil.*;
import static com.sponus.sponusbe.auth.jwt.util.HttpResponseUtil.*;
import static org.springframework.http.HttpStatus.*;

import java.io.IOException;
Expand All @@ -12,14 +12,13 @@

import com.sponus.sponusbe.auth.jwt.dto.CachedHttpServletRequest;
import com.sponus.sponusbe.auth.jwt.dto.JwtPair;
import com.sponus.sponusbe.auth.jwt.exception.CustomExpiredJwtException;
import com.sponus.sponusbe.auth.jwt.exception.CustomNoTokenException;
import com.sponus.sponusbe.auth.jwt.exception.SecurityCustomException;
import com.sponus.sponusbe.auth.jwt.exception.SecurityErrorCode;
import com.sponus.sponusbe.auth.jwt.util.JwtUtil;
import com.sponus.sponusbe.auth.jwt.util.RedisUtil;
import com.sponus.sponusbe.auth.user.CustomUserDetails;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -38,7 +37,7 @@ protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException, CustomNoTokenException, MalformedJwtException {
) throws ServletException, IOException {
logger.info("[*] Jwt Filter");

CachedHttpServletRequest cachedHttpServletRequest = new CachedHttpServletRequest(request);
Expand All @@ -48,26 +47,20 @@ protected void doFilterInternal(
// accessToken 없이 접근할 경우
if (accessToken == null) {
filterChain.doFilter(cachedHttpServletRequest, response);

return;
}

// logout 처리된 accessToken
if (redisUtil.get(accessToken) != null &&
redisUtil.get(accessToken).equals("logout")) {
logger.info("[*] Logout accessToken");

filterChain.doFilter(cachedHttpServletRequest, response);

return;
}

logger.info("[*] Authorization with Token");

authenticateAccessToken(accessToken);

filterChain.doFilter(cachedHttpServletRequest, response);

} catch (ExpiredJwtException e) {
logger.warn("[*] case : accessToken Expired");

Expand All @@ -77,20 +70,17 @@ protected void doFilterInternal(
logger.info("[*] refreshToken : " + refreshToken);
try {
if (jwtUtil.validateRefreshToken(refreshToken)) {

logger.info("[*] case : accessToken Expired && refreshToken in redis");

// refreshToken 유효 시 재발급
JwtPair reissueTokens = jwtUtil.reissueToken(refreshToken);

setSuccessResponse(response, CREATED, reissueTokens);
}
} catch (ExpiredJwtException e1) {
logger.info("[*] case : accessToken, refreshToken expired");
throw new CustomExpiredJwtException();
throw new SecurityCustomException(SecurityErrorCode.TOKEN_EXPIRED);
} catch (IllegalArgumentException e2) {
logger.info("[*] case : refreshToken expired");
throw new CustomNoTokenException();
logger.info("[*] case : Invalid refreshToken");
throw new SecurityCustomException(SecurityErrorCode.INVALID_TOKEN);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.sponus.sponusbe.auth.jwt.util;

import java.io.IOException;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sponus.sponusbe.global.common.ApiResponse;

import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class HttpResponseUtil {
private static final ObjectMapper objectMapper = new ObjectMapper();

public static void setSuccessResponse(HttpServletResponse response, HttpStatus httpStatus, Object body)
throws IOException {
String responseBody = objectMapper.writeValueAsString(ApiResponse.onSuccess(body));
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(httpStatus.value());
response.setCharacterEncoding("UTF-8");
response.getWriter().write(responseBody);
}

public static void setErrorResponse(HttpServletResponse response, HttpStatus httpStatus, Object body)
throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(httpStatus.value());
response.setCharacterEncoding("UTF-8");
objectMapper.writeValue(response.getOutputStream(), body);
}
}
6 changes: 4 additions & 2 deletions src/main/java/com/sponus/sponusbe/auth/jwt/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import org.springframework.stereotype.Component;

import com.sponus.sponusbe.auth.jwt.dto.JwtPair;
import com.sponus.sponusbe.auth.jwt.exception.CustomExpiredJwtException;
import com.sponus.sponusbe.auth.jwt.exception.SecurityCustomException;
import com.sponus.sponusbe.auth.jwt.exception.SecurityErrorCode;
import com.sponus.sponusbe.auth.user.CustomUserDetails;

import io.jsonwebtoken.Jwts;
Expand Down Expand Up @@ -148,7 +149,8 @@ public boolean validateRefreshToken(String refreshToken) {

//redis에 refreshToken 있는지 검증
if (!redisUtil.hasKey(email)) {
throw new CustomExpiredJwtException();
log.warn("[*] case : Invalid refreshToken");
throw new SecurityCustomException(SecurityErrorCode.INVALID_TOKEN);
}
return true;
}
Expand Down
Loading

0 comments on commit 6f83bc0

Please sign in to comment.