Skip to content

Commit

Permalink
Merge pull request #2 from kusitms-28th-Meetup-E/feat/global
Browse files Browse the repository at this point in the history
Feat/global
  • Loading branch information
seungueonn committed Nov 1, 2023
2 parents 956e069 + 8fe60df commit 4671b8f
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 98 deletions.
17 changes: 11 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,26 @@ dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'


implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3'


implementation "org.springframework.cloud:spring-cloud-starter-config"
implementation "org.springframework.cloud:spring-cloud-starter-bootstrap"
implementation 'org.springframework.boot:spring-boot-starter-actuator'

//자바 역직렬화 문제 해결 패키지
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'org.springframework.boot:spring-boot-starter-webflux'



// implementation 'org.springdoc:springdoc-openapi-ui:1.6.11'
// implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.0.2'



}

dependencyManagement {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/gwangjang/server/ApiGatewayApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import gwangjang.server.filter.AuthorizationHeaderFilter;
import gwangjang.server.handler.GlobalExceptionHandler;
import gwangjang.server.security.JwtTokenProvider;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
Expand All @@ -14,6 +16,7 @@


@SpringBootApplication
@OpenAPIDefinition(info = @Info(title = "API Gateway", version = "1.0", description = "Gwang-Jang API Gateway v1.0"))
public class ApiGatewayApplication {

public static void main(String[] args) {
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/gwangjang/server/exception/ApplicationException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gwangjang.server.exception;

import gwangjang.server.response.ErrorCode;
import org.springframework.http.HttpStatus;

public abstract class ApplicationException extends RuntimeException {

private final ErrorCode errorCode;
private final HttpStatus httpStatus;

protected ApplicationException(ErrorCode errorCode, HttpStatus httpStatus) {
super(errorCode.getMessage());
this.errorCode = errorCode;
this.httpStatus = httpStatus;
}

public ErrorCode getErrorCode() { return errorCode;}

public HttpStatus getHttpStatus() {
return httpStatus;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//package gwangjang.server.exception;
//
//import gwangjang.server.response.ErrorCode;
//import gwangjang.server.response.ErrorResponse;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.context.support.DefaultMessageSourceResolvable;
//import org.springframework.http.HttpStatus;
//import org.springframework.http.ResponseEntity;
//import org.springframework.http.converter.HttpMessageNotReadableException;
//import org.springframework.web.HttpRequestMethodNotSupportedException;
//import org.springframework.web.bind.MethodArgumentNotValidException;
//import org.springframework.web.bind.annotation.ExceptionHandler;
//import org.springframework.web.bind.annotation.RestControllerAdvice;
//
//import java.util.function.Consumer;
//import java.util.regex.PatternSyntaxException;
//import java.util.stream.Collectors;
//
//@RestControllerAdvice
//@Slf4j
//public class GlobalExceptionHandler {
//
// private static final String LOG_FORMAT = "Class : {}, Code : {}, Message : {}";
//
// @ExceptionHandler(ApplicationException.class)
// public ResponseEntity<ErrorResponse> handleApplicationException(ApplicationException ex) {
// return handleException(ex, ex.getErrorCode(), ex.getMessage(), ex.getHttpStatus(), log::warn);
// }
//
// @ExceptionHandler(MethodArgumentNotValidException.class)
// public ResponseEntity<ErrorResponse> inputMethodArgumentInvalidExceptionHandler (MethodArgumentNotValidException ex) {
// String message = ex.getBindingResult().getFieldErrors().stream()
// .map(DefaultMessageSourceResolvable::getDefaultMessage)
// .collect(Collectors.joining(", "));
// return handleException(ex, ErrorCode.BAD_REQUEST, message, HttpStatus.BAD_REQUEST, log::warn);
// }
//
// @ExceptionHandler(PatternSyntaxException.class)
// public ResponseEntity<ErrorResponse> inputPatternSyntaxExceptionHandler(PatternSyntaxException ex) {
// return handleException(ex, ErrorCode.BAD_REQUEST, ErrorCode.BAD_REQUEST.getMessage(), HttpStatus.BAD_REQUEST, log::warn);
// }
//
// @ExceptionHandler(HttpMessageNotReadableException.class)
// public ResponseEntity<ErrorResponse> jsonParseExceptionHandler(HttpMessageNotReadableException ex) {
// return handleException(ex, ErrorCode.BAD_REQUEST, ErrorCode.BAD_REQUEST.getMessage(), HttpStatus.BAD_REQUEST, log::warn);
// }
//
// @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
// public ResponseEntity<ErrorResponse> httpRequestNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
// return handleException(ex, ErrorCode.METHOD_NOT_ALLOWED, ErrorCode.METHOD_NOT_ALLOWED.getMessage(), HttpStatus.METHOD_NOT_ALLOWED, log::warn);
// }
//
// @ExceptionHandler(Exception.class)
// public ResponseEntity<ErrorResponse> internalServerErrorHandler(Exception ex) {
// return handleException(ex, ErrorCode.INTERNAL_SERVER_ERROR, ErrorCode.INTERNAL_SERVER_ERROR.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR, log::error);
// }
//
// private ResponseEntity<ErrorResponse> handleException(Exception ex, ErrorCode errorCode, String message, HttpStatus httpStatus, Consumer<String> logger) {
// log.error(LOG_FORMAT, ex.getClass().getSimpleName(), errorCode.getErrorCode(), ex.getMessage());
// ErrorResponse errorResponse = new ErrorResponse(errorCode, message);
// return ResponseEntity.status(httpStatus.value()).body(errorResponse);
// }
//
//}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gwangjang.server.exception;

public class InternalServerErrorException extends RuntimeException{

public InternalServerErrorException(String message){ super(message); }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import gwangjang.server.response.ErrorCode;
import gwangjang.server.response.ErrorResponse;
import gwangjang.server.response.SuccessResponse;
import gwangjang.server.security.JwtTokenProvider;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.InvalidClaimException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -22,6 +28,8 @@
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.NoSuchElementException;

@Component
@Slf4j
Expand Down Expand Up @@ -50,7 +58,7 @@ public GatewayFilter apply(Config config) {

HttpHeaders headers = request.getHeaders();
if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
return onError(exchange, "No authorization header", HttpStatus.UNAUTHORIZED);
return onError(exchange, NoSuchFieldException.class);
}

log.info("authorizationHeader.get-start");
Expand Down Expand Up @@ -85,28 +93,56 @@ public GatewayFilter apply(Config config) {
//isJwtValid: JWT를 파싱하여 유효한 토큰인지 확인한다. 여기서 사용되는 token.secret는 다음 단계에서 입력하겠지만 유저 서비스에서 사용하는 토큰과 동일하다.

// Mono(단일 값), Flux(다중 값) -> Spring WebFlux
private Mono<Void> onError(ServerWebExchange exchange, String errorMsg, HttpStatus httpStatus) {
log.error(errorMsg);
private Mono<Void> onError(ServerWebExchange exchange, Class<? extends Throwable> exceptionClass) {

ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(httpStatus);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.INTERNAL_SERVER_ERROR);
// byte[] bytes = errorResponse.toString().getBytes(StandardCharsets.UTF_8);
// DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
ServerHttpResponse response = exchange.getResponse();

Object responseBody = new HashMap<>();

if (exceptionClass == ExpiredJwtException.class) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody = new ErrorResponse<>(ErrorCode.EXPIRED_JWT);

} else if (exceptionClass == UnsupportedJwtException.class){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody = new ErrorResponse<>(ErrorCode.UNSUPPORTED_TOKEN);

} else if (exceptionClass == IllegalArgumentException.class){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody = new ErrorResponse<>(ErrorCode.INVALID_TOKEN);

} else if (exceptionClass == SecurityException.class || exceptionClass == MalformedJwtException.class){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody = new ErrorResponse<>(ErrorCode.INVALID_JWT_TOKEN);

} else if (exceptionClass == NoSuchFieldException.class ){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody = new ErrorResponse<>(ErrorCode.NOT_FOUND_JWT_TOKEN);
}
// 성공 시
else {
exchange.getResponse().setStatusCode(exchange.getResponse().getStatusCode());
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody = SuccessResponse.create("AuthorizationHeaderFilter else");
// responseBody.put("data",new ErrorResponse<>(ErrorCode.UNAUTHORIZED));
}

response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

DataBuffer wrap = null;
try {
byte[] bytes = objectMapper.writeValueAsBytes(errorResponse);
byte[] bytes = objectMapper.writeValueAsBytes(responseBody);
wrap = exchange.getResponse().bufferFactory().wrap(bytes);
} catch (JsonProcessingException e) {
e.printStackTrace();
}

response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return exchange.getResponse().writeWith(Flux.just(wrap));


}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import gwangjang.server.response.ErrorCode;
import org.springframework.http.HttpStatus;

public abstract class ApplicationException extends RuntimeException {
public abstract class ApplicationException extends Throwable {

private final ErrorCode errorCode;
private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,18 @@ public class GlobalExceptionHandler implements ErrorWebExceptionHandler {

private final ObjectMapper objectMapper;

private static final String LOG_FORMAT = "Class : {}, Code : {}, Message : {}";


@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {

ServerHttpResponse response = exchange.getResponse();
List<Class<? extends RuntimeException>> jwtExceptions =
List.of(SignatureException.class,
MalformedJwtException.class,
UnsupportedJwtException.class,
IllegalArgumentException.class);
Class<? extends Throwable> exceptionClass = ex.getClass();



Object responseBody = new HashMap<>();

if (exceptionClass == ExpiredJwtException.class) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);

responseBody = new ErrorResponse<>(ErrorCode.EXPIRED_JWT);

} else if (exceptionClass == UnsupportedJwtException.class){
Expand All @@ -78,9 +68,8 @@ public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
else {
exchange.getResponse().setStatusCode(exchange.getResponse().getStatusCode());
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
responseBody = SuccessResponse.create("테스트 로그인에 성공하였습니다.");
responseBody = SuccessResponse.create("GlobalExceptionhandler else");
// responseBody.put("data",new ErrorResponse<>(ErrorCode.UNAUTHORIZED));

}

response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Expand All @@ -96,20 +85,5 @@ public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
return exchange.getResponse().writeWith(Flux.just(wrap));
}

@ExceptionHandler(ApplicationException.class)
public ResponseEntity<ErrorResponse> handleApplicationException(ApplicationException ex) {
log.error(LOG_FORMAT, ex.getClass().getSimpleName(), ex.getErrorCode(), ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(ex.getErrorCode(), ex.getMessage());
return ResponseEntity.status(ex.getHttpStatus().value()).body(errorResponse);
}


private ErrorResponse handleException(Exception ex, ErrorCode errorCode, String message, HttpStatus httpStatus, Consumer<String> logger) {
log.error(LOG_FORMAT, ex.getClass().getSimpleName(), errorCode.getErrorCode(), ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(errorCode, message);
return errorResponse;
}



}
2 changes: 0 additions & 2 deletions src/main/java/gwangjang/server/handler/ModuleConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

Expand All @@ -13,7 +12,6 @@ public class ModuleConfig {
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
}
26 changes: 26 additions & 0 deletions src/main/java/gwangjang/server/handler/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gwangjang.server.handler;


import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class SwaggerConfig {
// @Bean
// public OpenAPI openAPI() {
// return new OpenAPI()
// .components(new Components())
// .info(apiInfo());
// }
//
// private Info apiInfo() {
// return new Info()
// .title("Springdoc 테스트")
// .description("Springdoc을 사용한 Swagger UI 테스트")
// .version("1.0.0");
// }
}
1 change: 1 addition & 0 deletions src/main/java/gwangjang/server/response/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public enum ErrorCode {
UNSUPPORTED_TOKEN("402","지원되지 않는 토큰입니다."),
INVALID_TOKEN("402","토큰이 잘못되었습니다."),
INVALID_JWT_TOKEN("402","잘못된 JWT 서명입니다."),
NOT_FOUND_JWT_TOKEN("402","JWT 토큰이 없습니다."),

//FCM 토큰 관련
INITIALIZE_ERROR("F0001", "Firebase Admin SDK 초기화에 실패했습니다."),
Expand Down
Loading

0 comments on commit 4671b8f

Please sign in to comment.