Initial commit

This commit is contained in:
jooho
2021-10-20 17:12:00 +09:00
parent 0c884beff8
commit 8caa4bbc5a
487 changed files with 44198 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
package org.egovframe.cloud.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* org.egovframe.cloud.apigateway.ApigatewayApplication
* <p>
* 게이트웨이 어플리케이션 클래스
* Eureka Client 로 설정했기 때문에 Eureka Server 가 먼저 기동되어야 한다.
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@EnableDiscoveryClient
@SpringBootApplication
public class ApigatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApigatewayApplication.class, args);
}
}

View File

@@ -0,0 +1,43 @@
package org.egovframe.cloud.apigateway.api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Locale;
/**
* org.egovframe.cloud.apigateway.api.MessageSourceApiController
* <p>
* MessageSource 정상 확인을 위한 컨트롤러
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/08/10
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/08/10 jaeyeolkim 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
@RestController
public class MessageSourceApiController {
private final MessageSource messageSource;
@GetMapping("/api/v1/messages/{code}/{lang}")
public String getMessage(@PathVariable String code, @PathVariable String lang) {
Locale locale = "en".equals(lang)? Locale.ENGLISH : Locale.KOREAN;
String message = messageSource.getMessage(code, null, locale);
log.info("code={}, lang={}, message={}", code, lang, message);
return message;
}
}

View File

@@ -0,0 +1,70 @@
package org.egovframe.cloud.apigateway.api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
import java.util.Optional;
/**
* org.egovframe.cloud.apigateway.api.SwaggerResourcesController
*
* Swagger resource 들을 모으는 controller class
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/07/07
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/07 shinmj 최초 생성
* </pre>
*/
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerResourcesController {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerResourcesController(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()),
HttpStatus.OK
));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()),
HttpStatus.OK
));
}
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just(new ResponseEntity(
swaggerResources.get(), HttpStatus.OK
));
}
}

View File

@@ -0,0 +1,64 @@
package org.egovframe.cloud.apigateway.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* org.egovframe.cloud.apigateway.config.MessageSourceConfig
* <p>
* Spring MessageSource 설정
* Message Domain 이 있는 portal-service 에서 messages.properties 를 공유 가능한 외부 위치에 생성한다.
* 각 서비스에서 해당 파일을 통해 다국어를 지원하도록 한다.
* module-common.jar 를 포함하지 않는 서비스에서는 이 configuration을 추가해주어야 한다.
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/08/09
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/08/09 jaeyeolkim 최초 생성
* </pre>
*/
@Slf4j
@Configuration
public class MessageSourceConfig {
@Value("${messages.directory}")
private String messagesDirectory;
@Value("${spring.profiles.active:default}")
private String profile;
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
final String MESSAGES = "/messages";
if ("default".equals(profile)) {
Path fileStorageLocation = Paths.get(messagesDirectory).toAbsolutePath().normalize();
String dbMessages = StringUtils.cleanPath("file://" + fileStorageLocation + MESSAGES);
log.info("DB MessageSource location = {}", dbMessages);
messageSource.setBasenames(dbMessages);
} else {
messageSource.setBasenames(messagesDirectory + MESSAGES);
}
messageSource.getBasenameSet().forEach(s -> log.info("messageSource getBasenameSet={}", s));
messageSource.setCacheSeconds(60); // 메세지 파일 변경 감지 간격
messageSource.setUseCodeAsDefaultMessage(true); // 메세지가 없으면 코드를 메세지로 한다
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
return messageSource;
}
}

View File

@@ -0,0 +1,127 @@
package org.egovframe.cloud.apigateway.config;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.access.AuthorizationServiceException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
/**
* org.egovframe.cloud.apigateway.config.ReactiveAuthorization
* <p>
* Spring Security 에 의해 요청 url에 대한 사용자 인가 서비스를 수행하는 클래스
* 요청에 대한 사용자의 권한여부 체크하여 true/false 리턴한다
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/19
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/19 jaeyeolkim 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class ReactiveAuthorization implements ReactiveAuthorizationManager<AuthorizationContext> {
@Value("${apigateway.host:http://localhost:8000}")
private String APIGATEWAY_HOST;
@Value("${token.secret}")
private String TOKEN_SECRET;
// org.egovframe.cloud.common.config.GlobalConstant 값도 같이 변경해주어야 한다.
public static final String AUTHORIZATION_URI = "/user-service" + "/api/v1/authorizations/check";
public static final String REFRESH_TOKEN_URI = "/user-service" + "/api/v1/users/token/refresh";
/**
* 요청에 대한 사용자의 권한여부 체크하여 true/false 리턴한다
* 헤더에 토큰이 있으면 유효성을 체크한다.
*
* @param authentication
* @param context
* @return
* @see WebFluxSecurityConfig
*/
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext context) {
ServerHttpRequest request = context.getExchange().getRequest();
RequestPath requestPath = request.getPath();
HttpMethod httpMethod = request.getMethod();
String baseUrl = APIGATEWAY_HOST + AUTHORIZATION_URI + "?httpMethod=" + httpMethod + "&requestPath=" + requestPath;
log.info("baseUrl={}", baseUrl);
String authorizationHeader = "";
if (request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)
&& StringUtils.hasLength(
request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0))
&& !"undefined".equals(request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0))
) {
try {
authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);
String jwt = authorizationHeader.replace("Bearer", "");
String subject = Jwts.parser().setSigningKey(TOKEN_SECRET)
.parseClaimsJws(jwt)
.getBody()
.getSubject();
// refresh token 요청 시 토큰 검증만 하고 인가 처리 한다.
if (REFRESH_TOKEN_URI.equals(requestPath + "")) {
return Mono.just(new AuthorizationDecision(true));
}
if (subject == null || subject.isEmpty()) {
log.error("토큰 인증 오류");
throw new AuthorizationServiceException("토큰 인증 오류");
}
} catch (IllegalArgumentException e) {
log.error("토큰 헤더 오류 : {}", e.getMessage());
throw new AuthorizationServiceException("토큰 인증 오류");
} catch (ExpiredJwtException e) {
log.error("토큰 유효기간이 만료되었습니다. : {}", e.getMessage());
throw new AuthorizationServiceException("토큰 유효기간 만료");
} catch (Exception e) {
log.error("토큰 인증 오류 Exception : {}", e.getMessage());
throw new AuthorizationServiceException("토큰 인증 오류");
}
}
Boolean granted = false;
try {
String token = authorizationHeader; // Variable used in lambda expression should be final or effectively final
Mono<Boolean> body = WebClient.create(baseUrl)
.get()
.headers(httpHeaders -> {
httpHeaders.add(HttpHeaders.AUTHORIZATION, token);
})
.retrieve().bodyToMono(Boolean.class);
granted = body.block();
log.info("Security AuthorizationDecision granted={}", granted);
} catch (Exception e) {
log.error("인가 서버에 요청 중 오류 : {}", e.getMessage());
throw new AuthorizationServiceException("인가 요청시 오류 발생");
}
return Mono.just(new AuthorizationDecision(granted));
}
}

View File

@@ -0,0 +1,78 @@
package org.egovframe.cloud.apigateway.config;
import lombok.AllArgsConstructor;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
/**
* org.egovframe.cloud.apigateway.config.SwaggerProvider
*
* Swagger API Doc aggregator class
* Swagger Resource인 api-docs를 가져오는 provider
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/07/07
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/07 shinmj 최초 생성
* </pre>
*/
@AllArgsConstructor
@Component
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {
public static final String API_URL = "/v2/api-docs";
public static final String WEBFLUX_API_URL = "/v3/api-docs";
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
gatewayProperties.getRoutes().stream()
.filter(routeDefinition -> routes.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition ->
resources.add(
swaggerResource(routeDefinition.getId(),
predicateDefinition.
getArgs().
get(NameUtils.GENERATED_NAME_PREFIX+"0").
replace("/**", API_URL))))
);
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
if (name.contains("reserve")) {
swaggerResource.setLocation(location.replace(API_URL, WEBFLUX_API_URL));
}else {
swaggerResource.setLocation(location);
}
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}

View File

@@ -0,0 +1,67 @@
package org.egovframe.cloud.apigateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
import org.springframework.security.web.server.authorization.AuthorizationContext;
/**
* org.egovframe.cloud.apigateway.config.WebFluxSecurityConfig
* <p>
* Spring Security Config 클래스
* ReactiveAuthorizationManager<AuthorizationContext> 구현체 ReactiveAuthorization 클래스를 통해 인증/인가 처리를 구현한다.
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@EnableWebFluxSecurity // Spring Security 설정들을 활성화시켜 준다
public class WebFluxSecurityConfig {
private final static String[] PERMITALL_ANTPATTERNS = {
ReactiveAuthorization.AUTHORIZATION_URI, "/", "/csrf",
"/user-service/login", "/?*-service/api/v1/messages/**", "/api/v1/messages/**",
"/?*-service/actuator/?*", "/actuator/?*",
"/?*-service/v2/api-docs", "/?*-service/v3/api-docs", "**/configuration/*", "/swagger*/**", "/webjars/**"
};
private final static String USER_JOIN_ANTPATTERNS = "/user-service/api/v1/users";
/**
* WebFlux 스프링 시큐리티 설정
*
* @see ReactiveAuthorization
* @param http
* @param check check(Mono<Authentication> authentication, AuthorizationContext context)
* @return
* @throws Exception
*/
@Bean
public SecurityWebFilterChain configure(ServerHttpSecurity http, ReactiveAuthorizationManager<AuthorizationContext> check) throws Exception {
http
.csrf().disable()
.headers().frameOptions().disable()
.and()
.formLogin().disable()
.httpBasic().authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)) // login dialog disabled & 401 HttpStatus return
.and()
.authorizeExchange()
.pathMatchers(PERMITALL_ANTPATTERNS).permitAll()
.pathMatchers(HttpMethod.POST, USER_JOIN_ANTPATTERNS).permitAll()
.anyExchange().access(check);
return http.build();
}
}

View File

@@ -0,0 +1,98 @@
package org.egovframe.cloud.apigateway.exception;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.cloud.apigateway.exception.dto.ErrorCode;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.reactive.function.server.ServerRequest;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* org.egovframe.cloud.apigateway.exception.ReactiveExceptionHandlerConfig
* <p>
* 에러 발생 시 에러 정보 중 필요한 내용만 반환한다
* ErrorCode 에서 status, code, message 세 가지 속성을 의존한다
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/16
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/16 jaeyeolkim 최초 생성
* </pre>
*/
@Slf4j
//@Configuration
public class ReactiveExceptionHandlerConfig {
private final MessageSource messageSource;
public ReactiveExceptionHandlerConfig(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* 에러 발생 시 에러 정보 중 필요한 내용만 반환한다
*
* @return
*/
// @Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Map<String, Object> defaultMap = super.getErrorAttributes(request, options);
Map<String, Object> errorAttributes = new LinkedHashMap<>();
int status = (int) defaultMap.get("status");
ErrorCode errorCode = getErrorCode(status);
String message = messageSource.getMessage(errorCode.getMessage(), null, LocaleContextHolder.getLocale());
errorAttributes.put("timestamp", LocalDateTime.now());
errorAttributes.put("message", message);
errorAttributes.put("status", status);
errorAttributes.put("code", errorCode.getCode());
// API Gateway 에서 FieldError는 처리하지 않는다.
log.error("getErrorAttributes()={}", defaultMap);
return errorAttributes;
}
};
}
/**
* 상태코드로부터 ErrorCode 를 매핑하여 리턴한다.
*
* @param status
* @return
*/
private ErrorCode getErrorCode(int status) {
switch (status) {
case 400:
return ErrorCode.ENTITY_NOT_FOUND;
case 401:
return ErrorCode.UNAUTHORIZED;
case 403:
return ErrorCode.ACCESS_DENIED;
case 404:
return ErrorCode.NOT_FOUND;
case 405:
return ErrorCode.METHOD_NOT_ALLOWED;
case 422:
return ErrorCode.UNPROCESSABLE_ENTITY;
default:
return ErrorCode.INTERNAL_SERVER_ERROR;
}
}
}

View File

@@ -0,0 +1,59 @@
package org.egovframe.cloud.apigateway.exception.dto;
/**
* org.egovframe.cloud.common.exception.dto.ErrorCode
* <p>
* REST API 요청에 대한 오류 반환값을 정의
* ErrorResponse 클래스에서 status, code, message 세 가지 속성을 의존한다
* message 는 MessageSource 의 키 값을 정의하여 다국어 처리를 지원한다
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/16
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/16 jaeyeolkim 최초 생성
* </pre>
*/
public enum ErrorCode {
INVALID_INPUT_VALUE(400, "E001", "err.invalid.input.value"), // Bad Request
INVALID_TYPE_VALUE(400, "E002", "err.invalid.type.value"), // Bad Request
ENTITY_NOT_FOUND(400, "E003", "err.entity.not.found"), // Bad Request
UNAUTHORIZED(401, "E004", "err.unauthorized"), // The request requires an user authentication
ACCESS_DENIED(403, "E005", "err.access.denied"), // Forbidden, Access is Denied
NOT_FOUND(404, "E007", "err.not.found"), // Not found
METHOD_NOT_ALLOWED(405, "E008", "err.method.not.allowed"), // 요청 방법이 서버에 의해 알려졌으나, 사용 불가능한 상태
UNPROCESSABLE_ENTITY(422, "E009", "err.unprocessable.entity"), // Unprocessable Entity
INTERNAL_SERVER_ERROR(500, "E999", "err.internal.server"), // Server Error
SERVICE_UNAVAILABLE(503, "E010", "err.service.unavailable") // Service Unavailable
;
private final int status;
private final String code;
private final String message;
ErrorCode(final int status, final String code, final String message) {
this.status = status;
this.code = code;
this.message = message;
}
public int getStatus() {
return status;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,49 @@
package org.egovframe.cloud.apigateway.filter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
public GlobalFilter() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// Pre filter
return ((exchange, chain) -> {
// Netty 비동기 방식 서버 사용시에는 ServerHttpRequest 를 사용해야 한다.
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
if (config.isPreLogger()) {
log.info("[GlobalFilter Start] request ID: {}, method: {}, path: {}", request.getId(), request.getMethod(), request.getPath());
}
// Post Filter
// 비동기 방식의 단일값 전달시 Mono 사용(Webflux)
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
if (config.isPostLogger()) {
log.info("[GlobalFilter End ] request ID: {}, method: {}, path: {}, statusCode: {}", request.getId(), request.getMethod(), request.getPath(), response.getStatusCode());
}
}));
});
}
@Data
public static class Config {
// put the configure
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
}
}

View File

@@ -0,0 +1,49 @@
package org.egovframe.cloud.apigateway.filter;
import org.egovframe.cloud.apigateway.config.SwaggerProvider;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
/**
* org.egovframe.cloud.apigateway.filter.SwaggerHeaderFilter
*
* Swagger header filter class
* 각 서비스 명을 붙여서 호출 할 수 있도록 filter를 추가 한다.
*
* @author 표준프레임워크센터 shinmj
* @version 1.0
* @since 2021/07/07
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/07 shinmj 최초 생성
* </pre>
*/
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URL)) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URL));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}

View File

@@ -0,0 +1,67 @@
server:
port: 8000
spring:
application:
name: apigateway
cloud:
gateway:
routes:
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/**
filters:
- RemoveRequestHeader=Cookie
- RewritePath=/user-service/(?<segment>.*), /$\{segment}
- SwaggerHeaderFilter
- id: portal-service
uri: lb://PORTAL-SERVICE
predicates:
- Path=/portal-service/**
filters:
- RewritePath=/portal-service/(?<segment>.*), /$\{segment}
- SwaggerHeaderFilter
- id: board-service
uri: lb://BOARD-SERVICE
predicates:
- Path=/board-service/**
filters:
- RewritePath=/board-service/(?<segment>.*), /$\{segment}
- SwaggerHeaderFilter
- id: reserve-item-service
uri: lb://RESERVE-ITEM-SERVICE
predicates:
- Path=/reserve-item-service/**
filters:
- RewritePath=/reserve-item-service/(?<segment>.*), /$\{segment}
- SwaggerHeaderFilter
- id: reserve-check-service
uri: lb://RESERVE-CHECK-SERVICE
predicates:
- Path=/reserve-check-service/**
filters:
- RewritePath=/reserve-check-service/(?<segment>.*), /$\{segment}
- SwaggerHeaderFilter
- id: reserve-request-service
uri: lb://RESERVE-REQUEST-SERVICE
predicates:
- Path=/reserve-request-service/**
filters:
- RewritePath=/reserve-request-service/(?<segment>.*), /$\{segment}
- SwaggerHeaderFilter
default-filters:
- name: GlobalFilter
args:
preLogger: true
postLogger: true
discovery:
locator:
enabled: true
# config server actuator
management:
endpoints:
web:
exposure:
include: refresh, health, beans

View File

@@ -0,0 +1,5 @@
spring:
cloud:
config:
uri: http://localhost:8888
name: apigateway

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %thread %-5level %logger - %m%n</pattern>
</encoder>
</appender>
<!-- 로컬에서는 로그를 전송하지 않도록 설정 -->
<springProfile name="default">
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</springProfile>
<springProfile name="!default">
<!-- java -Ddestination="localhost:8088" 와 같이 변경할 수 있다. cf 환경에서는 manifest.yml 파일에 환경변수로 추가 -->
<property name="destination" value="localhost:8088" />
<property name="app_name" value="${app_name}" />
<!-- ELK - Logstash 로 로그를 전송하기 위한 appender -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${destination}</destination><!-- native profile => localhost:8088 -->
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app.name":"${app_name}"}</customFields>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="LOGSTASH" />
<appender-ref ref="STDOUT" />
</root>
</springProfile>
</configuration>