🔧 Add 보안점검에 공통 모듈 포함
This commit is contained in:
94
backend/module-common/build.gradle
Normal file
94
backend/module-common/build.gradle
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.springframework.boot' version '2.4.5'
|
||||||
|
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
||||||
|
// querydsl
|
||||||
|
id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
group 'org.egovframe.cloud'
|
||||||
|
version '0.1'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "http://www.egovframe.go.kr/maven/" } // egovframe maven 원격 저장소 TODO https
|
||||||
|
}
|
||||||
|
|
||||||
|
bootJar {
|
||||||
|
enabled(false)
|
||||||
|
}
|
||||||
|
jar {
|
||||||
|
enabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compileOnly {
|
||||||
|
extendsFrom annotationProcessor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ext {
|
||||||
|
set('springCloudVersion', "2020.0.3")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// EgovAbstractServiceImpl
|
||||||
|
implementation('org.egovframe.rte:org.egovframe.rte.fdl.cmmn:4.0.0') {
|
||||||
|
exclude group: 'org.egovframe.rte', module: 'org.egovframe.rte.fdl.logging'
|
||||||
|
}
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-validation' // LocalValidatorFactoryBean
|
||||||
|
implementation 'io.jsonwebtoken:jjwt:0.9.1'
|
||||||
|
// querydsl
|
||||||
|
implementation 'com.querydsl:querydsl-jpa'
|
||||||
|
|
||||||
|
//openapi docs
|
||||||
|
implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
|
||||||
|
implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
|
||||||
|
implementation 'org.springdoc:springdoc-openapi-webmvc-core:1.5.8'
|
||||||
|
implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.5.8'
|
||||||
|
|
||||||
|
//reactive
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||||
|
|
||||||
|
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
|
||||||
|
|
||||||
|
//messaging
|
||||||
|
implementation 'org.springframework.cloud:spring-cloud-stream'
|
||||||
|
implementation 'org.springframework.cloud:spring-cloud-stream-binder-rabbit'
|
||||||
|
|
||||||
|
// lombok
|
||||||
|
implementation 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
testImplementation 'org.projectlombok:lombok'
|
||||||
|
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testImplementation 'io.projectreactor:reactor-test'
|
||||||
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyManagement {
|
||||||
|
imports {
|
||||||
|
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// querydsl 추가 시작
|
||||||
|
def querydslDir = "$buildDir/generated/querydsl"
|
||||||
|
querydsl {
|
||||||
|
jpa = true
|
||||||
|
querydslSourcesDir = querydslDir
|
||||||
|
}
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDir querydslDir
|
||||||
|
}
|
||||||
|
configurations {
|
||||||
|
querydsl.extendsFrom compileClasspath
|
||||||
|
}
|
||||||
|
compileQuerydsl {
|
||||||
|
options.annotationProcessorPath = configurations.querydsl
|
||||||
|
}
|
||||||
|
// querydsl 추가 끝
|
||||||
185
backend/module-common/gradlew
vendored
Executable file
185
backend/module-common/gradlew
vendored
Executable file
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
89
backend/module-common/gradlew.bat
vendored
Normal file
89
backend/module-common/gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
2
backend/module-common/settings.gradle
Normal file
2
backend/module-common/settings.gradle
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
rootProject.name = 'module-common'
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.egovframe.cloud.common.config;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.InitBinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.config.WebControllerAdvice
|
||||||
|
*
|
||||||
|
* 모든 컨트롤러에 적용되는 컨트롤러 어드바이스 클래스
|
||||||
|
* 예외 처리 (@ExceptionHandler), 바인딩 설정(@InitBinder), 모델 객체(@ModelAttributes)
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jooho
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/12
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/12 jooho 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@ControllerAdvice
|
||||||
|
public class ApiControllerAdvice {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모든 컨트롤러로 들어오는 요청 초기화
|
||||||
|
*
|
||||||
|
* @param binder 웹 데이터 바인더
|
||||||
|
*/
|
||||||
|
@InitBinder
|
||||||
|
public void initBinder(WebDataBinder binder) {
|
||||||
|
binder.initDirectFieldAccess(); // Setter 구현 없이 DTO 클래스 필드에 접근
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.egovframe.cloud.common.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.config.Constants
|
||||||
|
*
|
||||||
|
* 공통 전역 상수 정의
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/19
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/19 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public interface GlobalConstant {
|
||||||
|
final String HEADER_SITE_ID = "X-Site-Id"; // header에 어떤 사이트에서 보내는 요청인지 구분하기 위한 정보
|
||||||
|
final String AUTHORIZATION_URI = "/api/v1/authorizations/check";
|
||||||
|
final String REFRESH_TOKEN_URI = "/api/v1/users/token/refresh";
|
||||||
|
final String MESSAGES_URI = "/api/v1/messages/**";
|
||||||
|
final String LOGIN_URI = "/login";
|
||||||
|
final String[] SECURITY_PERMITALL_ANTPATTERNS = {AUTHORIZATION_URI, REFRESH_TOKEN_URI, MESSAGES_URI, LOGIN_URI, "/actuator/**", "/v3/api-docs/**", "/api/v1/images/**", "/swagger-ui.html"};
|
||||||
|
final String USER_SERVICE_URI = "/user-service";
|
||||||
|
//예약 신청 후 재고 변경 성공여부 exchange name
|
||||||
|
final String SUCCESS_OR_NOT_EX_NAME = "success-or-not.direct";
|
||||||
|
// 첨부파일 저장 후 entity 정보 update binding name
|
||||||
|
final String ATTACHMENT_ENTITY_BINDING_NAME = "attachmentEntity-out-0";
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.egovframe.cloud.common.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.egovframe.rte.fdl.cmmn.trace.LeaveaTrace;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.config.LeaveaTraceConfig
|
||||||
|
* <p>
|
||||||
|
* LeaveaTrace Bean 설정
|
||||||
|
* EgovAbstractServiceImpl 클래스가 의존한다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/09/24
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/09/24 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class LeaveaTraceConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LeaveaTrace leaveaTrace() {
|
||||||
|
return new LeaveaTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.egovframe.cloud.common.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.common.config.MessageSourceConfig
|
||||||
|
* <p>
|
||||||
|
* Spring MessageSource 설정
|
||||||
|
* Message Domain 이 있는 portal-service 에서 messages.properties 를 외부 위치에 생성한다.
|
||||||
|
* 각 서비스에서 해당 파일을 통해 다국어를 지원하도록 한다.
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.egovframe.cloud.common.config;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.Components;
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class OpenApiDocsConfig {
|
||||||
|
|
||||||
|
@Value("${spring.application.name}")
|
||||||
|
private String appName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @TODO
|
||||||
|
* api info update 필요
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public OpenAPI customOpenAPI() {
|
||||||
|
Server server = new Server();
|
||||||
|
server.url("/"+appName);
|
||||||
|
List<Server> servers = new ArrayList<>();
|
||||||
|
servers.add(server);
|
||||||
|
return new OpenAPI()
|
||||||
|
.components(new Components())
|
||||||
|
.servers(servers)
|
||||||
|
.info(new io.swagger.v3.oas.models.info.Info().title(appName+" API"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.egovframe.cloud.common.domain;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.domain.Role
|
||||||
|
* <p>
|
||||||
|
* 사용자 권한
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/06/30
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/06/30 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum Role {
|
||||||
|
|
||||||
|
// 스프링 시큐리티에서는 권한 코드에 항상 ROLE_ 이 앞에 있어야 한다.
|
||||||
|
ANONYMOUS("ROLE_ANONYMOUS", "손님"),
|
||||||
|
USER("ROLE_USER", "일반 사용자"),
|
||||||
|
EMPLOYEE("ROLE_EMPLOYEE", "내부 사용자"),
|
||||||
|
ADMIN("ROLE_ADMIN", "시스템 관리자");
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
private final String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 권한 id로 상수 검색
|
||||||
|
*
|
||||||
|
* @param key 권한 id
|
||||||
|
* @return Role 권한 상수
|
||||||
|
*/
|
||||||
|
public static Role findByKey(String key) {
|
||||||
|
return Arrays.stream(Role.values()).filter(c -> c.getKey().equals(key)).findAny().orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.egovframe.cloud.common.dto;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class AttachmentEntityMessage {
|
||||||
|
|
||||||
|
private String attachmentCode;
|
||||||
|
private String entityName;
|
||||||
|
private String entityId;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public AttachmentEntityMessage(String attachmentCode, String entityName, String entityId) {
|
||||||
|
this.attachmentCode = attachmentCode;
|
||||||
|
this.entityName = entityName;
|
||||||
|
this.entityId = entityId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.egovframe.cloud.common.dto;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.SuperBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.dto.RequestDto
|
||||||
|
* <p>
|
||||||
|
* 공통 조회조건 요청 파라미터 dto
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/15
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/15 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@SuperBuilder
|
||||||
|
public class RequestDto {
|
||||||
|
private String keywordType; // 검색조건
|
||||||
|
private String keyword; // 검색어
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.egovframe.cloud.common.exception;
|
||||||
|
|
||||||
|
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.exception.BusinessException
|
||||||
|
* <p>
|
||||||
|
* 런타임시 비즈니스 로직상 사용자에게 알려줄 오류 메시지를 만들어 던지는 처리를 담당한다
|
||||||
|
* 이 클래스를 상속하여 다양한 형태의 business exception 을 만들 수 있고,
|
||||||
|
* 그것들은 모두 ExceptionHandlerAdvice BusinessException 처리 메소드에서 잡아낸다.
|
||||||
|
* 상황에 맞게 에러 코드를 추가하고 이 클래스를 상속하여 사용할 수 있다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/16
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/16 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class BusinessException extends RuntimeException {
|
||||||
|
|
||||||
|
private String customMessage;
|
||||||
|
private ErrorCode errorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 정의 메시지를 받아 처리하는 경우
|
||||||
|
*
|
||||||
|
* @param errorCode 400 에러
|
||||||
|
* @param customMessage 사용자에게 표시할 메시지
|
||||||
|
*/
|
||||||
|
public BusinessException(ErrorCode errorCode, String customMessage) {
|
||||||
|
super(customMessage);
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
this.customMessage = customMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사전 정의된 에러코드 객체를 넘기는 경우
|
||||||
|
*
|
||||||
|
* @param message 서버에 남길 메시지
|
||||||
|
* @param errorCode 사전 정의된 에러코드
|
||||||
|
*/
|
||||||
|
public BusinessException(String message, ErrorCode errorCode) {
|
||||||
|
super(message);
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사전 정의된 에러코드의 메시지를 서버에 남기고 에러코드 객체를 리턴한다
|
||||||
|
* @param errorCode 사전 정의된 에러코드
|
||||||
|
*/
|
||||||
|
public BusinessException(ErrorCode errorCode) {
|
||||||
|
super(errorCode.getMessage());
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorCode getErrorCode() {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomMessage() {
|
||||||
|
return customMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package org.egovframe.cloud.common.exception;
|
||||||
|
|
||||||
|
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.exception.BusinessMessageException
|
||||||
|
* <p>
|
||||||
|
* 런타임시 비즈니스 로직상 사용자에게 알려줄 오류 메시지를 만들어 던지는 처리를 담당한다
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/28
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/28 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class BusinessMessageException extends BusinessException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자에게 표시될 메시지와 상태코드 400 을 넘긴다
|
||||||
|
*
|
||||||
|
* @param customMessage
|
||||||
|
*/
|
||||||
|
public BusinessMessageException(String customMessage) {
|
||||||
|
super(ErrorCode.BUSINESS_CUSTOM_MESSAGE, customMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.egovframe.cloud.common.exception;
|
||||||
|
|
||||||
|
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.exception.EntityNotFoundException
|
||||||
|
* <p>
|
||||||
|
* 요청한 엔티티를 찾을 수 없을 경우 사용자에게 알려준다.
|
||||||
|
* ExceptionHandlerAdvice BusinessException 처리 메소드에서 잡아낸다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/16
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/16 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class EntityNotFoundException extends BusinessException {
|
||||||
|
|
||||||
|
public EntityNotFoundException(String message) {
|
||||||
|
super(message, ErrorCode.ENTITY_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.egovframe.cloud.common.exception;
|
||||||
|
|
||||||
|
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.exception.InvalidValueException
|
||||||
|
* <p>
|
||||||
|
* 입력 받은 값이 잘못된 경우 사용자에게 알려준다.
|
||||||
|
* ExceptionHandlerAdvice BusinessException 처리 메소드에서 잡아낸다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/16
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/16 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class InvalidValueException extends BusinessException {
|
||||||
|
|
||||||
|
public InvalidValueException(String value) {
|
||||||
|
super(value, ErrorCode.INVALID_INPUT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidValueException(String value, ErrorCode errorCode) {
|
||||||
|
super(value, errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.egovframe.cloud.common.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
|
||||||
|
JWT_EXPIRED(401, "E005", "err.unauthorized"), // The request requires an user authentication
|
||||||
|
ACCESS_DENIED(403, "E006", "err.access.denied"), // Forbidden, Access is Denied
|
||||||
|
NOT_FOUND(404, "E010", "err.page.not.found"), // Not found
|
||||||
|
METHOD_NOT_ALLOWED(405, "E011", "err.method.not.allowed"), // 요청 방법이 서버에 의해 알려졌으나, 사용 불가능한 상태
|
||||||
|
REQUIRE_USER_JOIN(412, "E012", "err.user.notexists"), // Server Error
|
||||||
|
UNPROCESSABLE_ENTITY(422, "E020", "err.unprocessable.entity"), // Unprocessable Entity
|
||||||
|
INTERNAL_SERVER_ERROR(500, "E999", "err.internal.server"), // Server Error
|
||||||
|
|
||||||
|
// business error code
|
||||||
|
BUSINESS_CUSTOM_MESSAGE(400, "B001", ""), // 사용자 정의 메시지를 넘기는 business exception
|
||||||
|
DUPLICATE_INPUT_INVALID(400, "B002", "err.duplicate.input.value"), // 중복된 값을 입력하였습니다
|
||||||
|
DB_CONSTRAINT_DELETE(400, "B003", "err.duplicate.input.value") // 참조하는 데이터가 있어서 삭제할 수 없습니다
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package org.egovframe.cloud.common.exception.dto;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static lombok.AccessLevel.PROTECTED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.exception.ErrorResponse
|
||||||
|
* <p>
|
||||||
|
* 일관된 예외처리를 제공하는 클래스
|
||||||
|
* https://github.com/cheese10yun/spring-guide
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jooho
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/16
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/16 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor(access = PROTECTED)
|
||||||
|
public class ErrorResponse {
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
private String message;
|
||||||
|
private int status;
|
||||||
|
private String code;
|
||||||
|
private List<FieldError> errors;
|
||||||
|
|
||||||
|
private static final String DEFAULT_ERROR_MESSAGE = "ERROR";
|
||||||
|
|
||||||
|
private ErrorResponse(final ErrorCode code, final List<FieldError> errors, MessageSource messageSource) {
|
||||||
|
this.timestamp = LocalDateTime.now();
|
||||||
|
this.message = messageSource.getMessage(code.getMessage(), new Object[]{}, DEFAULT_ERROR_MESSAGE, LocaleContextHolder.getLocale());
|
||||||
|
this.status = code.getStatus();
|
||||||
|
this.code = code.getCode();
|
||||||
|
this.errors = errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ErrorResponse(final ErrorCode code, MessageSource messageSource) {
|
||||||
|
this.timestamp = LocalDateTime.now();
|
||||||
|
this.message = messageSource.getMessage(code.getMessage(), new Object[]{}, DEFAULT_ERROR_MESSAGE, LocaleContextHolder.getLocale());
|
||||||
|
this.status = code.getStatus();
|
||||||
|
this.code = code.getCode();
|
||||||
|
this.errors = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ErrorResponse(final ErrorCode code, String customMessage) {
|
||||||
|
this.timestamp = LocalDateTime.now();
|
||||||
|
this.message = customMessage;
|
||||||
|
this.status = code.getStatus();
|
||||||
|
this.code = code.getCode();
|
||||||
|
this.errors = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 정의 메시지를 받아 넘기는 경우
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @param customMessage
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ErrorResponse of(final ErrorCode code, final String customMessage) {
|
||||||
|
return new ErrorResponse(code, customMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ErrorResponse 는 protected 를 선언하여 new 생성할 수 없도록 막아두고 static 메소드를 통해 생성할 수 있도록 하였다
|
||||||
|
* ExceptionHandlerAdvice 에서 인자를 받아 ErrorResponse 객체를 생성한다
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @param bindingResult
|
||||||
|
* @param messageSource
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ErrorResponse of(final ErrorCode code, final BindingResult bindingResult, MessageSource messageSource) {
|
||||||
|
return new ErrorResponse(code, FieldError.of(bindingResult), messageSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ErrorResponse 는 protected 를 선언하여 new 생성할 수 없도록 막아두고 static 메소드를 통해 생성할 수 있도록 하였다
|
||||||
|
* ExceptionHandlerAdvice 에서 인자를 받아 ErrorResponse 객체를 생성한다
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @param messageSource
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ErrorResponse of(final ErrorCode code, MessageSource messageSource) {
|
||||||
|
return new ErrorResponse(code, messageSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ErrorResponse 는 protected 를 선언하여 new 생성할 수 없도록 막아두고 static 메소드를 통해 생성할 수 있도록 하였다
|
||||||
|
* ExceptionHandlerAdvice 에서 인자를 받아 ErrorResponse 객체를 생성한다
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @param errors
|
||||||
|
* @param messageSource
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ErrorResponse of(final ErrorCode code, final List<FieldError> errors, MessageSource messageSource) {
|
||||||
|
return new ErrorResponse(code, errors, messageSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ErrorResponse 는 protected 를 선언하여 new 생성할 수 없도록 막아두고 static 메소드를 통해 생성할 수 있도록 하였다
|
||||||
|
* ExceptionHandlerAdvice 에서 인자를 받아 ErrorResponse 객체를 생성한다
|
||||||
|
* java validator 에러 발생 시 에러 정보 중 필요한 내용만 FieldError 로 반환한다
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ErrorResponse of(MethodArgumentTypeMismatchException e, MessageSource messageSource) {
|
||||||
|
final String value = e.getValue() == null ? "" : e.getValue().toString();
|
||||||
|
final List<ErrorResponse.FieldError> errors = ErrorResponse.FieldError.of(e.getName(), value, e.getErrorCode());
|
||||||
|
return new ErrorResponse(ErrorCode.INVALID_TYPE_VALUE, errors, messageSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* java validator 에러 발생 시 에러 정보 중 필요한 내용만 반환한다
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor(access = PROTECTED)
|
||||||
|
public static class FieldError {
|
||||||
|
private String message;
|
||||||
|
private String field;
|
||||||
|
private String rejectedValue;
|
||||||
|
|
||||||
|
private FieldError(final String field, final String rejectedValue, final String message) {
|
||||||
|
this.field = field;
|
||||||
|
this.rejectedValue = rejectedValue;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<FieldError> of(final String field, final String rejectedValue, final String message) {
|
||||||
|
List<FieldError> fieldErrors = new ArrayList<>();
|
||||||
|
fieldErrors.add(new FieldError(field, rejectedValue, message));
|
||||||
|
return fieldErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BindingResult to FieldError
|
||||||
|
*
|
||||||
|
* @param bindingResult
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static List<FieldError> of(final BindingResult bindingResult) {
|
||||||
|
final List<org.springframework.validation.FieldError> fieldErrors = bindingResult.getFieldErrors();
|
||||||
|
return fieldErrors.stream()
|
||||||
|
.map(error -> new FieldError(
|
||||||
|
error.getField(),
|
||||||
|
error.getRejectedValue() == null ? "" : error.getRejectedValue().toString(),
|
||||||
|
error.getDefaultMessage()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.egovframe.cloud.common.service;
|
||||||
|
|
||||||
|
import static org.egovframe.cloud.common.config.GlobalConstant.*;
|
||||||
|
|
||||||
|
import org.egovframe.cloud.common.dto.AttachmentEntityMessage;
|
||||||
|
import org.egovframe.cloud.common.util.MessageUtil;
|
||||||
|
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.cloud.stream.function.StreamBridge;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.service.AbstractService
|
||||||
|
* <p>
|
||||||
|
* 표준프레임워크 EgovAbstractServiceImpl 을 상속하는 공통 추상 클래스이다.
|
||||||
|
* 각 @Service 클래스는 이 클래스를 반드시 상속하여야 한다.(표준프레임워크 준수사항)
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/28 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
* @since 2021/07/28
|
||||||
|
*/
|
||||||
|
public abstract class AbstractService extends EgovAbstractServiceImpl {
|
||||||
|
|
||||||
|
@Resource(name = "messageUtil")
|
||||||
|
protected MessageUtil messageUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* messageSource 에 코드값을 넘겨 메시지를 찾아 리턴한다.
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected String getMessage(String code) {
|
||||||
|
return messageUtil.getMessage(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* messageSource 에 코드값과 인자를 넘겨 메시지를 찾아 리턴한다.
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @param args
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected String getMessage(String code, Object[] args) {
|
||||||
|
return messageUtil.getMessage(code, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 게시물 저장 후 해당 정보를 첨부파일 entity에 입력하기 위해
|
||||||
|
* 이벤트 메세지 발행
|
||||||
|
*
|
||||||
|
* @param entityMessage
|
||||||
|
*/
|
||||||
|
protected void sendAttachmentEntityInfo(StreamBridge streamBridge, AttachmentEntityMessage entityMessage) {
|
||||||
|
streamBridge.send(ATTACHMENT_ENTITY_BINDING_NAME,
|
||||||
|
MessageBuilder.withPayload(entityMessage).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package org.egovframe.cloud.common.util;
|
||||||
|
|
||||||
|
import org.egovframe.cloud.common.config.GlobalConstant;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.util.LogUtil
|
||||||
|
* <p>
|
||||||
|
* 로그인, 접속 로그 입력 시 필요한 정보 제공
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/09/02
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/09/02 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class LogUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 클라이언트 사용자의 IP 가져오기
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getUserIp() {
|
||||||
|
|
||||||
|
String ip = null;
|
||||||
|
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
|
||||||
|
|
||||||
|
ip = request.getHeader("X-Forwarded-For");
|
||||||
|
|
||||||
|
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("X-Real-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("X-RealIP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("REMOTE_ADDR");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 접속 사이트 정보를 넘긴다.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Long getSiteId(HttpServletRequest request) {
|
||||||
|
String header = request.getHeader(GlobalConstant.HEADER_SITE_ID);
|
||||||
|
if (!StringUtils.hasLength(header)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Long.valueOf(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package org.egovframe.cloud.common.util;
|
||||||
|
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.util.MessageUtil
|
||||||
|
* <p>
|
||||||
|
* MessageSource 값을 읽을 수 있는 메소드를 제공한다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/09/08
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/09/08 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class MessageUtil {
|
||||||
|
|
||||||
|
@Resource(name = "messageSource")
|
||||||
|
private MessageSource messageSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* messageSource 에 코드값을 넘겨 메시지를 찾아 리턴한다.
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getMessage(String code) {
|
||||||
|
return this.getMessage(code, new Object[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* messageSource 에 코드값과 인자를 넘겨 메시지를 찾아 리턴한다.
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @param args
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getMessage(String code, Object[] args) {
|
||||||
|
return this.getMessage(code, args, LocaleContextHolder.getLocale());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* messageSource 에 코드값, 인자, 지역정보를 넘겨 메시지를 찾아 리턴한다.
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @param args
|
||||||
|
* @param locale
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getMessage(String code, Object[] args, Locale locale) {
|
||||||
|
return messageSource.getMessage(code, args, locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package org.egovframe.cloud.reactive.config;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
|
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.reserveitemservice.config.AuthenticationConverter
|
||||||
|
*
|
||||||
|
* 요청을 authentiation으로 변환하는 클래스
|
||||||
|
* AuthenticationWebFilter에서 호출됨.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 shinmj
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/09/06
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/09/06 shinmj 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class AuthenticationConverter implements ServerAuthenticationConverter {
|
||||||
|
@Value("${token.secret}")
|
||||||
|
private String TOKEN_SECRET;
|
||||||
|
final String TOKEN_CLAIM_NAME = "authorities";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 요청에 담긴 토큰을 조회하여 Authentication 정보를 설정한다.
|
||||||
|
*
|
||||||
|
* @param exchange
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Mono<Authentication> convert(ServerWebExchange exchange) {
|
||||||
|
return Mono.justOrEmpty(exchange)
|
||||||
|
.flatMap(e -> Mono.justOrEmpty(e.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION)))
|
||||||
|
.flatMap(auth -> {
|
||||||
|
if (auth == null) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = auth.get(0);
|
||||||
|
if (!StringUtils.hasText(token) || "undefined".equals(token)) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Claims claims = getClaimsFromToken(token);
|
||||||
|
String authorities = claims.get(TOKEN_CLAIM_NAME, String.class);
|
||||||
|
List<SimpleGrantedAuthority> roleList = new ArrayList<>();
|
||||||
|
roleList.add(new SimpleGrantedAuthority(authorities));
|
||||||
|
|
||||||
|
String username = claims.getSubject();
|
||||||
|
|
||||||
|
if (username == null) {
|
||||||
|
ReactiveSecurityContextHolder.withAuthentication(null);
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
UsernamePasswordAuthenticationToken authenticationToken =
|
||||||
|
new UsernamePasswordAuthenticationToken(username, null, roleList);
|
||||||
|
|
||||||
|
ReactiveSecurityContextHolder.withAuthentication(authenticationToken);
|
||||||
|
return Mono.just(authenticationToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthenticationFilter.doFilter 메소드에서 UsernamePasswordAuthenticationToken 정보를 세팅할 때 호출된다.
|
||||||
|
*
|
||||||
|
* @param token
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Claims getClaimsFromToken(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.setSigningKey(TOKEN_SECRET)
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.egovframe.cloud.reactive.config;
|
||||||
|
|
||||||
|
import io.r2dbc.spi.ConnectionFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.data.r2dbc.config.EnableR2dbcAuditing;
|
||||||
|
import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator;
|
||||||
|
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
|
||||||
|
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.reserveitemservice.config.R2dbcConfig
|
||||||
|
*
|
||||||
|
* R2DBC configuration class
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 shinmj
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/09/06
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/09/06 shinmj 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Profile("!test")
|
||||||
|
@Configuration
|
||||||
|
@EnableR2dbcAuditing(auditorAwareRef = "userAuditAware") //auditing
|
||||||
|
public class R2dbcConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
|
||||||
|
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
|
||||||
|
initializer.setConnectionFactory(connectionFactory);
|
||||||
|
|
||||||
|
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
|
||||||
|
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
|
||||||
|
initializer.setDatabasePopulator(populator);
|
||||||
|
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package org.egovframe.cloud.reactive.config;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||||
|
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
|
||||||
|
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||||
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.reserveitemservice.config.SecurityConfig
|
||||||
|
*
|
||||||
|
* Spring Security Config 클래스
|
||||||
|
* AuthenticationFilter 를 추가하고 토큰으로 setAuthentication 인증처리를 한다
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 shinmj
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/09/06
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/09/06 shinmj 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Configuration
|
||||||
|
@EnableWebFluxSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final AuthenticationConverter authenticationConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reactive Security 설정
|
||||||
|
*
|
||||||
|
* @param http
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
|
||||||
|
return http
|
||||||
|
.csrf().disable()
|
||||||
|
.headers().frameOptions().disable()
|
||||||
|
.and()
|
||||||
|
.formLogin().disable()
|
||||||
|
.httpBasic().disable()
|
||||||
|
.logout().disable()
|
||||||
|
.addFilterAt(authenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthenticationManager
|
||||||
|
* Api Gateway에서 인증되기 때문에 따로 확인하지 않는다.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ReactiveAuthenticationManager authenticationManager() {
|
||||||
|
return authentication -> Mono.just(authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 인증 요청 필터
|
||||||
|
* AuthenticationConverter 를 적용하여 Authentication 정보를 설정한다.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public AuthenticationWebFilter authenticationWebFilter() throws Exception {
|
||||||
|
AuthenticationWebFilter filter = new AuthenticationWebFilter(authenticationManager());
|
||||||
|
filter.setServerAuthenticationConverter(authenticationConverter);
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.egovframe.cloud.reactive.config;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.ReactiveAuditorAware;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class UserAuditAware implements ReactiveAuditorAware<String> {
|
||||||
|
@Override
|
||||||
|
public Mono<String> getCurrentAuditor() {
|
||||||
|
return ReactiveSecurityContextHolder.getContext()
|
||||||
|
.map(SecurityContext::getAuthentication)
|
||||||
|
.filter(Authentication::isAuthenticated)
|
||||||
|
.map(Authentication::getPrincipal)
|
||||||
|
.map(String.class::cast);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.egovframe.cloud.reactive.domain;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
|
import org.springframework.data.annotation.LastModifiedBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.domain.BaseEntity
|
||||||
|
* <p>
|
||||||
|
* JPA Entity 클래스들이 BaseEntity 를 상속할 경우 createdBy, lastModifiedBy 필드들과
|
||||||
|
* BaseTimeEntity 필드들(createdDate, modifiedDate)까지 컬럼으로 인식된다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/06/30
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/06/30 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public abstract class BaseEntity extends BaseTimeEntity{
|
||||||
|
@CreatedBy
|
||||||
|
protected String createdBy;
|
||||||
|
|
||||||
|
@LastModifiedBy
|
||||||
|
protected String lastModifiedBy;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.egovframe.cloud.reactive.domain;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.domain.BaseTimeEntity
|
||||||
|
* <p>
|
||||||
|
* JPA Entity 클래스들이 BaseTimeEntity을 상속할 경우 필드들(createdDate, modifiedDate)도 컬럼으로 인식된다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/06/30
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/06/30 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public abstract class BaseTimeEntity {
|
||||||
|
@CreatedDate
|
||||||
|
protected LocalDateTime createDate;
|
||||||
|
|
||||||
|
@LastModifiedDate
|
||||||
|
protected LocalDateTime modifiedDate;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
package org.egovframe.cloud.reactive.exception;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.egovframe.cloud.common.exception.BusinessException;
|
||||||
|
import org.egovframe.cloud.common.exception.BusinessMessageException;
|
||||||
|
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||||
|
import org.egovframe.cloud.common.exception.dto.ErrorResponse;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
import org.webjars.NotFoundException;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class ExceptionHandlerAdvice {
|
||||||
|
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다.
|
||||||
|
* HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생
|
||||||
|
* 주로 @RequestBody, @RequestPart 어노테이션에서 발생
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
protected Mono<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||||
|
log.error("handleMethodArgumentNotValidException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||||
|
return Mono.just(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 바인딩 객체 @ModelAttribute 으로 binding error 발생시 BindException 발생한다.
|
||||||
|
* ref https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BindException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
protected Mono<ErrorResponse> handleBindException(BindException e) {
|
||||||
|
log.error("handleBindException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||||
|
return Mono.just(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 요청은 잘 만들어졌지만, 문법 오류로 인하여 따를 수 없습니다
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpClientErrorException.UnprocessableEntity.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
protected Mono<ErrorResponse> handleUnprocessableEntityException(HttpClientErrorException.UnprocessableEntity e) {
|
||||||
|
log.error("handleUnprocessableEntityException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.UNPROCESSABLE_ENTITY, messageSource);
|
||||||
|
return Mono.just(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* enum type 일치하지 않아 binding 못할 경우 발생
|
||||||
|
* 주로 @RequestParam enum으로 binding 못했을 경우 발생
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
protected Mono<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
|
||||||
|
log.error("handleMethodArgumentTypeMismatchException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(e, messageSource);
|
||||||
|
return Mono.just(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 요청한 페이지가 존재하지 않는 경우
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(NotFoundException.class)
|
||||||
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
|
protected Mono<ErrorResponse> handleNotFoundException(NotFoundException e) {
|
||||||
|
log.error("handleNotFoundException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.NOT_FOUND, messageSource);
|
||||||
|
return Mono.just(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication 객체가 필요한 권한을 보유하지 않은 경우 발생
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(AccessDeniedException.class)
|
||||||
|
protected Mono<ResponseEntity<ErrorResponse>> handleAccessDeniedException(AccessDeniedException e) {
|
||||||
|
log.error("handleAccessDeniedException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.ACCESS_DENIED, messageSource);
|
||||||
|
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()))
|
||||||
|
.body(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 인증되지 않은 경우 발생
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpClientErrorException.Unauthorized.class)
|
||||||
|
protected Mono<ResponseEntity<ErrorResponse>> handleUnauthorizedException(HttpClientErrorException.Unauthorized e) {
|
||||||
|
log.error("handleUnauthorizedException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.UNAUTHORIZED, messageSource);
|
||||||
|
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()))
|
||||||
|
.body(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 인증 만료
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(ExpiredJwtException.class)
|
||||||
|
protected Mono<ResponseEntity<ErrorResponse>> handleExpiredJwtException(ExpiredJwtException e) {
|
||||||
|
log.error("handleExpiredJwtException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.JWT_EXPIRED, messageSource);
|
||||||
|
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()))
|
||||||
|
.body(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자에게 표시할 다양한 메시지를 직접 정의하여 처리하는 Business RuntimeException Handler
|
||||||
|
* 개발자가 만들어 던지는 런타임 오류를 처리
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BusinessMessageException.class)
|
||||||
|
protected Mono<ResponseEntity<ErrorResponse>> handleBusinessMessageException(BusinessMessageException e) {
|
||||||
|
log.error("handleBusinessMessageException", e);
|
||||||
|
final ErrorCode errorCode = e.getErrorCode();
|
||||||
|
final String customMessage = e.getCustomMessage();
|
||||||
|
final ErrorResponse response = ErrorResponse.of(errorCode, customMessage);
|
||||||
|
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(errorCode.getStatus()))
|
||||||
|
.body(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 개발자 정의 ErrorCode 를 처리하는 Business RuntimeException Handler
|
||||||
|
* 개발자가 만들어 던지는 런타임 오류를 처리
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BusinessException.class)
|
||||||
|
protected Mono<ResponseEntity<ErrorResponse>> handleBusinessException(BusinessException e) {
|
||||||
|
log.error("handleBusinessException", e);
|
||||||
|
final ErrorCode errorCode = e.getErrorCode();
|
||||||
|
final ErrorResponse response = ErrorResponse.of(errorCode, messageSource);
|
||||||
|
return Mono.just(ResponseEntity.status(HttpStatus.valueOf(errorCode.getStatus()))
|
||||||
|
.body(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default exception
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
protected Mono<ErrorResponse> handleException(Exception e) {
|
||||||
|
log.error("handleException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR, messageSource);
|
||||||
|
return Mono.just(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다.
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(WebExchangeBindException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
protected Mono<ErrorResponse> handleWebExchangeBindException(WebExchangeBindException e) {
|
||||||
|
log.error("handleWebExchangeBindException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||||
|
return Mono.just(response);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.egovframe.cloud.reactive.service;
|
||||||
|
|
||||||
|
import org.egovframe.cloud.common.exception.EntityNotFoundException;
|
||||||
|
import org.egovframe.cloud.common.service.AbstractService;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public class ReactiveAbstractService extends AbstractService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mono error entity not found exception
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> Mono<T> monoResponseStatusEntityNotFoundException(Object id) {
|
||||||
|
return Mono.error( new EntityNotFoundException("해당 데이터가 존재하지 않습니다. ID =" + String.valueOf(id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package org.egovframe.cloud.servlet.config;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.config.AuthenticationFilter
|
||||||
|
* <p>
|
||||||
|
* Spring Security AuthenticationFilter 처리
|
||||||
|
* 로그인 인증정보를 받아 토큰을 발급한다
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/06/30
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/06/30 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||||
|
|
||||||
|
private final String TOKEN_SECRET;
|
||||||
|
|
||||||
|
final String TOKEN_CLAIM_NAME = "authorities";
|
||||||
|
|
||||||
|
public AuthenticationFilter(AuthenticationManager authenticationManager, String tokenSecret) {
|
||||||
|
super.setAuthenticationManager(authenticationManager);
|
||||||
|
this.TOKEN_SECRET = tokenSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthenticationFilter.doFilter 메소드에서 UsernamePasswordAuthenticationToken 정보를 세팅할 때 호출된다.
|
||||||
|
*
|
||||||
|
* @param token
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Claims getClaimsFromToken(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.setSigningKey(TOKEN_SECRET)
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 로그인 요청 뿐만 아니라 모든 요청시마다 호출된다.
|
||||||
|
* 토큰에 담긴 정보로 Authentication 정보를 설정한다.
|
||||||
|
* 이 처리를 하지 않으면 AnonymousAuthenticationToken 으로 처리된다.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @param chain
|
||||||
|
* @throws IOException
|
||||||
|
* @throws ServletException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
|
String token = httpRequest.getHeader(HttpHeaders.AUTHORIZATION);
|
||||||
|
if (token == null || "undefined".equals(token) || "".equals(token)) {
|
||||||
|
super.doFilter(request, response, chain);
|
||||||
|
} else {
|
||||||
|
Claims claims = getClaimsFromToken(token);
|
||||||
|
String authorities = claims.get(TOKEN_CLAIM_NAME, String.class);
|
||||||
|
List<SimpleGrantedAuthority> roleList = new ArrayList<>();
|
||||||
|
roleList.add(new SimpleGrantedAuthority(authorities));
|
||||||
|
|
||||||
|
String username = claims.getSubject();
|
||||||
|
if (username != null) {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(username, null, roleList));
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
} else {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(null);
|
||||||
|
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||||
|
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.egovframe.cloud.servlet.config;
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.config.JpaConfig
|
||||||
|
*
|
||||||
|
* JPA 설정 클래스
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jooho
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/07
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/07 jooho 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableJpaAuditing(auditorAwareRef = "userAuditAware") // JPA Auditing 활성화
|
||||||
|
@EnableJpaRepositories(basePackages = "org.egovframe.cloud.*.domain")
|
||||||
|
public class JpaConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JpaQueryFactory 빈 등록
|
||||||
|
*
|
||||||
|
* @param entityManager 엔티티 매니저
|
||||||
|
* @return JPAQueryFactory 쿼리 및 DML 절 생성을 위한 팩토리 클래스
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
|
||||||
|
return new JPAQueryFactory(entityManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.egovframe.cloud.servlet.config;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.AuditorAware;
|
||||||
|
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.config.UserAuditAware
|
||||||
|
* <p>
|
||||||
|
* JPA Entity 생성자/수정자 정보를 자동 입력한다.
|
||||||
|
* AuthenticationFilter.doFilter 메소드에서 UsernamePasswordAuthenticationToken 정보를 세팅해주기 때문에
|
||||||
|
* Authentication 에서 userId 값을 받아올 수 있게 된다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/08
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/08 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class UserAuditAware implements AuditorAware<String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auditing 기능이 활성화된 엔티티에 변경이 감지되면 호출되어 생성자/수정자 정보를 반환한다.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> getCurrentAuditor() {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (authentication == null || !authentication.isAuthenticated() || authentication instanceof AnonymousAuthenticationToken) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
String userId = authentication.getPrincipal() == null ? null : authentication.getPrincipal().toString();
|
||||||
|
return Optional.of(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package org.egovframe.cloud.servlet.config;
|
||||||
|
|
||||||
|
import org.egovframe.cloud.servlet.interceptor.ApiLogInterceptor;
|
||||||
|
import org.egovframe.cloud.servlet.service.ApiLogService;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.userservice.domain.BaseTimeEntity
|
||||||
|
* <p>
|
||||||
|
* WebMvc 관련 Configuration 클래스
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/05
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/05 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WebMvcConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
private final ApiLogService apiLogService;
|
||||||
|
|
||||||
|
public WebMvcConfig(ApiLogService apiLogService) {
|
||||||
|
this.apiLogService = apiLogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LocalValidatorFactoryBean 에 messageSource Bean 주입하여 validtor 메시지를 지원한다.
|
||||||
|
*
|
||||||
|
* @param messageSource messages.properties
|
||||||
|
* @return LocalValidatorFactoryBean
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
|
||||||
|
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
|
||||||
|
bean.setValidationMessageSource(messageSource);
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry
|
||||||
|
.addInterceptor(new ApiLogInterceptor(apiLogService))
|
||||||
|
.excludePathPatterns(
|
||||||
|
"/",
|
||||||
|
"/error",
|
||||||
|
"/favicon.ico",
|
||||||
|
"/api/v1/authorizations/check",
|
||||||
|
"/api/v1/users/token/refresh",
|
||||||
|
"/api/v1/menu-roles/**",
|
||||||
|
"/api/v1/messages/**"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package org.egovframe.cloud.servlet.domain;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
|
import org.springframework.data.annotation.LastModifiedBy;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.EntityListeners;
|
||||||
|
import javax.persistence.MappedSuperclass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.domain.BaseTimeEntity
|
||||||
|
* <p>
|
||||||
|
* JPA Entity 클래스들이 BaseEntity 를 상속할 경우 createdBy, lastModifiedBy 필드들과
|
||||||
|
* BaseTimeEntity 필드들(createdDate, modifiedDate)까지 컬럼으로 인식된다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/06/30
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/06/30 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@MappedSuperclass
|
||||||
|
@EntityListeners(AuditingEntityListener.class) // Auditing 기능 포함
|
||||||
|
public abstract class BaseEntity extends BaseTimeEntity {
|
||||||
|
|
||||||
|
@CreatedBy
|
||||||
|
@Column(updatable = false)
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
@LastModifiedBy
|
||||||
|
private String lastModifiedBy;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.egovframe.cloud.servlet.domain;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
import javax.persistence.EntityListeners;
|
||||||
|
import javax.persistence.MappedSuperclass;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.domain.BaseTimeEntity
|
||||||
|
* <p>
|
||||||
|
* JPA Entity 클래스들이 BaseTimeEntity을 상속할 경우 필드들(createdDate, modifiedDate)도 컬럼으로 인식된다.
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/06/30
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/06/30 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@MappedSuperclass
|
||||||
|
@EntityListeners(AuditingEntityListener.class) // Auditing 기능 포함
|
||||||
|
public abstract class BaseTimeEntity {
|
||||||
|
|
||||||
|
@CreatedDate
|
||||||
|
private LocalDateTime createdDate;
|
||||||
|
|
||||||
|
@LastModifiedDate
|
||||||
|
private LocalDateTime modifiedDate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package org.egovframe.cloud.servlet.domain.log;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.egovframe.cloud.servlet.domain.BaseTimeEntity;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
|
||||||
|
import static javax.persistence.GenerationType.IDENTITY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.domain.log.LoginLog
|
||||||
|
* <p>
|
||||||
|
* 로그인 로그 엔티티
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/09/01
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/09/01 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Entity
|
||||||
|
public class ApiLog extends BaseTimeEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = IDENTITY)
|
||||||
|
@Column(name = "log_id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long siteId;
|
||||||
|
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Column(length = 10)
|
||||||
|
private String httpMethod;
|
||||||
|
|
||||||
|
@Column(length = 500)
|
||||||
|
private String requestUrl;
|
||||||
|
|
||||||
|
@Column(name = "ip_addr", length = 100)
|
||||||
|
private String remoteIp;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public ApiLog(Long siteId, String userId, String httpMethod, String requestUrl, String remoteIp) {
|
||||||
|
this.siteId = siteId;
|
||||||
|
this.userId = userId;
|
||||||
|
this.httpMethod = httpMethod;
|
||||||
|
this.requestUrl = requestUrl;
|
||||||
|
this.remoteIp = remoteIp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.egovframe.cloud.servlet.domain.log;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.domain.log.ApiLogRepository
|
||||||
|
* <p>
|
||||||
|
* Spring Data JPA 에서 제공되는 JpaRepository 를 상속하는 인터페이스
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/09/01
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/09/01 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public interface ApiLogRepository extends JpaRepository<ApiLog, Long> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package org.egovframe.cloud.servlet.exception;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
|
import javassist.NotFoundException;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.egovframe.cloud.common.exception.BusinessException;
|
||||||
|
import org.egovframe.cloud.common.exception.BusinessMessageException;
|
||||||
|
import org.egovframe.cloud.common.exception.dto.ErrorCode;
|
||||||
|
import org.egovframe.cloud.common.exception.dto.ErrorResponse;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
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 org.springframework.web.client.HttpClientErrorException;
|
||||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.common.exception.WebControllerAdvice
|
||||||
|
* <p>
|
||||||
|
* 모든 컨트롤러에 적용되는 컨트롤러 어드바이스 클래스
|
||||||
|
* 예외 처리 (@ExceptionHandler), 바인딩 설정(@InitBinder), 모델 객체(@ModelAttributes)
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/07/15
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/07/15 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestControllerAdvice
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ExceptionHandlerAdvice {
|
||||||
|
|
||||||
|
protected final MessageSource messageSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* javax.validation.Valid or @Validated 으로 binding error 발생시 발생한다.
|
||||||
|
* HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생
|
||||||
|
* 주로 @RequestBody, @RequestPart 어노테이션에서 발생
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||||
|
log.error("handleMethodArgumentNotValidException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 바인딩 객체 @ModelAttribute 으로 binding error 발생시 BindException 발생한다.
|
||||||
|
* ref https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BindException.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleBindException(BindException e) {
|
||||||
|
log.error("handleBindException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult(), messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 요청은 잘 만들어졌지만, 문법 오류로 인하여 따를 수 없습니다
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpClientErrorException.UnprocessableEntity.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleUnprocessableEntityException(HttpClientErrorException.UnprocessableEntity e) {
|
||||||
|
log.error("handleUnprocessableEntityException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.UNPROCESSABLE_ENTITY, messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 지원하지 않은 HTTP method 호출 할 경우 발생
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
|
||||||
|
log.error("handleHttpRequestMethodNotSupportedException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.METHOD_NOT_ALLOWED, messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.METHOD_NOT_ALLOWED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* enum type 일치하지 않아 binding 못할 경우 발생
|
||||||
|
* 주로 @RequestParam enum으로 binding 못했을 경우 발생
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
|
||||||
|
log.error("handleMethodArgumentTypeMismatchException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(e, messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 요청한 페이지가 존재하지 않는 경우
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(NotFoundException.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleNotFoundException(NotFoundException e) {
|
||||||
|
log.error("handleNotFoundException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.NOT_FOUND, messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication 객체가 필요한 권한을 보유하지 않은 경우 발생
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(AccessDeniedException.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleAccessDeniedException(AccessDeniedException e) {
|
||||||
|
log.error("handleAccessDeniedException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.ACCESS_DENIED, messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 인증되지 않은 경우 발생
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpClientErrorException.Unauthorized.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleUnauthorizedException(HttpClientErrorException.Unauthorized e) {
|
||||||
|
log.error("handleUnauthorizedException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.UNAUTHORIZED, messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 인증 만료
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(ExpiredJwtException.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleExpiredJwtException(ExpiredJwtException e) {
|
||||||
|
log.error("handleExpiredJwtException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.JWT_EXPIRED, messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.ACCESS_DENIED.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자에게 표시할 다양한 메시지를 직접 정의하여 처리하는 Business RuntimeException Handler
|
||||||
|
* 개발자가 만들어 던지는 런타임 오류를 처리
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BusinessMessageException.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleBusinessMessageException(BusinessMessageException e) {
|
||||||
|
log.error("handleBusinessMessageException", e);
|
||||||
|
final ErrorCode errorCode = e.getErrorCode();
|
||||||
|
final String customMessage = e.getCustomMessage();
|
||||||
|
final ErrorResponse response = ErrorResponse.of(errorCode, customMessage);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.valueOf(errorCode.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 개발자 정의 ErrorCode 를 처리하는 Business RuntimeException Handler
|
||||||
|
* 개발자가 만들어 던지는 런타임 오류를 처리
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BusinessException.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
|
||||||
|
log.error("handleBusinessException", e);
|
||||||
|
final ErrorCode errorCode = e.getErrorCode();
|
||||||
|
final ErrorResponse response = ErrorResponse.of(errorCode, messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.valueOf(errorCode.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default exception
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @return ResponseEntity<ErrorResponse>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
protected ResponseEntity<ErrorResponse> handleException(Exception e) {
|
||||||
|
log.error("handleException", e);
|
||||||
|
final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR, messageSource);
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.egovframe.cloud.servlet.interceptor;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.egovframe.cloud.servlet.service.ApiLogService;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ApiLogInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private final ApiLogService apiLogService;
|
||||||
|
|
||||||
|
public ApiLogInterceptor(ApiLogService apiLogService) {
|
||||||
|
this.apiLogService = apiLogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
// 접근 로그 입력
|
||||||
|
apiLogService.saveApiLog(request);
|
||||||
|
log.info("[ApiLogInterceptor preHandle] {}, {}, {}", request.getMethod(), request.getRequestURI(), response.getStatus());
|
||||||
|
return HandlerInterceptor.super.preHandle(request, response, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||||
|
log.info("[ApiLogInterceptor postHandle] {}, {}, {}", request.getMethod(), request.getRequestURI(), response.getStatus());
|
||||||
|
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||||
|
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.egovframe.cloud.servlet.service;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.egovframe.cloud.servlet.domain.log.ApiLog;
|
||||||
|
import org.egovframe.cloud.common.service.AbstractService;
|
||||||
|
import org.egovframe.cloud.servlet.domain.log.ApiLogRepository;
|
||||||
|
import org.egovframe.cloud.common.util.LogUtil;
|
||||||
|
import org.egovframe.cloud.servlet.interceptor.ApiLogInterceptor;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* org.egovframe.cloud.servlet.service.ApiLogService
|
||||||
|
* <p>
|
||||||
|
* API Log 처리 서비스
|
||||||
|
*
|
||||||
|
* @author 표준프레임워크센터 jaeyeolkim
|
||||||
|
* @version 1.0
|
||||||
|
* @since 2021/09/01
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* << 개정이력(Modification Information) >>
|
||||||
|
*
|
||||||
|
* 수정일 수정자 수정내용
|
||||||
|
* ---------- -------- ---------------------------
|
||||||
|
* 2021/09/01 jaeyeolkim 최초 생성
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class ApiLogService extends AbstractService {
|
||||||
|
|
||||||
|
private final ApiLogRepository apiLogRepository;
|
||||||
|
|
||||||
|
public ApiLogService(ApiLogRepository apiLogRepository) {
|
||||||
|
this.apiLogRepository = apiLogRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API log 입력
|
||||||
|
* LogInterceptor 에서 호출된다
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @see ApiLogInterceptor
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void saveApiLog(HttpServletRequest request) {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
apiLogRepository.save(
|
||||||
|
ApiLog.builder()
|
||||||
|
.siteId(LogUtil.getSiteId(request))
|
||||||
|
.httpMethod(request.getMethod())
|
||||||
|
.requestUrl(request.getRequestURI())
|
||||||
|
.userId(authentication.getName())
|
||||||
|
.remoteIp(LogUtil.getUserIp())
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user