Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HOTFIX] PR #189 누락된 내용 추가 #240

Merged
merged 3 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.kakaoshare.backend.common.config;

import lombok.RequiredArgsConstructor;
import org.kakaoshare.backend.common.error.handler.AuthenticationAccessDeniedHandler;
import org.kakaoshare.backend.common.error.handler.CustomAuthenticationEntryPoint;
import org.kakaoshare.backend.common.filter.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -27,6 +29,9 @@ public class SecurityConfig {
public static final String API_V_1 = "/api/v1/";
private static final List<String> ALLOWED_HEADERS = Arrays.asList("Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With");
private static final List<String> ALLOWED_METHODS = Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS");

private final AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
private final JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
Expand All @@ -53,6 +58,11 @@ public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception
.httpBasic(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.cors(httpSecurityCorsConfigurer -> corsConfigurationSource())
.exceptionHandling(
httpSecurityExceptionHandlingConfigurer -> httpSecurityExceptionHandlingConfigurer
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(authenticationAccessDeniedHandler)
)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.kakaoshare.backend.common.error.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.codec.CharEncoding;
import org.kakaoshare.backend.common.error.GlobalErrorCode;
import org.kakaoshare.backend.common.error.response.ErrorResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;
@Component
public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {
private final ObjectMapper objectMapper = new ObjectMapper();

@Override
public void handle(final HttpServletRequest request,
final HttpServletResponse response,
final AccessDeniedException accessDeniedException) throws IOException {
final String accept = request.getHeader(HttpHeaders.ACCEPT);
if (MediaType.APPLICATION_JSON_VALUE.equals(accept)) {
final GlobalErrorCode errorCode = GlobalErrorCode.RESOURCE_NOT_FOUND;
response.setStatus(errorCode.getHttpStatus().value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(CharEncoding.UTF_8);

final ErrorResponse errorResponse = ErrorResponse.from(errorCode);
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.kakaoshare.backend.common.error.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.CharEncoding;
import org.kakaoshare.backend.common.error.GlobalErrorCode;
import org.kakaoshare.backend.common.error.response.ErrorResponse;
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;

@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final String LOG_EXCEPTION_FORMAT = "Not Authenticated Request = {}";
private static final String LOG_URI_FORMAT = "Request Uri = {}";
private final ObjectMapper objectMapper=new ObjectMapper();

@Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException {
log.error(LOG_EXCEPTION_FORMAT, authException.toString());
log.error(LOG_URI_FORMAT, request.getRequestURI());
final GlobalErrorCode errorCode = GlobalErrorCode.RESOURCE_NOT_FOUND;
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(CharEncoding.UTF_8);

final ErrorResponse errorResponse = ErrorResponse.from(errorCode);
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import static org.kakaoshare.backend.common.error.GlobalErrorCode.INTERNAL_SERVER_ERROR;
Expand All @@ -41,6 +42,17 @@ protected ResponseEntity<Object> handleMissingServletRequestParameter(final Miss
logException(e, errorCode);
return handleExceptionInternal(errorCode);
}


@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(final NoHandlerFoundException e,
final HttpHeaders headers,
final HttpStatusCode status,
final WebRequest request) {
final GlobalErrorCode errorCode = GlobalErrorCode.RESOURCE_NOT_FOUND;
logException(e, errorCode);
return handleExceptionInternal(errorCode);
}

@ExceptionHandler(BusinessException.class)
protected ResponseEntity<?> handleBusinessException(BusinessException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
package org.kakaoshare.backend.common.error.response;

import lombok.Builder;
import org.kakaoshare.backend.common.error.ErrorCode;

@Builder
public record ErrorResponse(String message,
int code
// @JsonInclude(JsonInclude.Include.NON_EMPTY)
// List<ValidationError> errors
) {
// @Builder
// public record ValidationError(String field, String message) {
//
// public static ValidationError of(final FieldError fieldError) {
// return ValidationError.builder()
// .field(fieldError.getField())
// .message(fieldError.getDefaultMessage())
// .build();
// }
// }
public record ErrorResponse(String message, int code) {
public static ErrorResponse from(final ErrorCode errorCode) {
return ErrorResponse.builder()
.code(errorCode.getHttpStatus().value())
.message(errorCode.getMessage())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package org.kakaoshare.backend.common.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.querydsl.core.util.StringUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.CharEncoding;
import org.kakaoshare.backend.common.error.response.ErrorResponse;
import org.kakaoshare.backend.jwt.exception.JwtException;
import org.kakaoshare.backend.jwt.util.JwtProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -18,6 +24,7 @@

import java.io.IOException;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
Expand All @@ -26,14 +33,26 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
private final UserDetailsService userDetailsService;

private static void printLog(final JwtException e) {
log.error("\nException Class = {}\nResponse Code = {}\nMessage = {}",
e.getClass(),
e.getErrorCode().getHttpStatus().value(),
e.getMessage());
}

@Override
protected void doFilterInternal(final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {
final String accessToken = getAccessToken(request);
if (accessToken != null && jwtProvider.validateToken(accessToken)) {
SecurityContextHolder.getContext()
.setAuthentication(getAuthentication(accessToken));
try {
final String accessToken = getAccessToken(request);
if (accessToken != null && jwtProvider.validateToken(accessToken)) {
SecurityContextHolder.getContext()
.setAuthentication(getAuthentication(accessToken));
}
} catch (JwtException e) {
handleJwtException(response, e);
printLog(e);
}

filterChain.doFilter(request, response);
Expand All @@ -51,6 +70,15 @@ private String getAccessToken(final HttpServletRequest request) {
private Authentication getAuthentication(final String accessToken) {
final String username = jwtProvider.getUsername(accessToken);
final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(userDetails.getUsername(),null, userDetails.getAuthorities());
return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), null, userDetails.getAuthorities());
}

private void handleJwtException(final HttpServletResponse response, final JwtException e) throws IOException {
final ObjectMapper objectMapper = new ObjectMapper();
response.setStatus(e.getErrorCode().getHttpStatus().value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(CharEncoding.UTF_8);
final ErrorResponse errorResponse = ErrorResponse.from(e.getErrorCode());
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package org.kakaoshare.backend.jwt.exception;

public enum JwtErrorCode {
INVALID("유효하지 않은 토큰입니다."),
EXPIRED("만료된 토큰입니다."),
UNSUPPORTED("JWT를 지원하지 않습니다."),
NOT_FOUND("토큰을 찾을 수 없습니다.");
import lombok.Getter;
import org.kakaoshare.backend.common.error.ErrorCode;
import org.springframework.http.HttpStatus;

@Getter
public enum JwtErrorCode implements ErrorCode {
INVALID(HttpStatus.BAD_REQUEST,"유효하지 않은 토큰입니다."),
EXPIRED(HttpStatus.BAD_REQUEST,"만료된 토큰입니다."),
UNSUPPORTED(HttpStatus.BAD_REQUEST,"JWT를 지원하지 않습니다."),
NOT_FOUND(HttpStatus.NOT_FOUND,"토큰을 찾을 수 없습니다.");

private final HttpStatus httpStatus;
private final String message;

JwtErrorCode(final String message) {
JwtErrorCode(final HttpStatus httpStatus, final String message) {
this.httpStatus = httpStatus;
this.message = message;
}

public String getMessage() {
return message;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.kakaoshare.backend.jwt.exception;

public class JwtException extends RuntimeException {
private final JwtErrorCode errorCode;
import org.kakaoshare.backend.common.error.ErrorCode;
import org.kakaoshare.backend.common.error.exception.BusinessException;

public JwtException(final JwtErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
public class JwtException extends BusinessException {
public JwtException(final ErrorCode errorCode) {
super(errorCode);
}
}
Loading