diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml
index e4e6473..ebf3069 100644
--- a/WebContent/WEB-INF/web.xml
+++ b/WebContent/WEB-INF/web.xml
@@ -48,6 +48,10 @@
authserver
org.springframework.web.servlet.DispatcherServlet
+
+ throwExceptionIfNoHandlerFound
+ true
+
1
diff --git a/WebContent/error.jsp b/WebContent/error.jsp
index 20cdde1..1363dfc 100644
--- a/WebContent/error.jsp
+++ b/WebContent/error.jsp
@@ -1,5 +1,26 @@
+<%@page import="com.eactive.eai.common.util.MessageUtil"%>
<%@ page import="java.io.*, java.util.*"%>
<%@ page language="java" contentType="text/html;charset=utf-8" isErrorPage="true"%>
+<%
+Integer statusCode = (Integer) request.getAttribute("_authServerStatusCode");
+if (statusCode != null) {
+ response.setStatus(statusCode);
+}
+
+String code;
+if (statusCode != null && statusCode == 401) {
+ code = MessageUtil.ERROR_CODE_AUTH_FAIL;
+} else if (statusCode != null && statusCode == 404) {
+ code = MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND;
+} else {
+ code = MessageUtil.ERROR_CODE_AP_ERROR;
+}
+
+String message = (String)request.getAttribute("errorMessage");
+String jsonErrorMessage = MessageUtil.makeJsonErrorMessage(code, message);
+out.println(jsonErrorMessage);
+%>
+<%--
Error
@@ -16,3 +37,4 @@
<% response.setStatus(200); %>
+ --%>
\ No newline at end of file
diff --git a/elink-online-common b/elink-online-common
index aacc1a3..5274ce8 160000
--- a/elink-online-common
+++ b/elink-online-common
@@ -1 +1 @@
-Subproject commit aacc1a389e145453cf92c18e9d7ffef568cf89e7
+Subproject commit 5274ce811d05e63936057a0bece1ca9cf620f0cc
diff --git a/kjb-errors/.gitignore b/kjb-errors/.gitignore
new file mode 100644
index 0000000..08193f7
--- /dev/null
+++ b/kjb-errors/.gitignore
@@ -0,0 +1,32 @@
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
diff --git a/kjb-errors/build.gradle b/kjb-errors/build.gradle
new file mode 100644
index 0000000..a987ef4
--- /dev/null
+++ b/kjb-errors/build.gradle
@@ -0,0 +1,29 @@
+plugins {
+ id 'java'
+}
+
+group = 'com.eactive.eai'
+version = '1.0.0'
+
+java {
+ sourceCompatibility = '17'
+}
+
+tasks.withType(JavaCompile) {
+ options.encoding = 'UTF-8'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
+ implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
+
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
+}
+
+tasks.named('test') {
+ useJUnitPlatform()
+}
diff --git a/kjb-errors/gradle/wrapper/gradle-wrapper.jar b/kjb-errors/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..41d9927
Binary files /dev/null and b/kjb-errors/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/kjb-errors/gradle/wrapper/gradle-wrapper.properties b/kjb-errors/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..54228ea
--- /dev/null
+++ b/kjb-errors/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+networkTimeout=10000
diff --git a/kjb-errors/gradlew b/kjb-errors/gradlew
new file mode 100644
index 0000000..1b6c787
--- /dev/null
+++ b/kjb-errors/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${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 "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# 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 ;; #(
+ MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/kjb-errors/gradlew.bat b/kjb-errors/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/kjb-errors/gradlew.bat
@@ -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
diff --git a/kjb-errors/settings.gradle b/kjb-errors/settings.gradle
new file mode 100644
index 0000000..045fcf4
--- /dev/null
+++ b/kjb-errors/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'kjb-errors'
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/BearerTokenErrorTest.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/BearerTokenErrorTest.java
new file mode 100644
index 0000000..7edd2f9
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/BearerTokenErrorTest.java
@@ -0,0 +1,168 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class BearerTokenErrorTest {
+
+ @Test
+ void testCase001_잘못된GrantType() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/auth/oauth/v2/token");
+
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("grant_type", "password"));
+ params.add(new BasicNameValuePair("client_id", "AyeWx0OpYLJbHpZ21DDSxYfQPmD5Ckn8"));
+ params.add(new BasicNameValuePair("client_secret", "KwBYJT1m1v3SUvzN2HPHhy2Ad9Q2GADbgWkYZZg9Ybbc8R1uEuBJNQ9oVl13b1qiXwYvDWTaX0EanxY4SMjNfnBBsZzfNoMwwDPTb3IjQy5k3CRxTAYzcSnmzlC4FSsG"));
+ params.add(new BasicNameValuePair("scope", "api"));
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(401, response.getCode());
+ return body;
+ });
+
+ // MessageUtil.makeJsonErrorMessage 기본 포맷 검증
+ ErrorResponseValidator.validateErrorFormat(responseBody);
+ }
+ }
+
+ @Test
+ void testCase002_존재하지않는ClientId() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/auth/oauth/v2/token");
+
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("grant_type", "client_credentials"));
+ params.add(new BasicNameValuePair("client_id", "INVALID_CLIENT_ID"));
+ params.add(new BasicNameValuePair("client_secret", "invalid_secret"));
+ params.add(new BasicNameValuePair("scope", "api"));
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(401, response.getCode());
+ return body;
+ });
+
+ // MessageUtil.makeJsonErrorMessage 기본 포맷 검증
+ ErrorResponseValidator.validateErrorFormat(responseBody);
+ }
+ }
+
+ @Test
+ void testCase003_잘못된ClientSecret() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/auth/oauth/v2/token");
+
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("grant_type", "client_credentials"));
+ params.add(new BasicNameValuePair("client_id", "AyeWx0OpYLJbHpZ21DDSxYfQPmD5Ckn8"));
+ params.add(new BasicNameValuePair("client_secret", "WRONG_SECRET"));
+ params.add(new BasicNameValuePair("scope", "api"));
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(401, response.getCode());
+ return body;
+ });
+
+ // MessageUtil.makeJsonErrorMessage 기본 포맷 검증
+ ErrorResponseValidator.validateErrorFormat(responseBody);
+ }
+ }
+
+ @Test
+ void testCase004_Scope없음() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/auth/oauth/v2/token");
+
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("grant_type", "client_credentials"));
+ params.add(new BasicNameValuePair("client_id", "AyeWx0OpYLJbHpZ21DDSxYfQPmD5Ckn8"));
+ params.add(new BasicNameValuePair("client_secret", "KwBYJT1m1v3SUvzN2HPHhy2Ad9Q2GADbgWkYZZg9Ybbc8R1uEuBJNQ9oVl13b1qiXwYvDWTaX0EanxY4SMjNfnBBsZzfNoMwwDPTb3IjQy5k3CRxTAYzcSnmzlC4FSsG"));
+ params.add(new BasicNameValuePair("scope", ""));
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(401, response.getCode());
+ return body;
+ });
+
+ // MessageUtil.makeJsonErrorMessage 기본 포맷 검증
+ ErrorResponseValidator.validateErrorFormat(responseBody);
+ }
+ }
+
+ @Test
+ void testCase005_허용되지않은Scope() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/auth/oauth/v2/token");
+
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("grant_type", "client_credentials"));
+ params.add(new BasicNameValuePair("client_id", "AyeWx0OpYLJbHpZ21DDSxYfQPmD5Ckn8"));
+ params.add(new BasicNameValuePair("client_secret", "KwBYJT1m1v3SUvzN2HPHhy2Ad9Q2GADbgWkYZZg9Ybbc8R1uEuBJNQ9oVl13b1qiXwYvDWTaX0EanxY4SMjNfnBBsZzfNoMwwDPTb3IjQy5k3CRxTAYzcSnmzlC4FSsG"));
+ params.add(new BasicNameValuePair("scope", "invalid_scope"));
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(401, response.getCode());
+ return body;
+ });
+
+ // MessageUtil.makeJsonErrorMessage 기본 포맷 검증
+ ErrorResponseValidator.validateErrorFormat(responseBody);
+ }
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/BearerTokenFilterErrorTest.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/BearerTokenFilterErrorTest.java
new file mode 100644
index 0000000..d2db920
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/BearerTokenFilterErrorTest.java
@@ -0,0 +1,41 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.junit.jupiter.api.Test;
+
+class BearerTokenFilterErrorTest {
+
+ @Test
+ void testCase001_잘못된Bearer토큰_401에러() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/api/auth/case009?transactionId=1234567890");
+ String invalidToken = "INVALID_TOKEN_12345";
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setHeader("Authorization", "Bearer " + invalidToken);
+ httpPost.setHeader("Content-Type", "application/json");
+ httpPost.setEntity(new StringEntity("{}", ContentType.APPLICATION_JSON));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(401, response.getCode());
+ return body;
+ });
+
+ // MessageUtil.makeJsonErrorMessage 기본 포맷 검증
+ ErrorResponseValidator.validateErrorFormat(responseBody);
+ }
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/BearerTokenSuccessTest.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/BearerTokenSuccessTest.java
new file mode 100644
index 0000000..7911a91
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/BearerTokenSuccessTest.java
@@ -0,0 +1,67 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class BearerTokenSuccessTest {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ void testCase001_BearerToken발급성공() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/auth/oauth/v2/token");
+
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("grant_type", "client_credentials"));
+ params.add(new BasicNameValuePair("client_id", "AyeWx0OpYLJbHpZ21DDSxYfQPmD5Ckn8"));
+ params.add(new BasicNameValuePair("client_secret", "KwBYJT1m1v3SUvzN2HPHhy2Ad9Q2GADbgWkYZZg9Ybbc8R1uEuBJNQ9oVl13b1qiXwYvDWTaX0EanxY4SMjNfnBBsZzfNoMwwDPTb3IjQy5k3CRxTAYzcSnmzlC4FSsG"));
+ params.add(new BasicNameValuePair("scope", "api"));
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(200, response.getCode());
+ return body;
+ });
+
+ // 응답 검증
+ JsonNode jsonNode = objectMapper.readTree(responseBody);
+ assertNotNull(jsonNode, "JSON 응답이 null입니다");
+
+ // Bearer Token 응답 필드 확인
+ assertNotNull(jsonNode.get("access_token"), "access_token 필드가 없습니다");
+ assertNotNull(jsonNode.get("token_type"), "token_type 필드가 없습니다");
+ assertNotNull(jsonNode.get("expires_in"), "expires_in 필드가 없습니다");
+ assertNotNull(jsonNode.get("scope"), "scope 필드가 없습니다");
+
+ assertEquals("Bearer", jsonNode.get("token_type").asText());
+
+ String prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
+ System.out.println("Bearer Token Response:");
+ System.out.println(prettyJson);
+ }
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/EAPIM_ErrorResponse_WorkHistory.md b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/EAPIM_ErrorResponse_WorkHistory.md
new file mode 100644
index 0000000..0a31f5f
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/EAPIM_ErrorResponse_WorkHistory.md
@@ -0,0 +1,218 @@
+# EAPIM 에러 응답 포맷 통일 작업 히스토리
+
+## 작업 배경
+
+### 목표
+모든 HTTP 에러 응답을 다음과 같은 표준 JSON 포맷으로 통일:
+```json
+{"error":{"code":"에러코드","message":"에러메시지"}}
+```
+
+## 변경 내역
+
+### 1. MessageUtil 에러 포맷 정의
+**파일**: `elink-online-common/src/main/java/com/eactive/eai/common/util/MessageUtil.java`
+
+- 표준 에러 응답 포맷 정의
+ ```java
+ public static final String ERROR_MESSAGE_DEFAULT_FORMAT = "{\"error\":{\"code\":\"%s\",\"message\":\"%s\"}}";
+ ```
+
+- 에러 코드 상수 추가
+ ```java
+ public static final String ERROR_CODE_AP_ERROR = "E.GW.AP_ERROR";
+ public static final String ERROR_CODE_AUTH_FAIL = "E.GW.AUTH_FAIL";
+ public static final String ERROR_CODE_SERVICE_NOT_FOUND = "E.GW.SERVICE_NOT_FOUND";
+ ```
+
+- 에러 메시지 생성 메서드
+ ```java
+ public static String makeJsonErrorMessage(String code, String msg)
+ public static String makeJsonErrorMessage(String code, String msg, String errorFormat)
+ ```
+
+### 2. 전역 ExceptionHandler 추가
+**파일**: `src/main/java/com/eactive/eai/authserver/config/WebMvcConfig.java` (신규 생성)
+
+Spring MVC 전역 예외 처리를 위한 `@ControllerAdvice` 클래스 추가
+
+**처리하는 예외**:
+- `HttpMessageNotReadableException` - HTTP 메시지 파싱 실패
+- `MethodArgumentNotValidException` - 유효성 검증 실패
+- `MissingServletRequestParameterException` - 필수 파라미터 누락
+- `BindException` - 바인딩 에러
+- `NoHandlerFoundException` - 404 Not Found (핸들러 없음)
+- `AuthenticationException` - 401 Unauthorized (인증 실패)
+- `AccessDeniedException` - 403 Forbidden (권한 없음)
+- `Exception` - 500 Internal Server Error (기타 예외)
+
+**동작 방식**:
+1. 예외 발생 시 적절한 HTTP 상태 코드 설정
+2. request에 `_authServerStatusCode`, `errorMessage` 속성 설정
+3. `/error.jsp`로 forward하여 표준 JSON 응답 생성
+
+### 3. error.jsp 수정
+**파일**: `WebContent/error.jsp`
+
+**변경 사항**:
+- 기존 HTML 에러 페이지를 JSON 응답으로 변경
+- `MessageUtil.makeJsonErrorMessage()` 호출하여 표준 포맷 응답 생성
+- HTTP 상태 코드에 따른 에러 코드 매핑:
+ - 401 → `ERROR_CODE_AUTH_FAIL`
+ - 404 → `ERROR_CODE_SERVICE_NOT_FOUND`
+ - 기타 → `ERROR_CODE_AP_ERROR`
+
+```jsp
+Integer statusCode = (Integer) request.getAttribute("_authServerStatusCode");
+if (statusCode != null) {
+ response.setStatus(statusCode);
+}
+
+String code;
+if (statusCode != null && statusCode == 401) {
+ code = MessageUtil.ERROR_CODE_AUTH_FAIL;
+} else if (statusCode != null && statusCode == 404) {
+ code = MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND;
+} else {
+ code = MessageUtil.ERROR_CODE_AP_ERROR;
+}
+
+String message = (String)request.getAttribute("errorMessage");
+String jsonErrorMessage = MessageUtil.makeJsonErrorMessage(code, message);
+out.println(jsonErrorMessage);
+```
+
+### 4. WebSecurityConfig 에러 처리 추가
+**파일**: `src/main/java/com/eactive/eai/authserver/config/WebSecurityConfig.java`
+
+**변경 사항**:
+- Spring Security 레벨의 인증/권한 에러 처리 추가
+- `authenticationEntryPoint`: 401 Unauthorized → error.jsp 포워딩
+- `accessDeniedHandler`: 403 Forbidden → error.jsp 포워딩
+- `/error.jsp` 접근 허용 설정 추가
+
+### 5. web.xml 설정 추가
+**파일**: `WebContent/WEB-INF/web.xml`
+
+**변경 사항**:
+- DispatcherServlet에 `throwExceptionIfNoHandlerFound=true` 설정 추가
+ ```xml
+
+ throwExceptionIfNoHandlerFound
+ true
+
+ ```
+ → 핸들러 없을 시 `NoHandlerFoundException` 발생하도록 설정
+
+- error-page 매핑 (400, 401, 403, 404, 500 등)
+ ```xml
+
+ 404
+ /error.jsp
+
+ ```
+ → Servlet 레벨에서 발생한 에러도 error.jsp로 라우팅
+
+### 6. KjbMGOAuth2Controller 에러 처리 적용
+**파일**: `src/main/java/com/eactive/eai/authserver/custom/KjbMGOAuth2Controller.java`
+
+**변경 사항**:
+- `JwtAuthException`, 일반 예외 처리 시 `MessageUtil.makeJsonErrorMessage()` 사용
+- HTTP 400/401 에러를 표준 포맷으로 반환
+
+### 7. BearerTokenContoller 에러 처리 적용
+**파일**: `src/main/java/com/eactive/eai/authserver/custom/BearerTokenContoller.java`
+
+**변경 사항**:
+- `MessageUtil` import 추가
+- 기존 OAuth2 RFC 6749 에러 포맷을 MessageUtil 표준 포맷으로 변경
+- `JwtAuthException`: HTTP 401 + `ERROR_CODE_AUTH_FAIL`
+- 일반 예외: HTTP 500 + `ERROR_CODE_AP_ERROR`
+
+### 8. BearerTokenFilter 에러 처리 적용
+**파일**: `src/main/java/com/eactive/eai/authserver/custom/BearerTokenFilter.java`
+
+**변경 사항**:
+- `MessageUtil` import 추가
+- 필터에서 발생하는 에러 응답을 MessageUtil 표준 포맷으로 변경
+- `JwtAuthException`: HTTP 401 + `ERROR_CODE_AUTH_FAIL`
+- 일반 예외: HTTP 500 + `ERROR_CODE_AP_ERROR`
+- JSONObject 직접 생성 방식 제거
+
+### 9. ApiAdapterController 에러 처리 적용
+**파일**: `src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java`
+
+**변경 사항**:
+- Adapter URI를 찾을 수 없는 경우: `ERROR_CODE_SERVICE_NOT_FOUND`
+- Adapter를 찾을 수 없는 경우: `ERROR_CODE_AP_ERROR`
+
+## 적용 범위
+
+### 처리되는 에러 시나리오
+
+1. **OAuth2 토큰 발급 실패** (`/mapi/oauth2/token`)
+ - 필수 파라미터 누락, 잘못된 grant_type, 인증 실패 등
+ - HTTP 400/401 + 표준 JSON 에러 응답
+
+2. **CA Bearer 토큰 발급 실패** (`/auth/oauth/v2/token`)
+ - 잘못된 grant_type, 존재하지 않는 client_id, 잘못된 client_secret, scope 오류 등
+ - HTTP 401/500 + 표준 JSON 에러 응답
+
+3. **Bearer 토큰 인증 실패** (`/api/auth/*`)
+ - 토큰 만료, 잘못된 토큰 등
+ - HTTP 401 + 표준 JSON 에러 응답
+
+4. **404 Not Found**
+ - 존재하지 않는 API 엔드포인트 호출
+ - HTTP 404 + 표준 JSON 에러 응답
+
+5. **400 Bad Request**
+ - 잘못된 JSON 포맷, 필수 파라미터 누락 등
+ - HTTP 400 + 표준 JSON 에러 응답
+
+6. **403 Forbidden**
+ - 권한 없는 리소스 접근
+ - HTTP 403 + 표준 JSON 에러 응답
+
+7. **500 Internal Server Error**
+ - 서버 내부 오류
+ - HTTP 500 + 표준 JSON 에러 응답
+
+## 테스트
+
+테스트 코드는 별도 모듈(`kjb-errors`)에 작성됨.
+
+**테스트 문서**: `kjb-errors/src/test/java/com/eactive/eai/kjb/errors/EAPIM_ExceptionHandler_Test.md`
+
+**주요 테스트**:
+- `OAuth2TokenSuccessTest` - OAuth2 토큰 발급 성공
+- `OAuth2BearerAuthTest` - Bearer 토큰 인증 성공
+- `OAuth2BearerAuth404ErrorTest` - Bearer 토큰 인증 후 404 에러
+- `ExceptionHandler400ErrorTest` - 400 에러 응답 검증
+- `ExceptionHandler404ErrorTest` - 404 에러 응답 검증
+- `ExceptionHandlerSuccessTest` - 일반 API 정상 응답
+- `BearerTokenSuccessTest` - CA Bearer 토큰 발급 성공
+- `BearerTokenErrorTest` - CA Bearer 토큰 발급 에러 (5가지 케이스)
+- `BearerTokenFilterErrorTest` - Bearer 토큰 필터 인증 실패
+
+**검증 항목**:
+- HTTP 상태 코드 정확성
+- 응답 포맷이 `{"error":{"code":"...","message":"..."}}` 형식인지 확인
+- 에러 코드가 적절히 매핑되는지 확인
+
+## 에러 코드 매핑
+
+| HTTP Status | Error Code | 설명 |
+|-------------|-----------|------|
+| 400 | E.GW.AP_ERROR | Bad Request (잘못된 요청) |
+| 401 | E.GW.AUTH_FAIL | Unauthorized (인증 실패) |
+| 403 | E.GW.AP_ERROR | Forbidden (권한 없음) |
+| 404 | E.GW.SERVICE_NOT_FOUND | Not Found (서비스 없음) |
+| 500 | E.GW.AP_ERROR | Internal Server Error (서버 오류) |
+
+## 참고 사항
+
+### 주의사항
+- `MessageUtil.makeJsonErrorMessage()`는 메시지 내의 특수문자를 JSON escape 처리함
+- error.jsp는 JSON 응답만 출력하므로 브라우저에서 HTML로 보이지 않음
+- 상태 코드는 실제 HTTP 응답 상태 코드와 일치해야 함
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/EAPIM_ExceptionHandler_Test.md b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/EAPIM_ExceptionHandler_Test.md
new file mode 100644
index 0000000..d0e7546
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/EAPIM_ExceptionHandler_Test.md
@@ -0,0 +1,99 @@
+# EAPIM ExceptionHandler 테스트
+
+## 개요
+에러 응답이 `MessageUtil.makeJsonErrorMessage()` 포맷으로 통일되는지 검증
+
+**에러 응답 포맷**:
+```json
+{"error":{"code":"에러코드","message":"에러메시지"}}
+```
+
+## 테스트 케이스
+
+### OAuth2 토큰 발급 성공 (OAuth2TokenSuccessTest)
+- **URL**: `http://127.0.0.1:30110/oauth/token`
+- **Content-Type**: `application/x-www-form-urlencoded`
+- **파라미터**: grant_type, client_id, client_secret, scope
+- **테스트 대상**: AuthorizationServerConfig 표준 OAuth2 토큰 발급
+- **검증**: HTTP 200, access_token, token_type, expires_in
+
+### Bearer 토큰 인증 성공 (OAuth2BearerAuthTest)
+- **Step 1**: 토큰 발급 (`/oauth/token`)
+- **Step 2**: Bearer 토큰으로 API 호출 (POST)
+- **URL**: `http://127.0.0.1:30110/api/auth/case009?transactionId=1234567890`
+- **Authorization**: `Bearer {access_token}`
+- **요청**: `{}`
+- **테스트 대상**: Bearer 토큰 인증 API
+- **검증**: HTTP 200, 정상 응답
+
+### Bearer 토큰 인증 404 에러 (OAuth2BearerAuth404ErrorTest)
+- **Step 1**: 토큰 발급 (`/oauth/token`)
+- **Step 2**: 존재하지 않는 API 호출 (POST)
+- **URL**: `http://127.0.0.1:30110/api/auth/caseXXX`
+- **Authorization**: `Bearer {access_token}`
+- **요청**: `{}`
+- **테스트 대상**: WebMvcConfig @ExceptionHandler(NoHandlerFoundException)
+- **검증**: HTTP 404, MessageUtil 포맷
+
+### 400 에러 (ExceptionHandler400ErrorTest)
+- **URL**: `http://127.0.0.1:30110/mapi/oauth2/token`
+- **요청**: `{}`
+- **테스트 대상**: KjbMGOAuth2Controller JwtAuthException 처리
+- **검증**: HTTP 400, MessageUtil 포맷
+
+### 404 에러 (ExceptionHandler404ErrorTest)
+- **URL**: `http://127.0.0.1:30110/oauth/token/v2`
+- **요청**: `{}`
+- **테스트 대상**: WebMvcConfig @ExceptionHandler(NoHandlerFoundException)
+- **검증**: HTTP 404, MessageUtil 포맷
+
+### 일반 API 정상 응답 (ExceptionHandlerSuccessTest)
+- **URL**: `http://localhost:30110/api/test/case001`
+- **요청**: `{"serviceId": "TST10001", "aaa": "aaaa", "bbb": "bbbb"}`
+- **테스트 대상**: 일반 컨트롤러 정상 응답
+- **검증**: HTTP 200, transactionId, status="approved"
+
+### Bearer 토큰 발급 성공 (BearerTokenSuccessTest)
+- **URL**: `http://127.0.0.1:30110/auth/oauth/v2/token`
+- **Content-Type**: `application/x-www-form-urlencoded`
+- **파라미터**: grant_type, client_id, client_secret, scope
+- **테스트 대상**: BearerTokenContoller CA Bearer Token 발급
+- **검증**: HTTP 200, access_token, token_type="Bearer", expires_in, scope
+
+### Bearer 토큰 발급 에러 (BearerTokenErrorTest)
+#### testCase001_잘못된GrantType
+- **URL**: `http://127.0.0.1:30110/auth/oauth/v2/token`
+- **grant_type**: `password` (잘못된 값)
+- **테스트 대상**: BearerTokenContoller JwtAuthException 처리
+- **검증**: HTTP 401, MessageUtil 포맷
+
+#### testCase002_존재하지않는ClientId
+- **URL**: `http://127.0.0.1:30110/auth/oauth/v2/token`
+- **client_id**: `INVALID_CLIENT_ID`
+- **테스트 대상**: BearerTokenContoller JwtAuthException 처리
+- **검증**: HTTP 401, MessageUtil 포맷
+
+#### testCase003_잘못된ClientSecret
+- **URL**: `http://127.0.0.1:30110/auth/oauth/v2/token`
+- **client_secret**: `WRONG_SECRET`
+- **테스트 대상**: BearerTokenContoller JwtAuthException 처리
+- **검증**: HTTP 401, MessageUtil 포맷
+
+#### testCase004_Scope없음
+- **URL**: `http://127.0.0.1:30110/auth/oauth/v2/token`
+- **scope**: `` (빈 값)
+- **테스트 대상**: BearerTokenContoller JwtAuthException 처리
+- **검증**: HTTP 401, MessageUtil 포맷
+
+#### testCase005_허용되지않은Scope
+- **URL**: `http://127.0.0.1:30110/auth/oauth/v2/token`
+- **scope**: `invalid_scope`
+- **테스트 대상**: BearerTokenContoller JwtAuthException 처리
+- **검증**: HTTP 401, MessageUtil 포맷
+
+### Bearer 토큰 필터 에러 (BearerTokenFilterErrorTest)
+- **URL**: `http://127.0.0.1:30110/api/auth/case009?transactionId=1234567890`
+- **Authorization**: `Bearer INVALID_TOKEN_12345`
+- **요청**: `{}`
+- **테스트 대상**: BearerTokenFilter JwtAuthException 처리
+- **검증**: HTTP 401, MessageUtil 포맷
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ErrorResponseValidator.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ErrorResponseValidator.java
new file mode 100644
index 0000000..e249dbe
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ErrorResponseValidator.java
@@ -0,0 +1,42 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * MessageUtil.makeJsonErrorMessage 기본 포맷 검증 유틸리티
+ * 기본 포맷: { "error" : {"code":"%s","message":"%s"} }
+ */
+public class ErrorResponseValidator {
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * MessageUtil.makeJsonErrorMessage 포맷 검증
+ * @param responseBody JSON 응답 문자열
+ * @throws Exception JSON 파싱 실패 시
+ */
+ public static void validateErrorFormat(String responseBody) throws Exception {
+ JsonNode jsonNode = objectMapper.readTree(responseBody);
+ assertNotNull(jsonNode, "JSON 응답이 null입니다");
+
+ assertTrue(jsonNode.has("error"), "error 필드가 존재해야 합니다");
+
+ JsonNode errorNode = jsonNode.get("error");
+ assertNotNull(errorNode, "error 객체가 null입니다");
+
+ assertTrue(errorNode.has("code"), "error.code 필드가 존재해야 합니다");
+ assertTrue(errorNode.has("message"), "error.message 필드가 존재해야 합니다");
+
+ assertNotNull(errorNode.get("code").asText(), "error.code 값이 null입니다");
+ assertNotNull(errorNode.get("message").asText(), "error.message 값이 null입니다");
+
+ // 검증 성공 시 Pretty Print
+ String prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
+ System.out.println("Validated Error Format:");
+ System.out.println(prettyJson);
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ExceptionHandler400ErrorTest.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ExceptionHandler400ErrorTest.java
new file mode 100644
index 0000000..bdbc547
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ExceptionHandler400ErrorTest.java
@@ -0,0 +1,44 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.junit.jupiter.api.Test;
+
+class ExceptionHandler400ErrorTest {
+
+ @Test
+ void testCase001_400에러() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/mapi/oauth2/token");
+
+ String requestBody = """
+ {}
+ """;
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setHeader("Content-Type", "application/json");
+ httpPost.setHeader("Accept", "application/json");
+ httpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(400, response.getCode());
+ return body;
+ });
+
+ // MessageUtil.makeJsonErrorMessage 기본 포맷 검증
+ ErrorResponseValidator.validateErrorFormat(responseBody);
+ }
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ExceptionHandler404ErrorTest.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ExceptionHandler404ErrorTest.java
new file mode 100644
index 0000000..a7e6532
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ExceptionHandler404ErrorTest.java
@@ -0,0 +1,44 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.junit.jupiter.api.Test;
+
+class ExceptionHandler404ErrorTest {
+
+ @Test
+ void testCase001_404에러() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/oauth/token/v2");
+
+ String requestBody = """
+ {}
+ """;
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setHeader("Content-Type", "application/json");
+ httpPost.setHeader("Accept", "application/json");
+ httpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(404, response.getCode());
+ return body;
+ });
+
+ // MessageUtil.makeJsonErrorMessage 기본 포맷 검증
+ ErrorResponseValidator.validateErrorFormat(responseBody);
+ }
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ExceptionHandlerSuccessTest.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ExceptionHandlerSuccessTest.java
new file mode 100644
index 0000000..a2f86d5
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/ExceptionHandlerSuccessTest.java
@@ -0,0 +1,60 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+class ExceptionHandlerSuccessTest {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ void testCase001_정상호출() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/api/test/case001");
+
+ String requestBody = """
+ {
+ "serviceId": "TST10001",
+ "aaa": "aaaa",
+ "bbb": "bbbb"
+ }
+ """;
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setHeader("Content-Type", "application/json");
+ httpPost.setHeader("Accept", "application/json");
+ httpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ assertEquals(200, response.getCode());
+ return EntityUtils.toString(response.getEntity());
+ });
+
+ // then
+ assertNotNull(responseBody);
+
+ JsonNode jsonNode = objectMapper.readTree(responseBody);
+ assertNotNull(jsonNode);
+
+ // validate
+ assertNotNull(jsonNode.get("transactionId"));
+ assertEquals("approved", jsonNode.get("status").asText());
+ String prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
+ System.out.println(prettyJson);
+ }
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/OAuth2BearerAuth404ErrorTest.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/OAuth2BearerAuth404ErrorTest.java
new file mode 100644
index 0000000..1107a1c
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/OAuth2BearerAuth404ErrorTest.java
@@ -0,0 +1,78 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class OAuth2BearerAuth404ErrorTest {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ void testCase001_Bearer인증_존재하지않는API_404에러() throws Exception {
+ // Step 1: 토큰 발급
+ String tokenUrl = TestConfig.getBaseUrl("/oauth/token");
+ String accessToken;
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost tokenPost = new HttpPost(tokenUrl);
+
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("grant_type", "client_credentials"));
+ params.add(new BasicNameValuePair("client_id", "AyeWx0OpYLJbHpZ21DDSxYfQPmD5Ckn8"));
+ params.add(new BasicNameValuePair("client_secret", "KwBYJT1m1v3SUvzN2HPHhy2Ad9Q2GADbgWkYZZg9Ybbc8R1uEuBJNQ9oVl13b1qiXwYvDWTaX0EanxY4SMjNfnBBsZzfNoMwwDPTb3IjQy5k3CRxTAYzcSnmzlC4FSsG"));
+ params.add(new BasicNameValuePair("scope", "api"));
+
+ tokenPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String tokenResponse = httpClient.execute(tokenPost, response -> {
+ assertEquals(200, response.getCode(), "토큰 발급 실패");
+ return EntityUtils.toString(response.getEntity());
+ });
+
+ JsonNode tokenJson = objectMapper.readTree(tokenResponse);
+ accessToken = tokenJson.get("access_token").asText();
+ assertNotNull(accessToken, "access_token이 null입니다");
+
+ System.out.println("Access Token: " + accessToken);
+ }
+
+ // Step 2: 존재하지 않는 API 호출 (404 에러 예상)
+ String apiUrl = TestConfig.getBaseUrl("/api/auth/caseXXX");
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(apiUrl);
+ httpPost.setHeader("Authorization", "Bearer " + accessToken);
+ httpPost.setHeader("Content-Type", "application/json");
+ httpPost.setEntity(new StringEntity("{}", ContentType.APPLICATION_JSON));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(404, response.getCode(), "404 에러가 발생해야 합니다");
+ return body;
+ });
+
+ // MessageUtil.makeJsonErrorMessage 기본 포맷 검증
+ ErrorResponseValidator.validateErrorFormat(responseBody);
+ }
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/OAuth2BearerAuthTest.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/OAuth2BearerAuthTest.java
new file mode 100644
index 0000000..6682c5e
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/OAuth2BearerAuthTest.java
@@ -0,0 +1,83 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class OAuth2BearerAuthTest {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ void testCase001_Bearer인증_성공() throws Exception {
+ // Step 1: 토큰 발급
+ String tokenUrl = TestConfig.getBaseUrl("/oauth/token");
+ String accessToken;
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost tokenPost = new HttpPost(tokenUrl);
+
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("grant_type", "client_credentials"));
+ params.add(new BasicNameValuePair("client_id", "AyeWx0OpYLJbHpZ21DDSxYfQPmD5Ckn8"));
+ params.add(new BasicNameValuePair("client_secret", "KwBYJT1m1v3SUvzN2HPHhy2Ad9Q2GADbgWkYZZg9Ybbc8R1uEuBJNQ9oVl13b1qiXwYvDWTaX0EanxY4SMjNfnBBsZzfNoMwwDPTb3IjQy5k3CRxTAYzcSnmzlC4FSsG"));
+ params.add(new BasicNameValuePair("scope", "api"));
+
+ tokenPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String tokenResponse = httpClient.execute(tokenPost, response -> {
+ assertEquals(200, response.getCode(), "토큰 발급 실패");
+ return EntityUtils.toString(response.getEntity());
+ });
+
+ JsonNode tokenJson = objectMapper.readTree(tokenResponse);
+ accessToken = tokenJson.get("access_token").asText();
+ assertNotNull(accessToken, "access_token이 null입니다");
+
+ System.out.println("Access Token: " + accessToken);
+ }
+
+ // Step 2: Bearer 토큰으로 인증 API 호출
+ String apiUrl = TestConfig.getBaseUrl("/api/auth/case009?transactionId=1234567890");
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(apiUrl);
+ httpPost.setHeader("Authorization", "Bearer " + accessToken);
+ httpPost.setHeader("Content-Type", "application/json");
+ httpPost.setEntity(new StringEntity("{}", ContentType.APPLICATION_JSON));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(200, response.getCode(), "Bearer 인증 실패");
+ return body;
+ });
+
+ // 응답 검증
+ JsonNode jsonNode = objectMapper.readTree(responseBody);
+ assertNotNull(jsonNode, "JSON 응답이 null입니다");
+
+ String prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
+ System.out.println("API Response:");
+ System.out.println(prettyJson);
+ }
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/OAuth2TokenSuccessTest.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/OAuth2TokenSuccessTest.java
new file mode 100644
index 0000000..138277b
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/OAuth2TokenSuccessTest.java
@@ -0,0 +1,64 @@
+package com.eactive.eai.kjb.errors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class OAuth2TokenSuccessTest {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ void testCase001_토큰발급성공() throws Exception {
+ // given
+ String url = TestConfig.getBaseUrl("/oauth/token");
+
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("grant_type", "client_credentials"));
+ params.add(new BasicNameValuePair("client_id", "AyeWx0OpYLJbHpZ21DDSxYfQPmD5Ckn8"));
+ params.add(new BasicNameValuePair("client_secret", "KwBYJT1m1v3SUvzN2HPHhy2Ad9Q2GADbgWkYZZg9Ybbc8R1uEuBJNQ9oVl13b1qiXwYvDWTaX0EanxY4SMjNfnBBsZzfNoMwwDPTb3IjQy5k3CRxTAYzcSnmzlC4FSsG"));
+ params.add(new BasicNameValuePair("scope", "api"));
+
+ // when
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String responseBody = httpClient.execute(httpPost, response -> {
+ String body = EntityUtils.toString(response.getEntity());
+ System.out.println("Response Body: " + body);
+
+ // then
+ assertEquals(200, response.getCode());
+ return body;
+ });
+
+ // 응답 검증
+ JsonNode jsonNode = objectMapper.readTree(responseBody);
+ assertNotNull(jsonNode, "JSON 응답이 null입니다");
+
+ // OAuth2 토큰 응답 필드 확인
+ assertNotNull(jsonNode.get("access_token"), "access_token 필드가 없습니다");
+ assertNotNull(jsonNode.get("token_type"), "token_type 필드가 없습니다");
+ assertNotNull(jsonNode.get("expires_in"), "expires_in 필드가 없습니다");
+
+ String prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
+ System.out.println("Token Response:");
+ System.out.println(prettyJson);
+ }
+ }
+}
diff --git a/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/TestConfig.java b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/TestConfig.java
new file mode 100644
index 0000000..ad2c912
--- /dev/null
+++ b/kjb-errors/src/test/java/com/eactive/eai/kjb/errors/TestConfig.java
@@ -0,0 +1,34 @@
+package com.eactive.eai.kjb.errors;
+
+public class TestConfig {
+
+ private static final String DEFAULT_HOST = "127.0.0.1";
+ private static final String DEFAULT_PORT = "30110";
+
+ /**
+ * 테스트 서버 호스트 반환
+ * 시스템 프로퍼티 test.host가 있으면 사용, 없으면 127.0.0.1
+ * @return 호스트 주소
+ */
+ public static String getHost() {
+ return System.getProperty("test.host", DEFAULT_HOST);
+ }
+
+ /**
+ * 테스트 서버 포트 반환
+ * 시스템 프로퍼티 test.port가 있으면 사용, 없으면 30110
+ * @return 포트 번호
+ */
+ public static String getPort() {
+ return System.getProperty("test.port", DEFAULT_PORT);
+ }
+
+ /**
+ * 테스트 베이스 URL 생성
+ * @param path API 경로 (예: "/oauth/token")
+ * @return 전체 URL
+ */
+ public static String getBaseUrl(String path) {
+ return String.format("http://%s:%s%s", getHost(), getPort(), path);
+ }
+}
diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java
index e2d424b..f79e0a1 100644
--- a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java
+++ b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java
@@ -87,8 +87,10 @@ public class ApiAdapterController implements HttpAdapterServiceKey {
if (adptUri == null) {
logError(servletRequest);
-// throw new Exception("can not find Adapter Uri");
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("can not find Adapter Uri");
+ String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND, "can not find Adapter Uri");
+ return ResponseEntity.status(HttpStatus.NOT_FOUND)
+ .contentType(MediaType.APPLICATION_JSON)
+ .body(errorMsg);
}
//Received time , jwhong
@@ -100,8 +102,10 @@ public class ApiAdapterController implements HttpAdapterServiceKey {
AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
AdapterVO adapterVO = AdapterManager.getInstance().getAdapterVO(adapterGroupName, adapterName);
if (adapterVO == null) {
-// throw new Exception("Adapter not found error");
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Adapter not found error");
+ String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR, "Adapter not found error");
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .contentType(MediaType.APPLICATION_JSON)
+ .body(errorMsg);
}
Properties httpProp = AdapterPropManager.getInstance().getProperties(adapterVO.getPropGroupName());
@@ -152,17 +156,19 @@ public class ApiAdapterController implements HttpAdapterServiceKey {
}
} catch (HttpStatusException e) {
logger.warn("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage());
- String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, e.getCode(),
+ String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, MessageUtil.ERROR_CODE_AP_ERROR,
e.getMessage(), errorResponseFormat);
responseEntity = ResponseEntity.status(e.getStatus()).contentType(mediaType).body(errorMsg);
} catch (JwtAuthException e) {
logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e);
- String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, e.getCode(),
+ String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, MessageUtil.ERROR_CODE_AUTH_FAIL,
e.getMessage(), errorResponseFormat);
responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(errorMsg);
} catch (Exception e) {
logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e);
- responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(mediaType).body(e);
+ String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, MessageUtil.ERROR_CODE_AP_ERROR,
+ e.getMessage(), errorResponseFormat);
+ responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(mediaType).body(errorMsg);
} finally {
/**
* 로깅 인터셉터에 데이터를 전달하기 위한 처리
diff --git a/src/main/java/com/eactive/eai/authserver/config/WebMvcConfig.java b/src/main/java/com/eactive/eai/authserver/config/WebMvcConfig.java
new file mode 100644
index 0000000..ad56833
--- /dev/null
+++ b/src/main/java/com/eactive/eai/authserver/config/WebMvcConfig.java
@@ -0,0 +1,65 @@
+package com.eactive.eai.authserver.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.validation.BindException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.NoHandlerFoundException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Configuration
+@ControllerAdvice
+public class WebMvcConfig {
+
+ @ExceptionHandler({
+ HttpMessageNotReadableException.class,
+ MethodArgumentNotValidException.class,
+ MissingServletRequestParameterException.class,
+ BindException.class
+ })
+ public String handleBadRequest(HttpServletRequest request, HttpServletResponse response, Exception ex) {
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ request.setAttribute("_authServerStatusCode", HttpServletResponse.SC_BAD_REQUEST);
+ request.setAttribute("errorMessage", "400 Bad Request");
+ return "forward:/error.jsp";
+ }
+
+ @ExceptionHandler(NoHandlerFoundException.class)
+ public String handleNotFound(HttpServletRequest request, HttpServletResponse response, Exception ex) {
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ request.setAttribute("_authServerStatusCode", HttpServletResponse.SC_NOT_FOUND);
+ request.setAttribute("errorMessage", "404 Not Found");
+ return "forward:/error.jsp";
+ }
+
+ @ExceptionHandler(AuthenticationException.class)
+ public String handleUnauthorized(HttpServletRequest request, HttpServletResponse response, Exception ex) {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ request.setAttribute("_authServerStatusCode", HttpServletResponse.SC_UNAUTHORIZED);
+ request.setAttribute("errorMessage", "401 Unauthorized");
+ return "forward:/error.jsp";
+ }
+
+ @ExceptionHandler(AccessDeniedException.class)
+ public String handleAccessDenied(HttpServletRequest request, HttpServletResponse response, Exception ex) {
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ request.setAttribute("_authServerStatusCode", HttpServletResponse.SC_FORBIDDEN);
+ request.setAttribute("errorMessage", "403 Forbidden");
+ return "forward:/error.jsp";
+ }
+
+ @ExceptionHandler(Exception.class)
+ public String handleException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ request.setAttribute("_authServerStatusCode", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ request.setAttribute("errorMessage", ex.getMessage());
+ return "forward:/error.jsp";
+ }
+}
diff --git a/src/main/java/com/eactive/eai/authserver/config/WebSecurityConfig.java b/src/main/java/com/eactive/eai/authserver/config/WebSecurityConfig.java
index f61bad2..e968562 100644
--- a/src/main/java/com/eactive/eai/authserver/config/WebSecurityConfig.java
+++ b/src/main/java/com/eactive/eai/authserver/config/WebSecurityConfig.java
@@ -14,6 +14,8 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import javax.servlet.http.HttpServletResponse;
+
@Configuration
@EnableWebSecurity
@Order(1)
@@ -29,8 +31,23 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.headers().frameOptions().disable()
.and()
.authorizeRequests()
+ .antMatchers("/error.jsp").permitAll()
.antMatchers("/**").permitAll()
.and()
+ .exceptionHandling()
+ .authenticationEntryPoint((request, response, authException) -> {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ request.setAttribute("_authServerStatusCode", HttpServletResponse.SC_UNAUTHORIZED);
+ request.setAttribute("errorMessage", "401 Unauthorized");
+ request.getRequestDispatcher("/error.jsp").forward(request, response);
+ })
+ .accessDeniedHandler((request, response, accessDeniedException) -> {
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ request.setAttribute("_authServerStatusCode", HttpServletResponse.SC_FORBIDDEN);
+ request.setAttribute("errorMessage", "403 Forbidden");
+ request.getRequestDispatcher("/error.jsp").forward(request, response);
+ })
+ .and()
.formLogin();
// .and()
// .httpBasic();
diff --git a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenContoller.java b/src/main/java/com/eactive/eai/authserver/custom/BearerTokenContoller.java
index bda62ca..d3d547f 100644
--- a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenContoller.java
+++ b/src/main/java/com/eactive/eai/authserver/custom/BearerTokenContoller.java
@@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.RestController;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.authserver.service.OAuth2Manager;
import com.eactive.eai.common.util.Logger;
+import com.eactive.eai.common.util.MessageUtil;
@RestController
@RequestMapping("/auth/oauth/v2")
@@ -75,20 +76,12 @@ public class BearerTokenContoller {
return ResponseEntity.ok(resObject);
} catch (JwtAuthException e) {
logger.error(e);
-
- JSONObject errorJson = new JSONObject();
- errorJson.put("error", e.getCode());
- errorJson.put("error_description", e.getMessage());
- resObject.putAll(errorJson);
- return ResponseEntity.status(401).body(resObject);
+ String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage());
+ return ResponseEntity.status(401).body(errorJson);
} catch (Exception e) {
logger.error(e);
-
- JSONObject errorJson = new JSONObject();
- errorJson.put("error", "invalid_request");
- errorJson.put("error_description", e.getMessage());
- resObject.putAll(errorJson);
- return ResponseEntity.status(500).body(resObject);
+ String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage());
+ return ResponseEntity.status(500).body(errorJson);
}
}
diff --git a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenFilter.java b/src/main/java/com/eactive/eai/authserver/custom/BearerTokenFilter.java
index d993d30..5ccb70c 100644
--- a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenFilter.java
+++ b/src/main/java/com/eactive/eai/authserver/custom/BearerTokenFilter.java
@@ -16,6 +16,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.common.util.Logger;
+import com.eactive.eai.common.util.MessageUtil;
@Component
public class BearerTokenFilter extends OncePerRequestFilter{
@@ -35,8 +36,6 @@ public class BearerTokenFilter extends OncePerRequestFilter{
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
- JSONObject resObject = new JSONObject();
-
try {
String auth = request.getHeader("Authorization");
@@ -62,23 +61,16 @@ public class BearerTokenFilter extends OncePerRequestFilter{
filterChain.doFilter(request, response);
} catch (JwtAuthException jae) {
logger.debug(jae.getMessage());
-
- JSONObject errorJson = new JSONObject();
- errorJson.put("error", jae.getCode());
- errorJson.put("error_description", jae.getMessage());
- resObject.putAll(errorJson);
- response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 설정
+ String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, jae.getMessage());
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
- response.getWriter().write(resObject.toJSONString());
+ response.getWriter().write(errorJson);
} catch (Exception e) {
logger.error(e.getMessage());
- JSONObject errorJson = new JSONObject();
- errorJson.put("error", "invalid_request");
- errorJson.put("error_description", e.getMessage());
- resObject.putAll(errorJson);
- response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // 500 설정
+ String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage());
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType("application/json;charset=UTF-8");
- response.getWriter().write(resObject.toJSONString());
+ response.getWriter().write(errorJson);
}
}
}
diff --git a/src/main/java/com/eactive/eai/authserver/custom/KjbMGOAuth2Controller.java b/src/main/java/com/eactive/eai/authserver/custom/KjbMGOAuth2Controller.java
index 5918d96..00781b8 100644
--- a/src/main/java/com/eactive/eai/authserver/custom/KjbMGOAuth2Controller.java
+++ b/src/main/java/com/eactive/eai/authserver/custom/KjbMGOAuth2Controller.java
@@ -1,14 +1,11 @@
package com.eactive.eai.authserver.custom;
import java.nio.charset.StandardCharsets;
-import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Principal;
-import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
-import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.HashMap;
@@ -39,7 +36,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.authserver.config.IssueLimitOAuth2Exception;
-import com.eactive.eai.authserver.config.AuthorizationServerConfig;
import com.eactive.eai.authserver.config.RequestContextData;
import com.eactive.eai.authserver.dao.TokenIssuanceLogDAO;
import com.eactive.eai.authserver.service.OAuth2Manager;
@@ -48,8 +44,10 @@ import com.eactive.eai.authserver.vo.ClientVO;
import com.eactive.eai.common.dao.DAOException;
import com.eactive.eai.common.logger.EAIDBLogControl;
import com.eactive.eai.common.util.Logger;
+import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.common.util.UUID;
import com.eactive.eai.data.entity.onl.authserver.TokenIssuanceLog;
+import com.fasterxml.jackson.databind.ObjectMapper;
@Controller
public class KjbMGOAuth2Controller {
@@ -63,7 +61,7 @@ public class KjbMGOAuth2Controller {
@RequestMapping(value = "/mapi/oauth2/token", method = RequestMethod.POST, produces = "application/json; charset=\"UTF-8\"")
@ResponseBody
- public KjbMGOAuth2AccessTokenResponse token(@RequestBody KjbMGOAuth2AccessTokenRequest tokenRequest,
+ public ResponseEntity token(@RequestBody KjbMGOAuth2AccessTokenRequest tokenRequest,
HttpServletRequest request, HttpServletResponse response) {
if (logger.isDebug()) {
logger.debug(tokenRequest.toString());
@@ -96,7 +94,7 @@ public class KjbMGOAuth2Controller {
ClientDetails clientDetails = OAuth2Manager.getInstance().getClientDeatilsStore().get(clientId);
verifyClient(clientDetails, clientId, scopeSet);
- String traceId = UUID.randomUUID().toString().replace("-", "");
+ String traceId = UUID.randomUUID().toString().replace("-", "");
response.setHeader(HEADER_TRACEID, traceId);
HashMap authorizationParameters = new HashMap();
@@ -123,19 +121,22 @@ public class KjbMGOAuth2Controller {
responseToken.setExpiresOn(System.currentTimeMillis() + (token.getExpiresIn() * 1000));
responseToken.setScope(tokenRequest.getScope());
responseToken.setResource(tokenRequest.getResource());
+
+ // 성공 시 JSON 문자열로 변환하여 반환
+ ObjectMapper mapper = new ObjectMapper();
+ String successJson = mapper.writeValueAsString(responseToken);
+ return ResponseEntity.ok(successJson);
+
} catch (JwtAuthException e) {
- response.setStatus(NumberUtils.toInt(StringUtils.left(e.getCode(), 3), HttpStatus.UNAUTHORIZED.value()));
- responseToken.setResponseCode(e.getCode());
- responseToken.setResponseMessage(e.getMessage());
+ String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage());
+ int statusCode = NumberUtils.toInt(StringUtils.left(e.getCode(), 3), HttpStatus.UNAUTHORIZED.value());
+ return ResponseEntity.status(statusCode).body(errorJson);
+
} catch (Exception e) {
logger.error(e.getMessage());
- response.setStatus(HttpStatus.UNAUTHORIZED.value());
- responseToken.setResponseCode(
- String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"));
- responseToken.setResponseMessage("Unauthorized. [Unknown]");
+ String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, "Unauthorized. [Unknown]");
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorJson);
}
-
- return responseToken;
}
private void verifyClient(ClientDetails clientDetails, String clientId, Set scopeSet)