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)