From 44d9250f2485c5aae76ffcff9456c80142ed7acd Mon Sep 17 00:00:00 2001 From: nowgnas Date: Sun, 19 Nov 2023 00:17:51 +0900 Subject: [PATCH 01/42] :wrench: LF1-24 #resolve add dockerfile --- .gitignore | 39 +++ Dockerfile | 15 ++ build.gradle | 52 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 ++++++++++++++++++ gradlew.bat | 92 +++++++ settings.gradle | 1 + .../ApigatewayServiceApplication.java | 15 ++ src/main/resources/application.yml | 16 ++ .../ApigatewayServiceApplicationTests.java | 13 + 11 files changed, 499 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/kr/bb/apigateway/ApigatewayServiceApplication.java create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/kr/bb/apigateway/ApigatewayServiceApplicationTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aab7ace --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ +.DS_Store +docker.sh \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d4088d2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM adoptopenjdk:11-hotspot AS builder +ENV USE_PROFILE local + +COPY gradlew . +COPY gradle gradle +COPY build.gradle . +COPY settings.gradle . +COPY src src +RUN chmod +x ./gradlew +RUN ./gradlew clean bootJar + +FROM adoptopenjdk:11-hotspot +COPY --from=builder build/libs/*.jar app.jar + +ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=${USE_PROFILE}", "/app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..83da562 --- /dev/null +++ b/build.gradle @@ -0,0 +1,52 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '2.7.17' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' +} + +group = 'kr.bb' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '11' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +ext { + set('springCloudVersion', "2021.0.8") +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.cloud:spring-cloud-starter-config' + implementation 'org.springframework.cloud:spring-cloud-starter-gateway' + implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation "org.springframework.cloud:spring-cloud-starter-bus-kafka" +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + +tasks.named('bootBuildImage') { + builder = 'paketobuildpacks/builder-jammy-base:latest' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7f93135c49b765f8051ef9d0a6055ff8e46073d8 GIT binary patch literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fa8f86 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/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/HEAD/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 + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + 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 + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# 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/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +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% equ 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% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..ed34aee --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'apigateway' diff --git a/src/main/java/kr/bb/apigateway/ApigatewayServiceApplication.java b/src/main/java/kr/bb/apigateway/ApigatewayServiceApplication.java new file mode 100644 index 0000000..6b26dfe --- /dev/null +++ b/src/main/java/kr/bb/apigateway/ApigatewayServiceApplication.java @@ -0,0 +1,15 @@ +package kr.bb.apigateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; + +@SpringBootApplication +@EnableEurekaClient +public class ApigatewayServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(ApigatewayServiceApplication.class, args); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..94b9fe0 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,16 @@ +server: + port: 8000 +spring: + application: + name: apigateway-service + config: + activate: + on-profile: local, dev, local + import: optional:configserver:http://localhost:8888 +management: + endpoints: + web: + exposure: + include: + - "refresh" + - "bus-refresh" \ No newline at end of file diff --git a/src/test/java/kr/bb/apigateway/ApigatewayServiceApplicationTests.java b/src/test/java/kr/bb/apigateway/ApigatewayServiceApplicationTests.java new file mode 100644 index 0000000..67b5e17 --- /dev/null +++ b/src/test/java/kr/bb/apigateway/ApigatewayServiceApplicationTests.java @@ -0,0 +1,13 @@ +package kr.bb.apigateway; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApigatewayServiceApplicationTests { + + @Test + void contextLoads() { + } + +} From 66d31498d70dd93757990f031a1cf617bf3c5cfd Mon Sep 17 00:00:00 2001 From: nowgnas Date: Sun, 19 Nov 2023 02:37:42 +0900 Subject: [PATCH 02/42] :heavy_minus_sign: Remove dev tools --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 83da562..b0602b3 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,6 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-gateway' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' compileOnly 'org.projectlombok:lombok' - developmentOnly 'org.springframework.boot:spring-boot-devtools' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation "org.springframework.cloud:spring-cloud-starter-bus-kafka" From 2761eb61d471826282178602f4619a777a987ebb Mon Sep 17 00:00:00 2001 From: nowgnas Date: Sun, 19 Nov 2023 15:21:29 +0900 Subject: [PATCH 03/42] :memo: Amend ignore file --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index aab7ace..35bc9c5 100644 --- a/.gitignore +++ b/.gitignore @@ -35,5 +35,5 @@ out/ ### VS Code ### .vscode/ -.DS_Store +*.DS_Store docker.sh \ No newline at end of file From 1f9ef171c9a537af6a5ac08c4b70ea0947a08696 Mon Sep 17 00:00:00 2001 From: sangwon Date: Sat, 25 Nov 2023 18:15:53 +0900 Subject: [PATCH 04/42] Create deploy-dev.yml --- .github/workflows/deploy-dev.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/deploy-dev.yml diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..0146b73 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,23 @@ +on: + push: + branches: + - 'develop' +jobs: + build-and-push-docker-image: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DEV_DOCKER_ID }} + password: ${{ secrets.DEV_DOCKER_PW }} + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + push: true + tags: nowgnas/bb:apigateway From 0ca6354b448008557c0e0aa54cae4e5296a475bb Mon Sep 17 00:00:00 2001 From: sangwon Date: Sat, 25 Nov 2023 18:16:55 +0900 Subject: [PATCH 05/42] Create build.yml --- .github/workflows/build.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..80fe23e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,25 @@ +name: PR build + + +on: + pull_request: + branches: [ develop ] # develop branch에 PR을 보낼 때 실행 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + + # Gradle wrapper 파일 실행 권한주기 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + # Gradle test를 실행한다 + - name: build gradle + run: ./gradlew clean build From c6787a9aaf52f5f856eb9c0b4cb6005207069b86 Mon Sep 17 00:00:00 2001 From: sangwon Date: Sat, 2 Dec 2023 17:09:12 +0900 Subject: [PATCH 06/42] Delete src/test/java/kr/bb/apigateway/ApigatewayServiceApplicationTests.java --- .../ApigatewayServiceApplicationTests.java | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 src/test/java/kr/bb/apigateway/ApigatewayServiceApplicationTests.java diff --git a/src/test/java/kr/bb/apigateway/ApigatewayServiceApplicationTests.java b/src/test/java/kr/bb/apigateway/ApigatewayServiceApplicationTests.java deleted file mode 100644 index 67b5e17..0000000 --- a/src/test/java/kr/bb/apigateway/ApigatewayServiceApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package kr.bb.apigateway; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ApigatewayServiceApplicationTests { - - @Test - void contextLoads() { - } - -} From 5350174aebd269dd176fa88b18d8e9a8349b3283 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:31:23 +0900 Subject: [PATCH 07/42] add: add the filters and valueObject which needs for setting Gateway --- build.gradle | 5 + .../common/JwtAuthenticationFilter.java | 68 ++++++++++ .../common/dto/RenewAccessTokenDto.java | 18 +++ .../apigateway/common/entity/BaseEntity.java | 28 +++++ .../apigateway/common/exception/ErrorDTO.java | 17 +++ .../IssueRefreshRefreshTokenInCookie.java | 46 +++++++ .../JwtAccessTokenCreateProcessor.java | 28 +++++ .../JwtAccessTokenDeleteStrategy.java | 7 ++ .../common/security/RefreshTokenStrategy.java | 11 ++ ...AccessTokenInRedisBlackListWhenLogout.java | 18 +++ .../common/security/SecurityContextUtil.java | 18 +++ .../SystemAuthenticationSuccessHandler.java | 56 +++++++++ .../common/security/TokenHandler.java | 29 +++++ .../service/RenewRefreshTokenStrategy.java | 15 +++ ...enRefreshTokenIsMatchedCookieAndRedis.java | 42 +++++++ .../bb/apigateway/common/util/CookieUtil.java | 43 +++++++ .../util/ExtractAuthorizationTokenUtil.java | 36 ++++++ .../common/util/JsonBinderUtil.java | 25 ++++ .../kr/bb/apigateway/common/util/JwtUtil.java | 119 ++++++++++++++++++ .../common/util/OauthInfoConvertor.java | 12 ++ .../common/util/RedisBlackListTokenUtil.java | 25 ++++ .../common/util/RedisRefreshTokenUtil.java | 35 ++++++ .../apigateway/common/valueobject/AuthId.java | 16 +++ .../valueobject/AuthenticationProvider.java | 6 + .../apigateway/common/valueobject/BaseId.java | 33 +++++ ...thenticationShouldNotFilterAntMatcher.java | 8 ++ .../valueobject/KakaoOAuthURLAntURI.java | 10 ++ .../apigateway/common/valueobject/Role.java | 7 ++ .../SecurityPolicyStaticValue.java | 13 ++ .../common/valueobject/SwaggerRequestURI.java | 11 ++ .../social/dto/SocialLoginRequestCommand.java | 18 +++ .../social/exception/SocialAuthException.java | 16 +++ .../filter/SocialAuthorizationFilter.java | 38 ++++++ .../exception/StoreManagerAuthException.java | 12 ++ .../StoreMangerAuthorizationFilter.java | 47 +++++++ .../apigateway/store/valueobject/StoreId.java | 9 ++ .../store/valueobject/StoreManagerStatus.java | 7 ++ .../exception/SystemAdminAuthException.java | 12 ++ .../SystemAdminAuthorizationFilter.java | 36 ++++++ 39 files changed, 1000 insertions(+) create mode 100644 src/main/java/kr/bb/apigateway/common/JwtAuthenticationFilter.java create mode 100644 src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java create mode 100644 src/main/java/kr/bb/apigateway/common/entity/BaseEntity.java create mode 100644 src/main/java/kr/bb/apigateway/common/exception/ErrorDTO.java create mode 100644 src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java create mode 100644 src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java create mode 100644 src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenDeleteStrategy.java create mode 100644 src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java create mode 100644 src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java create mode 100644 src/main/java/kr/bb/apigateway/common/security/SecurityContextUtil.java create mode 100644 src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java create mode 100644 src/main/java/kr/bb/apigateway/common/security/TokenHandler.java create mode 100644 src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenStrategy.java create mode 100644 src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java create mode 100644 src/main/java/kr/bb/apigateway/common/util/CookieUtil.java create mode 100644 src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java create mode 100644 src/main/java/kr/bb/apigateway/common/util/JsonBinderUtil.java create mode 100644 src/main/java/kr/bb/apigateway/common/util/JwtUtil.java create mode 100644 src/main/java/kr/bb/apigateway/common/util/OauthInfoConvertor.java create mode 100644 src/main/java/kr/bb/apigateway/common/util/RedisBlackListTokenUtil.java create mode 100644 src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java create mode 100644 src/main/java/kr/bb/apigateway/common/valueobject/AuthId.java create mode 100644 src/main/java/kr/bb/apigateway/common/valueobject/AuthenticationProvider.java create mode 100644 src/main/java/kr/bb/apigateway/common/valueobject/BaseId.java create mode 100644 src/main/java/kr/bb/apigateway/common/valueobject/JWTAuthenticationShouldNotFilterAntMatcher.java create mode 100644 src/main/java/kr/bb/apigateway/common/valueobject/KakaoOAuthURLAntURI.java create mode 100644 src/main/java/kr/bb/apigateway/common/valueobject/Role.java create mode 100644 src/main/java/kr/bb/apigateway/common/valueobject/SecurityPolicyStaticValue.java create mode 100644 src/main/java/kr/bb/apigateway/common/valueobject/SwaggerRequestURI.java create mode 100644 src/main/java/kr/bb/apigateway/social/dto/SocialLoginRequestCommand.java create mode 100644 src/main/java/kr/bb/apigateway/social/exception/SocialAuthException.java create mode 100644 src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationFilter.java create mode 100644 src/main/java/kr/bb/apigateway/store/exception/StoreManagerAuthException.java create mode 100644 src/main/java/kr/bb/apigateway/store/filter/StoreMangerAuthorizationFilter.java create mode 100644 src/main/java/kr/bb/apigateway/store/valueobject/StoreId.java create mode 100644 src/main/java/kr/bb/apigateway/store/valueobject/StoreManagerStatus.java create mode 100644 src/main/java/kr/bb/apigateway/systsem/exception/SystemAdminAuthException.java create mode 100644 src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationFilter.java diff --git a/build.gradle b/build.gradle index b0602b3..130010a 100644 --- a/build.gradle +++ b/build.gradle @@ -29,11 +29,16 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.cloud:spring-cloud-starter-config' implementation 'org.springframework.cloud:spring-cloud-starter-gateway' + implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation "org.springframework.cloud:spring-cloud-starter-bus-kafka" + implementation 'org.springframework.data:spring-data-redis' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' } dependencyManagement { diff --git a/src/main/java/kr/bb/apigateway/common/JwtAuthenticationFilter.java b/src/main/java/kr/bb/apigateway/common/JwtAuthenticationFilter.java new file mode 100644 index 0000000..0c707b5 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/JwtAuthenticationFilter.java @@ -0,0 +1,68 @@ +package kr.bb.apigateway.common; + +import io.jsonwebtoken.ExpiredJwtException; +import java.io.IOException; +import javax.security.sasl.AuthenticationException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.util.JwtUtil; +import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; +import kr.bb.apigateway.common.valueobject.JWTAuthenticationShouldNotFilterAntMatcher; +import kr.bb.apigateway.common.valueobject.KakaoOAuthURLAntURI; +import kr.bb.apigateway.common.valueobject.SwaggerRequestURI; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final RedisBlackListTokenUtil redisBlackListTokenUtil; + + private boolean shouldNotFilterSwaggerURI(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + return requestURI.contains(SwaggerRequestURI.UI_URI) || requestURI.contains(SwaggerRequestURI.API_DOCS_URI) + || requestURI.contains(SwaggerRequestURI.WEB_JARS) || requestURI.contains(SwaggerRequestURI.FAVICON) + || requestURI.contains(SwaggerRequestURI.RESOURCES); + } + + private boolean shouldNotFilterKakaoOauth2(HttpServletRequest request) { + + String requestURI = request.getRequestURI(); + return requestURI.contains(KakaoOAuthURLAntURI.KAPI) + || requestURI.contains(KakaoOAuthURLAntURI.KAUTH) || requestURI.contains(KakaoOAuthURLAntURI.REDIRECT) + || requestURI.contains(KakaoOAuthURLAntURI.OAUTH); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + return shouldNotFilterSwaggerURI(request) || shouldNotFilterKakaoOauth2(request) + || requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.SIGNUP_ANT) + || requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.LOGIN_ANT) + || requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.EMAIL_ANT); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String token = ExtractAuthorizationTokenUtil.extractToken(request); + if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { + throw new AuthenticationException("해당 토큰은 이미 로그아웃 처리된 토큰이라 사용할 수 없는 토큰입니다."); + } + try { + JwtUtil.isTokenValid(token); + } catch (ExpiredJwtException e) { + throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "만료된 토큰입니다. Refresh토큰을 확인하세요"); + } + filterChain.doFilter(request, response); + } + + + + +} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java b/src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java new file mode 100644 index 0000000..fdd8e69 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java @@ -0,0 +1,18 @@ +package kr.bb.apigateway.common.dto; + +import com.bit.lot.flower.auth.common.valueobject.BaseId; +import com.bit.lot.flower.auth.common.valueobject.Role; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class RenewAccessTokenDto { + T authId; + Role role; + +} diff --git a/src/main/java/kr/bb/apigateway/common/entity/BaseEntity.java b/src/main/java/kr/bb/apigateway/common/entity/BaseEntity.java new file mode 100644 index 0000000..b028ab5 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/entity/BaseEntity.java @@ -0,0 +1,28 @@ +package kr.bb.apigateway.common.entity; + +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class BaseEntity { + + @CreatedDate + @Column(name = "created_at") + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @Column(name = "is_deleted") + private Boolean isDeleted; +} + diff --git a/src/main/java/kr/bb/apigateway/common/exception/ErrorDTO.java b/src/main/java/kr/bb/apigateway/common/exception/ErrorDTO.java new file mode 100644 index 0000000..bc203ec --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/exception/ErrorDTO.java @@ -0,0 +1,17 @@ +package kr.bb.apigateway.common.exception; + + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ErrorDTO { + + private final String code; + private final String message; + public ErrorDTO(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java b/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java new file mode 100644 index 0000000..cf8afa4 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java @@ -0,0 +1,46 @@ +package kr.bb.apigateway.common.security; + +import com.bit.lot.flower.auth.common.util.CookieUtil; +import com.bit.lot.flower.auth.common.util.JwtUtil; +import com.bit.lot.flower.auth.common.util.RedisRefreshTokenUtil; +import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; +import java.time.Duration; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + + +@RequiredArgsConstructor +@Component +public class IssueRefreshRefreshTokenInCookie implements + RefreshTokenStrategy { + + @Value("${cookie.refresh.http.domain}") + private String domain; + @Value("${cookie.refresh.token.name}") + private String refreshCookieName; + + private final RedisRefreshTokenUtil redisRefreshTokenUtil; + + @Override + public void createRefreshToken(String userId,HttpServletResponse response) { + String refreshToken = JwtUtil.generateRefreshToken(String.valueOf(userId)); + redisRefreshTokenUtil.saveRefreshToken(userId, refreshToken, + Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME)); + response.addCookie(CookieUtil.createHttpOnlyCookie(refreshCookieName, refreshToken, + Duration.ofDays(1), domain)); + } + + @Override + public void invalidateRefreshToken(String id, HttpServletResponse response) { + CookieUtil.deleteCookie(refreshCookieName, domain); + redisRefreshTokenUtil.deleteRefreshToken(id); + } + + @Override + public void renewRefreshToken(String id, HttpServletResponse response) { + + } + +} diff --git a/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java b/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java new file mode 100644 index 0000000..868437b --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java @@ -0,0 +1,28 @@ +package kr.bb.apigateway.common.security; + +import com.bit.lot.flower.auth.common.util.JwtUtil; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + + +@RequiredArgsConstructor +@Component +public class JwtAccessTokenCreateProcessor { + + private String createAccessTokenWithoutClaims(String subject){ + return JwtUtil.generateAccessToken(subject); + } + private String createAccessTokenWithClaims(String subject , Map claimsList){ + return JwtUtil.generateAccessTokenWithClaims(subject,claimsList); + } + + public String createAccessToken(String subject , Map claimsList){ + if(claimsList==null) return createAccessTokenWithoutClaims(subject); + else{ + return createAccessTokenWithClaims(subject,claimsList); + } + } + + +} diff --git a/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenDeleteStrategy.java b/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenDeleteStrategy.java new file mode 100644 index 0000000..6757b2a --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenDeleteStrategy.java @@ -0,0 +1,7 @@ +package kr.bb.apigateway.common.security; + + +public interface JwtAccessTokenDeleteStrategy { + public void invalidateAccessToken(String token); + +} diff --git a/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java b/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java new file mode 100644 index 0000000..df9856c --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java @@ -0,0 +1,11 @@ +package kr.bb.apigateway.common.security; + +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; + +@Component +public interface RefreshTokenStrategy { + public void createRefreshToken(String id, HttpServletResponse response); + public void invalidateRefreshToken(String id, HttpServletResponse response); + public void renewRefreshToken(String id, HttpServletResponse response); +} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java b/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java new file mode 100644 index 0000000..354a986 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java @@ -0,0 +1,18 @@ +package kr.bb.apigateway.common.security; + +import com.bit.lot.flower.auth.common.util.RedisBlackListTokenUtil; +import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class RegisterAccessTokenInRedisBlackListWhenLogout implements + JwtAccessTokenDeleteStrategy { + private final RedisBlackListTokenUtil redisBlackListTokenUtil; + @Override + public void invalidateAccessToken(String token) { + redisBlackListTokenUtil.addTokenToBlacklist(token, + Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME)); + } +} diff --git a/src/main/java/kr/bb/apigateway/common/security/SecurityContextUtil.java b/src/main/java/kr/bb/apigateway/common/security/SecurityContextUtil.java new file mode 100644 index 0000000..bd369ab --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/security/SecurityContextUtil.java @@ -0,0 +1,18 @@ +package kr.bb.apigateway.common.security; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +public class SecurityContextUtil { + + + + public static void setSecurityContextWithUserId(String userId) { + Authentication authentication = new UsernamePasswordAuthenticationToken(userId, null); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + + +} diff --git a/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java b/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java new file mode 100644 index 0000000..43e6bf7 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java @@ -0,0 +1,56 @@ +package kr.bb.apigateway.common.security; + +import com.bit.lot.flower.auth.common.util.JwtUtil; +import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; +import com.bit.lot.flower.auth.social.valueobject.AuthId; +import java.io.IOException; +import java.util.Map; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class SystemAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + private final TokenHandler tokenHandler; + + + private String getRoleFromSecurityContext() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && !authentication.getAuthorities().isEmpty()) { + return authentication.getAuthorities().iterator().next().getAuthority(); + } + throw new IllegalArgumentException("토큰에 해당 유저의 역할이 담겨있지 않습니다."); + } + + private Map createClaimsRoleMap() { + return JwtUtil.addClaims( + SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME, getRoleFromSecurityContext()); + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + FilterChain chain, Authentication authentication) throws IOException, ServletException { + onAuthenticationSuccess(request, response, authentication); + chain.doFilter(request,response); + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { + String token = tokenHandler.createToken(getIdFromPrincipal(authentication), + createClaimsRoleMap(), response); + response.setHeader(SecurityPolicyStaticValue.TOKEN_AUTHORIZAION_HEADER_NAME,SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_PREFIX +token ); + } + + private String getIdFromPrincipal(Authentication authentication){ + return ((AuthId)authentication.getPrincipal()).getValue().toString(); + } +} diff --git a/src/main/java/kr/bb/apigateway/common/security/TokenHandler.java b/src/main/java/kr/bb/apigateway/common/security/TokenHandler.java new file mode 100644 index 0000000..89e5fcc --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/security/TokenHandler.java @@ -0,0 +1,29 @@ +package kr.bb.apigateway.common.security; + +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class TokenHandler { + + private final RefreshTokenStrategy refreshTokenStrategy; + private final JwtAccessTokenCreateProcessor accessTokenStrategy; + private final JwtAccessTokenDeleteStrategy deleteStrategy; + + + public String createToken(String id, + Map claimList,HttpServletResponse response) { + refreshTokenStrategy.createRefreshToken(id,response); + return accessTokenStrategy.createAccessToken(id, claimList); + } + + public void invalidateToken(String id, String token, HttpServletResponse response) { + deleteStrategy.invalidateAccessToken(token); + refreshTokenStrategy.invalidateRefreshToken(id, response); + } + + +} diff --git a/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenStrategy.java b/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenStrategy.java new file mode 100644 index 0000000..db673cc --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenStrategy.java @@ -0,0 +1,15 @@ +package kr.bb.apigateway.common.service; + +import com.bit.lot.flower.auth.common.valueobject.BaseId; +import com.bit.lot.flower.auth.common.valueobject.Role; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Service; + + +@Service +public interface RenewRefreshTokenStrategy { + + String renew(ID id, Role role, HttpServletRequest request, + HttpServletResponse response); +} diff --git a/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java b/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java new file mode 100644 index 0000000..07251e7 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java @@ -0,0 +1,42 @@ +package kr.bb.apigateway.common.service; + +import com.bit.lot.flower.auth.common.security.TokenHandler; +import com.bit.lot.flower.auth.common.util.CookieUtil; +import com.bit.lot.flower.auth.common.util.JwtUtil; +import com.bit.lot.flower.auth.common.util.RedisRefreshTokenUtil; +import com.bit.lot.flower.auth.common.valueobject.BaseId; +import com.bit.lot.flower.auth.common.valueobject.Role; +import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis implements + RenewRefreshTokenStrategy { + + private final RedisRefreshTokenUtil redisRefreshTokenUtil; + private final TokenHandler tokenHandler; + @Value("${cookie.refresh.token.name}") + private String refreshCookieName; + + @Override + public String renew(ID id, Role role, HttpServletRequest request, + HttpServletResponse response) { + String refreshTokenAtCookie = CookieUtil.getCookieValue(request, refreshCookieName); + if (redisRefreshTokenUtil.getRefreshToken(refreshTokenAtCookie) == null) { + throw new IllegalArgumentException("유효한 접근이 아닙니다. Refresh토큰을 확인해주세요"); + } + return tokenHandler.createToken(id.getValue().toString(), createClaimsRoleMap(role), response); + } + + private Map createClaimsRoleMap(Role role) { + return JwtUtil.addClaims( + SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME, role); + } + +} diff --git a/src/main/java/kr/bb/apigateway/common/util/CookieUtil.java b/src/main/java/kr/bb/apigateway/common/util/CookieUtil.java new file mode 100644 index 0000000..e70b01a --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/util/CookieUtil.java @@ -0,0 +1,43 @@ +package kr.bb.apigateway.common.util; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Optional; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +public class CookieUtil { + + public static Cookie createHttpOnlyCookie(String name, String value, Duration maxAge, + String path) { + + Cookie cookie = new Cookie(name, value); + cookie.setHttpOnly(true); + cookie.setMaxAge(Math.toIntExact(maxAge.toMillis())); + cookie.setPath(path); + return cookie; + } + + public static String getCookieValue(HttpServletRequest request, String name) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + Optional matchingCookie = Arrays.stream(cookies) + .filter(cookie -> name.equals(cookie.getName())) + .findFirst(); + + if (matchingCookie.isPresent()) { + return matchingCookie.get().getValue(); + } + } + throw new IllegalArgumentException("존재하지 않는 쿠키입니다."); + } + + public static Cookie deleteCookie(String name, String path) { + Cookie cookie = new Cookie(name, null); + cookie.setMaxAge(0); + cookie.setSecure(true); + cookie.setHttpOnly(true); + cookie.setPath(path); + return cookie; + } +} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java new file mode 100644 index 0000000..00c7352 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java @@ -0,0 +1,36 @@ +package kr.bb.apigateway.common.util; + +import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; +import io.jsonwebtoken.Claims; +import javax.servlet.http.HttpServletRequest; + +public class ExtractAuthorizationTokenUtil { + + + private ExtractAuthorizationTokenUtil() { + + } + + public static String extractToken(HttpServletRequest request) { + + String authorizationHeader = request.getHeader( + SecurityPolicyStaticValue.TOKEN_AUTHORIZAION_HEADER_NAME); + if (authorizationHeader != null && authorizationHeader.startsWith(SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_PREFIX)) { + return authorizationHeader.substring(7); + } else { + throw new IllegalArgumentException("토큰 정보를 찾을 수 없습니다. 로그인을 먼저 해주세요."); + } + } + + public static String extractUserId(HttpServletRequest request){ + String token = extractToken(request); + return JwtUtil.extractSubject(token); + } + + public static String extractRole(HttpServletRequest request) { + String token = ExtractAuthorizationTokenUtil.extractToken(request); + Claims claims = JwtUtil.extractClaims(token); + return (String) claims.get(SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME); + } + +} diff --git a/src/main/java/kr/bb/apigateway/common/util/JsonBinderUtil.java b/src/main/java/kr/bb/apigateway/common/util/JsonBinderUtil.java new file mode 100644 index 0000000..3486073 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/util/JsonBinderUtil.java @@ -0,0 +1,25 @@ +package kr.bb.apigateway.common.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; + +public class JsonBinderUtil { + + public static HttpServletResponse setResponseWithJson(HttpServletResponse response, int status, + Object type) throws IOException { + + response.setContentType("application/json"); + response.setStatus(status); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(getJsonFromType(type)); + return response; + } + + private static String getJsonFromType(Object type) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.writeValueAsString(type); + } + +} diff --git a/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java new file mode 100644 index 0000000..56da2d2 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java @@ -0,0 +1,119 @@ +package kr.bb.apigateway.common.util; + + +import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import org.springframework.stereotype.Component; + +@Component +public class JwtUtil { + + + private JwtUtil(){ + + } + private static SecretKey accessSecret; + private static SecretKey refreshSecret; + + + + public static String generateAccessTokenWithClaims(String subject, + Map claimsList) { + Date now = new Date(); + + initAccessKey(); + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(now) + .setExpiration(Date.from(Instant.now().plusMillis( + Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME)))) + .signWith(accessSecret) + .addClaims(claimsList) + .compact(); + } + + public static Map addClaims(String id, Object value) { + Map claims = new HashMap<>(); + claims.put(id, value); + return claims; + } + + public static String + generateAccessToken(String subject) { + Date now = new Date(); + + initAccessKey(); + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(now) + .setExpiration(Date.from(Instant.now().plusMillis( + Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME)))) + .signWith(accessSecret) + .compact(); + } + + public static String generateRefreshToken(String subject) { + Date now = new Date(); + initRefreshKey(); + + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(now) + .setExpiration(Date.from(Instant.now().plusMillis( + Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME)))) + .signWith(refreshSecret) + .compact(); + } + + public static String extractSubject(String token) { + return extractClaims(token).getSubject(); + } + + public static Claims extractClaims(String token) { + return Jwts.parserBuilder().setSigningKey(accessSecret).build().parseClaimsJws(token) + .getBody(); + } + + public static boolean isTokenValid(String token) { + try { + extractClaims(token); + return true; + } catch (ExpiredJwtException e) { + throw new ExpiredJwtException(e.getHeader(),e.getClaims(),e.getMessage()) { + }; + } catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) { + throw new IllegalArgumentException("올바르지 않은 접근입니다."); + } + } + + private static void initRefreshKey() { + try { + refreshSecret = KeyGenerator.getInstance("HmacSHA256").generateKey(); + + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("plz init the secret key"); + } + } + + + private static void initAccessKey() { + try { + accessSecret = KeyGenerator.getInstance("HmacSHA256").generateKey(); + + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("plz init the secret key"); + } + + } + } diff --git a/src/main/java/kr/bb/apigateway/common/util/OauthInfoConvertor.java b/src/main/java/kr/bb/apigateway/common/util/OauthInfoConvertor.java new file mode 100644 index 0000000..14809ac --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/util/OauthInfoConvertor.java @@ -0,0 +1,12 @@ +package kr.bb.apigateway.common.util; + +public class OauthInfoConvertor { + + public static String convertInternationalPhoneNumberToDomestic(String phoneNumber) { + String numericPhone = phoneNumber.replaceAll("[^0-9]", ""); + numericPhone = 0 +numericPhone.substring(2); + return numericPhone; + } + +} + diff --git a/src/main/java/kr/bb/apigateway/common/util/RedisBlackListTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/RedisBlackListTokenUtil.java new file mode 100644 index 0000000..5ceff00 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/util/RedisBlackListTokenUtil.java @@ -0,0 +1,25 @@ +package kr.bb.apigateway.common.util; + +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class RedisBlackListTokenUtil { + + private static final String BLACKLIST_KEY = "logout-blacklist:"; + + private final RedisTemplate redisTemplate; + + public void addTokenToBlacklist(String token, long expirationTimeSeconds) { + String key = BLACKLIST_KEY + token; + redisTemplate.opsForValue().set(key, "blacklisted", expirationTimeSeconds, TimeUnit.SECONDS); + } + + public boolean isTokenBlacklisted(String token) { + String key = BLACKLIST_KEY + token; + return redisTemplate.hasKey(key); + } +} diff --git a/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java new file mode 100644 index 0000000..238d5d8 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java @@ -0,0 +1,35 @@ +package kr.bb.apigateway.common.util; + +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + + +@RequiredArgsConstructor +@Component +public class +RedisRefreshTokenUtil { + + private final RedisTemplate redisTemplate; + + public void + saveRefreshToken(String userId, String refreshToken, long expirationTimeInSeconds) { + String key = getRefreshTokenKey(userId); + redisTemplate.opsForValue().set(key, refreshToken, expirationTimeInSeconds, TimeUnit.SECONDS); + } + + public String getRefreshToken(String userId) { + String key = getRefreshTokenKey(userId); + return (String) redisTemplate.opsForValue().get(key); + } + + public void deleteRefreshToken(String userId) { + String key = getRefreshTokenKey(userId); + redisTemplate.delete(key); + } + + private String getRefreshTokenKey(String userId) { + return "refresh_token:" + userId; + } +} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/AuthId.java b/src/main/java/kr/bb/apigateway/common/valueobject/AuthId.java new file mode 100644 index 0000000..04d2542 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/valueobject/AuthId.java @@ -0,0 +1,16 @@ +package kr.bb.apigateway.common.valueobject; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + + +@NoArgsConstructor +@SuperBuilder +@Getter +public class AuthId extends BaseId { + public AuthId(Long value) { + super(value); + } + +} diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/AuthenticationProvider.java b/src/main/java/kr/bb/apigateway/common/valueobject/AuthenticationProvider.java new file mode 100644 index 0000000..e86c88e --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/valueobject/AuthenticationProvider.java @@ -0,0 +1,6 @@ +package kr.bb.apigateway.common.valueobject; + +public enum AuthenticationProvider { + kakao, + none; +} diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/BaseId.java b/src/main/java/kr/bb/apigateway/common/valueobject/BaseId.java new file mode 100644 index 0000000..e12e939 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/valueobject/BaseId.java @@ -0,0 +1,33 @@ +package kr.bb.apigateway.common.valueobject; + +import java.util.Objects; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@SuperBuilder +public abstract class BaseId { + + private T value; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BaseId baseId = (BaseId) o; + return value.equals(baseId.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/JWTAuthenticationShouldNotFilterAntMatcher.java b/src/main/java/kr/bb/apigateway/common/valueobject/JWTAuthenticationShouldNotFilterAntMatcher.java new file mode 100644 index 0000000..1d53b64 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/valueobject/JWTAuthenticationShouldNotFilterAntMatcher.java @@ -0,0 +1,8 @@ +package kr.bb.apigateway.common.valueobject; + +public class JWTAuthenticationShouldNotFilterAntMatcher { + + public static final String LOGIN_ANT = "/login"; + public static final String EMAIL_ANT = "/emails"; + public static final String SIGNUP_ANT = "/signup"; +} diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/KakaoOAuthURLAntURI.java b/src/main/java/kr/bb/apigateway/common/valueobject/KakaoOAuthURLAntURI.java new file mode 100644 index 0000000..53dd1ac --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/valueobject/KakaoOAuthURLAntURI.java @@ -0,0 +1,10 @@ +package kr.bb.apigateway.common.valueobject; + +public class KakaoOAuthURLAntURI { + + public static final String KAUTH = "/kauth"; + public static final String OAUTH = "/oauth"; + public static final String REDIRECT = "/"; + public static final String KAPI = "/kapi"; + +} diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/Role.java b/src/main/java/kr/bb/apigateway/common/valueobject/Role.java new file mode 100644 index 0000000..7410770 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/valueobject/Role.java @@ -0,0 +1,7 @@ +package kr.bb.apigateway.common.valueobject; + +public enum Role { + ROLE_SOCIAL_USER, + ROLE_STORE_MANAGER, + ROLE_SYSTEM_ADMIN +} diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/SecurityPolicyStaticValue.java b/src/main/java/kr/bb/apigateway/common/valueobject/SecurityPolicyStaticValue.java new file mode 100644 index 0000000..a06bf5d --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/valueobject/SecurityPolicyStaticValue.java @@ -0,0 +1,13 @@ +package kr.bb.apigateway.common.valueobject; + + +public class SecurityPolicyStaticValue { + + public static String REFRESH_EXPIRATION_TIME="3660"; + public static String TOKEN_AUTHORIZAION_HEADER_NAME = "Authorization"; + public static String TOKEN_AUTHORIZATION_PREFIX= "Bearer "; + public static String CLAIMS_ROLE_KEY_NAME = "ROLE"; + public static String ACCESS_EXPIRATION_TIME="10"; + + +} diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/SwaggerRequestURI.java b/src/main/java/kr/bb/apigateway/common/valueobject/SwaggerRequestURI.java new file mode 100644 index 0000000..ac933d4 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/valueobject/SwaggerRequestURI.java @@ -0,0 +1,11 @@ +package kr.bb.apigateway.common.valueobject; + +public class SwaggerRequestURI { + + public static final String UI_URI = "/swagger-ui.html"; + public static final String API_DOCS_URI = "/v2/api-docs"; + public static final String WEB_JARS = "/webjars"; + public static final String RESOURCES = "/swagger-resources"; + public static final String FAVICON = "/favicon"; + +} diff --git a/src/main/java/kr/bb/apigateway/social/dto/SocialLoginRequestCommand.java b/src/main/java/kr/bb/apigateway/social/dto/SocialLoginRequestCommand.java new file mode 100644 index 0000000..bcd4718 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/social/dto/SocialLoginRequestCommand.java @@ -0,0 +1,18 @@ +package kr.bb.apigateway.social.dto; + +import kr.bb.apigateway.common.valueobject.AuthId; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class SocialLoginRequestCommand { + private AuthId socialId; + private String email; + private String phoneNumber; + private String nickname; +} diff --git a/src/main/java/kr/bb/apigateway/social/exception/SocialAuthException.java b/src/main/java/kr/bb/apigateway/social/exception/SocialAuthException.java new file mode 100644 index 0000000..8c8fe82 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/social/exception/SocialAuthException.java @@ -0,0 +1,16 @@ +package kr.bb.apigateway.social.exception; + +import lombok.Getter; + + +@Getter +public class SocialAuthException extends RuntimeException{ + + public SocialAuthException(String message) { + super(message); + } + + public SocialAuthException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationFilter.java new file mode 100644 index 0000000..76264dc --- /dev/null +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationFilter.java @@ -0,0 +1,38 @@ +package kr.bb.apigateway.social.filter; + + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import kr.bb.apigateway.common.security.SecurityContextUtil; +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.valueobject.Role; +import kr.bb.apigateway.social.exception.SocialAuthException; +import lombok.RequiredArgsConstructor; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +public class SocialAuthorizationFilter extends OncePerRequestFilter { + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String requestURI = request.getRequestURI(); + return !requestURI.contains("/social") || requestURI.contains("/social/login"); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + if (!Role.ROLE_SOCIAL_USER.name().equals(ExtractAuthorizationTokenUtil.extractRole(request))) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + throw new SocialAuthException("유저의 권한이 없습니다."); + } + SecurityContextUtil.setSecurityContextWithUserId( + ExtractAuthorizationTokenUtil.extractUserId(request)); + doFilter(request, response, filterChain); + } + + +} diff --git a/src/main/java/kr/bb/apigateway/store/exception/StoreManagerAuthException.java b/src/main/java/kr/bb/apigateway/store/exception/StoreManagerAuthException.java new file mode 100644 index 0000000..0241cc4 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/store/exception/StoreManagerAuthException.java @@ -0,0 +1,12 @@ +package kr.bb.apigateway.store.exception; + +public class StoreManagerAuthException extends + RuntimeException { + + public StoreManagerAuthException() { + } + + public StoreManagerAuthException(String message) { + super(message); + } +} diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreMangerAuthorizationFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreMangerAuthorizationFilter.java new file mode 100644 index 0000000..b7f07a6 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreMangerAuthorizationFilter.java @@ -0,0 +1,47 @@ +package kr.bb.apigateway.store.filter; + + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import kr.bb.apigateway.common.security.SecurityContextUtil; +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.store.exception.StoreManagerAuthException; +import kr.bb.apigateway.store.valueobject.StoreManagerStatus; +import lombok.RequiredArgsConstructor; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +public class StoreMangerAuthorizationFilter extends OncePerRequestFilter { + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + return !requestURI.contains("/stores") || requestURI.contains("/stores/login") || + requestURI.contains("/stores/signup") || + requestURI.contains("/stores/emails"); + } + + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals( + ExtractAuthorizationTokenUtil.extractRole(request))) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + throw new StoreManagerAuthException("시스템 관리자의 승인을 기다려주세요"); + } else if (StoreManagerStatus.ROLE_STORE_MANAGER_DENIED.name() + .equals(ExtractAuthorizationTokenUtil.extractRole(request))) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + throw new StoreManagerAuthException("잘못된 사업자등록증 번호입니다."); + } + SecurityContextUtil.setSecurityContextWithUserId( + ExtractAuthorizationTokenUtil.extractUserId(request)); + doFilter(request, response, filterChain); + } + + +} diff --git a/src/main/java/kr/bb/apigateway/store/valueobject/StoreId.java b/src/main/java/kr/bb/apigateway/store/valueobject/StoreId.java new file mode 100644 index 0000000..ecc9297 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/store/valueobject/StoreId.java @@ -0,0 +1,9 @@ +package kr.bb.apigateway.store.valueobject; + +import kr.bb.apigateway.common.valueobject.BaseId; +import lombok.experimental.SuperBuilder; + +@SuperBuilder +public class StoreId extends BaseId { + +} diff --git a/src/main/java/kr/bb/apigateway/store/valueobject/StoreManagerStatus.java b/src/main/java/kr/bb/apigateway/store/valueobject/StoreManagerStatus.java new file mode 100644 index 0000000..cbc8d03 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/store/valueobject/StoreManagerStatus.java @@ -0,0 +1,7 @@ +package kr.bb.apigateway.store.valueobject; + +public enum StoreManagerStatus { + ROLE_STORE_MANAGER_PERMITTED, + ROLE_STORE_MANAGER_PENDING, + ROLE_STORE_MANAGER_DENIED, +} diff --git a/src/main/java/kr/bb/apigateway/systsem/exception/SystemAdminAuthException.java b/src/main/java/kr/bb/apigateway/systsem/exception/SystemAdminAuthException.java new file mode 100644 index 0000000..c47f619 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/systsem/exception/SystemAdminAuthException.java @@ -0,0 +1,12 @@ +package kr.bb.apigateway.systsem.exception; + +public class SystemAdminAuthException extends + RuntimeException { + + public SystemAdminAuthException() { + } + + public SystemAdminAuthException(String message) { + super(message); + } +} diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationFilter.java new file mode 100644 index 0000000..30c2027 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationFilter.java @@ -0,0 +1,36 @@ +package kr.bb.apigateway.systsem.filter; + + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import kr.bb.apigateway.common.security.SecurityContextUtil; +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.valueobject.Role; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +public class SystemAdminAuthorizationFilter extends OncePerRequestFilter { + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String requestURI = request.getRequestURI(); + return !requestURI.contains("/admin") || requestURI.contains("/admin/login"); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + if (!Role.ROLE_SYSTEM_ADMIN.name().equals(ExtractAuthorizationTokenUtil.extractRole(request))) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + throw new InsufficientAuthenticationException("시스템 관리자의 권한이 없습니다. 시스템 관리자로 로그인해주세요"); + } + SecurityContextUtil.setSecurityContextWithUserId( + ExtractAuthorizationTokenUtil.extractUserId(request)); + doFilter(request, response, filterChain); + } +} From 5ebd73661fd41be1bfc30c6cd71b470662cf7be8 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:29:43 +0900 Subject: [PATCH 08/42] refactor: refactor Extract token util --- .../common/JwtAuthenticationFilter.java | 68 ------------------- .../filter/JwtValidationGatewayFilter.java | 57 ++++++++++++++++ .../IssueRefreshRefreshTokenInCookie.java | 12 ++-- .../common/security/RefreshTokenStrategy.java | 1 - .../util/ExtractAuthorizationTokenUtil.java | 15 ++-- .../SocialAuthorizationGatewayFilter.java | 5 ++ .../StoreAuthorizationGatewayFilter.java | 5 ++ 7 files changed, 80 insertions(+), 83 deletions(-) delete mode 100644 src/main/java/kr/bb/apigateway/common/JwtAuthenticationFilter.java create mode 100644 src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java create mode 100644 src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java create mode 100644 src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java diff --git a/src/main/java/kr/bb/apigateway/common/JwtAuthenticationFilter.java b/src/main/java/kr/bb/apigateway/common/JwtAuthenticationFilter.java deleted file mode 100644 index 0c707b5..0000000 --- a/src/main/java/kr/bb/apigateway/common/JwtAuthenticationFilter.java +++ /dev/null @@ -1,68 +0,0 @@ -package kr.bb.apigateway.common; - -import io.jsonwebtoken.ExpiredJwtException; -import java.io.IOException; -import javax.security.sasl.AuthenticationException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; -import kr.bb.apigateway.common.util.JwtUtil; -import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; -import kr.bb.apigateway.common.valueobject.JWTAuthenticationShouldNotFilterAntMatcher; -import kr.bb.apigateway.common.valueobject.KakaoOAuthURLAntURI; -import kr.bb.apigateway.common.valueobject.SwaggerRequestURI; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -@RequiredArgsConstructor -@Component -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final RedisBlackListTokenUtil redisBlackListTokenUtil; - - private boolean shouldNotFilterSwaggerURI(HttpServletRequest request) { - String requestURI = request.getRequestURI(); - return requestURI.contains(SwaggerRequestURI.UI_URI) || requestURI.contains(SwaggerRequestURI.API_DOCS_URI) - || requestURI.contains(SwaggerRequestURI.WEB_JARS) || requestURI.contains(SwaggerRequestURI.FAVICON) - || requestURI.contains(SwaggerRequestURI.RESOURCES); - } - - private boolean shouldNotFilterKakaoOauth2(HttpServletRequest request) { - - String requestURI = request.getRequestURI(); - return requestURI.contains(KakaoOAuthURLAntURI.KAPI) - || requestURI.contains(KakaoOAuthURLAntURI.KAUTH) || requestURI.contains(KakaoOAuthURLAntURI.REDIRECT) - || requestURI.contains(KakaoOAuthURLAntURI.OAUTH); - } - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - String requestURI = request.getRequestURI(); - return shouldNotFilterSwaggerURI(request) || shouldNotFilterKakaoOauth2(request) - || requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.SIGNUP_ANT) - || requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.LOGIN_ANT) - || requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.EMAIL_ANT); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - String token = ExtractAuthorizationTokenUtil.extractToken(request); - if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { - throw new AuthenticationException("해당 토큰은 이미 로그아웃 처리된 토큰이라 사용할 수 없는 토큰입니다."); - } - try { - JwtUtil.isTokenValid(token); - } catch (ExpiredJwtException e) { - throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "만료된 토큰입니다. Refresh토큰을 확인하세요"); - } - filterChain.doFilter(request, response); - } - - - - -} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java new file mode 100644 index 0000000..dbf7b48 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java @@ -0,0 +1,57 @@ +package kr.bb.apigateway.common.filter; + +import io.jsonwebtoken.ExpiredJwtException; +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.util.JwtUtil; +import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; +import kr.bb.apigateway.common.valueobject.JWTAuthenticationShouldNotFilterAntMatcher; +import kr.bb.apigateway.common.valueobject.SwaggerRequestURI; +import lombok.RequiredArgsConstructor; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@Component +public class JwtValidationGatewayFilter implements GlobalFilter { + + private final RedisBlackListTokenUtil redisBlackListTokenUtil; + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String requestURI = request.getURI().getPath(); + + if (shouldNotFilter(requestURI)) { + return chain.filter(exchange); + } + + String token = ExtractAuthorizationTokenUtil.extractToken(exchange.getRequest()); + if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + return response.setComplete(); + } + + try { + JwtUtil.isTokenValid(token); + } catch (ExpiredJwtException e) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + return response.setComplete(); + } + + return chain.filter(exchange); + } + + private boolean shouldNotFilter(String requestURI) { + return requestURI.contains(SwaggerRequestURI.UI_URI) || + requestURI.contains(SwaggerRequestURI.API_DOCS_URI) || + requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.EMAIL_ANT); + } +} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java b/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java index cf8afa4..4cc00fb 100644 --- a/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java +++ b/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java @@ -1,11 +1,11 @@ package kr.bb.apigateway.common.security; -import com.bit.lot.flower.auth.common.util.CookieUtil; -import com.bit.lot.flower.auth.common.util.JwtUtil; -import com.bit.lot.flower.auth.common.util.RedisRefreshTokenUtil; -import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; import java.time.Duration; import javax.servlet.http.HttpServletResponse; +import kr.bb.apigateway.common.util.CookieUtil; +import kr.bb.apigateway.common.util.JwtUtil; +import kr.bb.apigateway.common.util.RedisRefreshTokenUtil; +import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -38,9 +38,5 @@ public void invalidateRefreshToken(String id, HttpServletResponse response) { redisRefreshTokenUtil.deleteRefreshToken(id); } - @Override - public void renewRefreshToken(String id, HttpServletResponse response) { - - } } diff --git a/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java b/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java index df9856c..7498831 100644 --- a/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java +++ b/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java @@ -7,5 +7,4 @@ public interface RefreshTokenStrategy { public void createRefreshToken(String id, HttpServletResponse response); public void invalidateRefreshToken(String id, HttpServletResponse response); - public void renewRefreshToken(String id, HttpServletResponse response); } \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java index 00c7352..694e48e 100644 --- a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java @@ -1,8 +1,10 @@ package kr.bb.apigateway.common.util; -import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; import io.jsonwebtoken.Claims; import javax.servlet.http.HttpServletRequest; +import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; public class ExtractAuthorizationTokenUtil { @@ -11,18 +13,19 @@ private ExtractAuthorizationTokenUtil() { } - public static String extractToken(HttpServletRequest request) { - - String authorizationHeader = request.getHeader( + public static String extractToken(ServerHttpRequest request) { + HttpHeaders headers = request.getHeaders(); + String authorizationHeader = headers.getFirst( SecurityPolicyStaticValue.TOKEN_AUTHORIZAION_HEADER_NAME); - if (authorizationHeader != null && authorizationHeader.startsWith(SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_PREFIX)) { + if (authorizationHeader != null && authorizationHeader.startsWith( + SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_PREFIX)) { return authorizationHeader.substring(7); } else { throw new IllegalArgumentException("토큰 정보를 찾을 수 없습니다. 로그인을 먼저 해주세요."); } } - public static String extractUserId(HttpServletRequest request){ + public static String extractUserId(HttpServletRequest request) { String token = extractToken(request); return JwtUtil.extractSubject(token); } diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java new file mode 100644 index 0000000..aaacc3d --- /dev/null +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java @@ -0,0 +1,5 @@ +package kr.bb.apigateway.social.filter; + +public class SocialAuthorizationGatewayFilter { + +} diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java new file mode 100644 index 0000000..6e6946d --- /dev/null +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java @@ -0,0 +1,5 @@ +package kr.bb.apigateway.store.filter; + +public class StoreAuthorizationGatewayFilter { + +} From 69bfb3f579c772baeacddaff65674337f681a69d Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:42:10 +0900 Subject: [PATCH 09/42] feat: add the filters which needs for routing --- .../filter/JwtValidationGatewayFilter.java | 14 ++++- .../util/ExtractAuthorizationTokenUtil.java | 4 +- .../filter/SocialAuthorizationFilter.java | 38 -------------- .../SocialAuthorizationGatewayFilter.java | 45 +++++++++++++++- .../StoreAuthorizationGatewayFilter.java | 51 ++++++++++++++++++- .../StoreMangerAuthorizationFilter.java | 47 ----------------- .../SystemAdminAuthorizationFilter.java | 36 ------------- ...SystemAdminAuthorizationGatewayFilter.java | 44 ++++++++++++++++ 8 files changed, 153 insertions(+), 126 deletions(-) delete mode 100644 src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationFilter.java delete mode 100644 src/main/java/kr/bb/apigateway/store/filter/StoreMangerAuthorizationFilter.java delete mode 100644 src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationFilter.java create mode 100644 src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java index dbf7b48..cce4bec 100644 --- a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java @@ -46,7 +46,7 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return response.setComplete(); } - return chain.filter(exchange); + return chain.filter(addUserIdHeaderAtRequest(exchange,JwtUtil.extractSubject(token))); } private boolean shouldNotFilter(String requestURI) { @@ -54,4 +54,16 @@ private boolean shouldNotFilter(String requestURI) { requestURI.contains(SwaggerRequestURI.API_DOCS_URI) || requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.EMAIL_ANT); } + + private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { + ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() + .header("userId", userId) + .build(); + + ServerWebExchange modifiedExchange = exchange.mutate() + .request(modifiedRequest) + .build(); + + return modifiedExchange; + } } \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java index 694e48e..ac60e59 100644 --- a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java @@ -25,12 +25,12 @@ public static String extractToken(ServerHttpRequest request) { } } - public static String extractUserId(HttpServletRequest request) { + public static String extractUserId(ServerHttpRequest request) { String token = extractToken(request); return JwtUtil.extractSubject(token); } - public static String extractRole(HttpServletRequest request) { + public static String extractRole(ServerHttpRequest request) { String token = ExtractAuthorizationTokenUtil.extractToken(request); Claims claims = JwtUtil.extractClaims(token); return (String) claims.get(SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME); diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationFilter.java deleted file mode 100644 index 76264dc..0000000 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationFilter.java +++ /dev/null @@ -1,38 +0,0 @@ -package kr.bb.apigateway.social.filter; - - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import kr.bb.apigateway.common.security.SecurityContextUtil; -import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; -import kr.bb.apigateway.common.valueobject.Role; -import kr.bb.apigateway.social.exception.SocialAuthException; -import lombok.RequiredArgsConstructor; -import org.springframework.web.filter.OncePerRequestFilter; - -@RequiredArgsConstructor -public class SocialAuthorizationFilter extends OncePerRequestFilter { - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { - String requestURI = request.getRequestURI(); - return !requestURI.contains("/social") || requestURI.contains("/social/login"); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - if (!Role.ROLE_SOCIAL_USER.name().equals(ExtractAuthorizationTokenUtil.extractRole(request))) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - throw new SocialAuthException("유저의 권한이 없습니다."); - } - SecurityContextUtil.setSecurityContextWithUserId( - ExtractAuthorizationTokenUtil.extractUserId(request)); - doFilter(request, response, filterChain); - } - - -} diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java index aaacc3d..edc0b20 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java @@ -1,5 +1,48 @@ package kr.bb.apigateway.social.filter; -public class SocialAuthorizationGatewayFilter { +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.valueobject.Role; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Component +public class SocialAuthorizationGatewayFilter implements GlobalFilter { + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String requestURI = request.getURI().getPath(); + + if (shouldNotFilter(requestURI)) { + return chain.filter(exchange); + } + + if (!isAuthorizedUser(exchange)) { + return handleUnauthenticatedUser(exchange); + } + + return chain.filter(exchange); + } + + private boolean shouldNotFilter(String requestURI) { + return !requestURI.contains("/social") || requestURI.contains("/social/login"); + } + + private boolean isAuthorizedUser(ServerWebExchange exchange) { + String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); + return Role.ROLE_SOCIAL_USER.name().equals(role); + } + + private Mono handleUnauthenticatedUser(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + return response.setComplete(); + } } diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java index 6e6946d..30fe86f 100644 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java @@ -1,5 +1,54 @@ package kr.bb.apigateway.store.filter; -public class StoreAuthorizationGatewayFilter { +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.store.valueobject.StoreManagerStatus; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class StoreAuthorizationGatewayFilter implements GlobalFilter { + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String requestURI = request.getURI().getPath(); + + if (shouldNotFilter(requestURI)) { + return chain.filter(exchange); + } + + String role = getRoleFromHeader(exchange); + if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals(role)) { + return handlePendingApproval(exchange); + } else if (StoreManagerStatus.ROLE_STORE_MANAGER_DENIED.name().equals(role)) { + return handleDeniedRole(exchange); + } + return chain.filter(exchange); + } + + private boolean shouldNotFilter(String requestURI) { + return !requestURI.contains("/stores") || requestURI.contains("/stores/login") || + requestURI.contains("/stores/signup") || requestURI.contains("/stores/emails"); + } + + private String getRoleFromHeader(ServerWebExchange exchange) { + return ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); + } + + private Mono handlePendingApproval(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + return response.setComplete(); + } + + private Mono handleDeniedRole(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.FORBIDDEN); + return response.setComplete(); + } } diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreMangerAuthorizationFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreMangerAuthorizationFilter.java deleted file mode 100644 index b7f07a6..0000000 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreMangerAuthorizationFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -package kr.bb.apigateway.store.filter; - - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import kr.bb.apigateway.common.security.SecurityContextUtil; -import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; -import kr.bb.apigateway.store.exception.StoreManagerAuthException; -import kr.bb.apigateway.store.valueobject.StoreManagerStatus; -import lombok.RequiredArgsConstructor; -import org.springframework.web.filter.OncePerRequestFilter; - -@RequiredArgsConstructor -public class StoreMangerAuthorizationFilter extends OncePerRequestFilter { - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - String requestURI = request.getRequestURI(); - return !requestURI.contains("/stores") || requestURI.contains("/stores/login") || - requestURI.contains("/stores/signup") || - requestURI.contains("/stores/emails"); - } - - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - - if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals( - ExtractAuthorizationTokenUtil.extractRole(request))) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - throw new StoreManagerAuthException("시스템 관리자의 승인을 기다려주세요"); - } else if (StoreManagerStatus.ROLE_STORE_MANAGER_DENIED.name() - .equals(ExtractAuthorizationTokenUtil.extractRole(request))) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - throw new StoreManagerAuthException("잘못된 사업자등록증 번호입니다."); - } - SecurityContextUtil.setSecurityContextWithUserId( - ExtractAuthorizationTokenUtil.extractUserId(request)); - doFilter(request, response, filterChain); - } - - -} diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationFilter.java deleted file mode 100644 index 30c2027..0000000 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationFilter.java +++ /dev/null @@ -1,36 +0,0 @@ -package kr.bb.apigateway.systsem.filter; - - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import kr.bb.apigateway.common.security.SecurityContextUtil; -import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; -import kr.bb.apigateway.common.valueobject.Role; -import lombok.RequiredArgsConstructor; -import org.springframework.security.authentication.InsufficientAuthenticationException; -import org.springframework.web.filter.OncePerRequestFilter; - -@RequiredArgsConstructor -public class SystemAdminAuthorizationFilter extends OncePerRequestFilter { - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { - String requestURI = request.getRequestURI(); - return !requestURI.contains("/admin") || requestURI.contains("/admin/login"); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - if (!Role.ROLE_SYSTEM_ADMIN.name().equals(ExtractAuthorizationTokenUtil.extractRole(request))) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - throw new InsufficientAuthenticationException("시스템 관리자의 권한이 없습니다. 시스템 관리자로 로그인해주세요"); - } - SecurityContextUtil.setSecurityContextWithUserId( - ExtractAuthorizationTokenUtil.extractUserId(request)); - doFilter(request, response, filterChain); - } -} diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java new file mode 100644 index 0000000..1aa65c5 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java @@ -0,0 +1,44 @@ +package kr.bb.apigateway.systsem.filter; + +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.valueobject.Role; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class SystemAdminAuthorizationGatewayFilter implements GlobalFilter { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String requestURI = request.getURI().getPath(); + + if (shouldNotFilter(requestURI)) { + return chain.filter(exchange); + } + + if (!isSystemAdmin(exchange)) { + return handleUnauthorized(exchange); + } + return chain.filter(exchange); + } + + private boolean shouldNotFilter(String requestURI) { + return !requestURI.contains("/admin") || requestURI.contains("/admin/login"); + } + + private boolean isSystemAdmin(ServerWebExchange exchange) { + String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); + return Role.ROLE_SYSTEM_ADMIN.name().equals(role); + } + + + private Mono handleUnauthorized(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + return response.setComplete(); + } +} From b535681c88d84dd51ad753b67fb0765ed5c51606 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:49:13 +0900 Subject: [PATCH 10/42] fix: fix wrong path of package --- .../common/dto/RenewAccessTokenDto.java | 5 ++- .../apigateway/common/entity/BaseEntity.java | 28 ------------- .../JwtAccessTokenCreateProcessor.java | 2 +- ...AccessTokenInRedisBlackListWhenLogout.java | 5 ++- .../SystemAuthenticationSuccessHandler.java | 7 ++-- .../service/RenewRefreshTokenStrategy.java | 15 ------- ...enRefreshTokenIsMatchedCookieAndRedis.java | 42 ------------------- .../kr/bb/apigateway/common/util/JwtUtil.java | 2 +- 8 files changed, 12 insertions(+), 94 deletions(-) delete mode 100644 src/main/java/kr/bb/apigateway/common/entity/BaseEntity.java delete mode 100644 src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenStrategy.java delete mode 100644 src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java diff --git a/src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java b/src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java index fdd8e69..e75cdd4 100644 --- a/src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java +++ b/src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java @@ -1,7 +1,8 @@ package kr.bb.apigateway.common.dto; -import com.bit.lot.flower.auth.common.valueobject.BaseId; -import com.bit.lot.flower.auth.common.valueobject.Role; + +import kr.bb.apigateway.common.valueobject.BaseId; +import kr.bb.apigateway.common.valueobject.Role; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/bb/apigateway/common/entity/BaseEntity.java b/src/main/java/kr/bb/apigateway/common/entity/BaseEntity.java deleted file mode 100644 index b028ab5..0000000 --- a/src/main/java/kr/bb/apigateway/common/entity/BaseEntity.java +++ /dev/null @@ -1,28 +0,0 @@ -package kr.bb.apigateway.common.entity; - -import java.time.LocalDateTime; -import javax.persistence.Column; -import javax.persistence.EntityListeners; -import javax.persistence.MappedSuperclass; -import lombok.Getter; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -@Getter -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -public class BaseEntity { - - @CreatedDate - @Column(name = "created_at") - private LocalDateTime createdAt; - - @LastModifiedDate - @Column(name = "updated_at") - private LocalDateTime updatedAt; - - @Column(name = "is_deleted") - private Boolean isDeleted; -} - diff --git a/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java b/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java index 868437b..be62069 100644 --- a/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java +++ b/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java @@ -1,7 +1,7 @@ package kr.bb.apigateway.common.security; -import com.bit.lot.flower.auth.common.util.JwtUtil; import java.util.Map; +import kr.bb.apigateway.common.util.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java b/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java index 354a986..64d59dd 100644 --- a/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java +++ b/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java @@ -1,7 +1,8 @@ package kr.bb.apigateway.common.security; -import com.bit.lot.flower.auth.common.util.RedisBlackListTokenUtil; -import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; + +import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; +import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java b/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java index 43e6bf7..a0d3004 100644 --- a/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java +++ b/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java @@ -1,14 +1,15 @@ package kr.bb.apigateway.common.security; -import com.bit.lot.flower.auth.common.util.JwtUtil; -import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; -import com.bit.lot.flower.auth.social.valueobject.AuthId; + import java.io.IOException; import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import kr.bb.apigateway.common.util.JwtUtil; +import kr.bb.apigateway.common.valueobject.AuthId; +import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; diff --git a/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenStrategy.java b/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenStrategy.java deleted file mode 100644 index db673cc..0000000 --- a/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenStrategy.java +++ /dev/null @@ -1,15 +0,0 @@ -package kr.bb.apigateway.common.service; - -import com.bit.lot.flower.auth.common.valueobject.BaseId; -import com.bit.lot.flower.auth.common.valueobject.Role; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.springframework.stereotype.Service; - - -@Service -public interface RenewRefreshTokenStrategy { - - String renew(ID id, Role role, HttpServletRequest request, - HttpServletResponse response); -} diff --git a/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java b/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java deleted file mode 100644 index 07251e7..0000000 --- a/src/main/java/kr/bb/apigateway/common/service/RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis.java +++ /dev/null @@ -1,42 +0,0 @@ -package kr.bb.apigateway.common.service; - -import com.bit.lot.flower.auth.common.security.TokenHandler; -import com.bit.lot.flower.auth.common.util.CookieUtil; -import com.bit.lot.flower.auth.common.util.JwtUtil; -import com.bit.lot.flower.auth.common.util.RedisRefreshTokenUtil; -import com.bit.lot.flower.auth.common.valueobject.BaseId; -import com.bit.lot.flower.auth.common.valueobject.Role; -import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -@RequiredArgsConstructor -@Service -public class RenewRefreshTokenWhenRefreshTokenIsMatchedCookieAndRedis implements - RenewRefreshTokenStrategy { - - private final RedisRefreshTokenUtil redisRefreshTokenUtil; - private final TokenHandler tokenHandler; - @Value("${cookie.refresh.token.name}") - private String refreshCookieName; - - @Override - public String renew(ID id, Role role, HttpServletRequest request, - HttpServletResponse response) { - String refreshTokenAtCookie = CookieUtil.getCookieValue(request, refreshCookieName); - if (redisRefreshTokenUtil.getRefreshToken(refreshTokenAtCookie) == null) { - throw new IllegalArgumentException("유효한 접근이 아닙니다. Refresh토큰을 확인해주세요"); - } - return tokenHandler.createToken(id.getValue().toString(), createClaimsRoleMap(role), response); - } - - private Map createClaimsRoleMap(Role role) { - return JwtUtil.addClaims( - SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME, role); - } - -} diff --git a/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java index 56da2d2..04f8f6a 100644 --- a/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java @@ -1,7 +1,6 @@ package kr.bb.apigateway.common.util; -import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; @@ -14,6 +13,7 @@ import java.util.Map; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; +import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; import org.springframework.stereotype.Component; @Component From cdae665793563ff9fb5bc7767c1db72f489d6323 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:35:22 +0900 Subject: [PATCH 11/42] add: add the kafka dependecy --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 130010a..7f2adca 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,7 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.kafka:spring-kafka' } dependencyManagement { From 1cdda548c86869a6f6c0656a47ac27712620d01d Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 7 Dec 2023 19:08:49 +0900 Subject: [PATCH 12/42] fix: fix JwtUtil Access token expiration time --- src/main/java/kr/bb/apigateway/common/util/JwtUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java index 04f8f6a..ef5446b 100644 --- a/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java @@ -36,7 +36,7 @@ public static String generateAccessTokenWithClaims(String subject, return Jwts.builder() .setSubject(subject) .setIssuedAt(now) - .setExpiration(Date.from(Instant.now().plusMillis( + .setExpiration(Date.from(Instant.now().plusSeconds( Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME)))) .signWith(accessSecret) .addClaims(claimsList) From 8477d8232d7221a0ae5cbf4b596bd4dfc9148c72 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 7 Dec 2023 19:35:08 +0900 Subject: [PATCH 13/42] fix: fix the JwtUtil use second --- src/main/java/kr/bb/apigateway/common/util/JwtUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java index ef5446b..3ad15b9 100644 --- a/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java @@ -57,7 +57,7 @@ public static Map addClaims(String id, Object value) { return Jwts.builder() .setSubject(subject) .setIssuedAt(now) - .setExpiration(Date.from(Instant.now().plusMillis( + .setExpiration(Date.from(Instant.now().plusSeconds( Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME)))) .signWith(accessSecret) .compact(); @@ -70,7 +70,7 @@ public static String generateRefreshToken(String subject) { return Jwts.builder() .setSubject(subject) .setIssuedAt(now) - .setExpiration(Date.from(Instant.now().plusMillis( + .setExpiration(Date.from(Instant.now().plusSeconds( Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME)))) .signWith(refreshSecret) .compact(); From 6bef66187d0d3c09451bdf111a8e128ce56b5376 Mon Sep 17 00:00:00 2001 From: nowgnas Date: Mon, 11 Dec 2023 11:07:36 +0900 Subject: [PATCH 14/42] :wrench: Amend application yml --- src/main/resources/application-dev.yml | 16 ++++++++++++++++ .../{application.yml => application-local.yml} | 2 +- src/main/resources/application-prod.yml | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/application-dev.yml rename src/main/resources/{application.yml => application-local.yml} (88%) create mode 100644 src/main/resources/application-prod.yml diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..d17808f --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,16 @@ +server: + port: 8000 +spring: + application: + name: apigateway-service + config: + activate: + on-profile: dev + import: optional:configserver:http://config-service:8888 +management: + endpoints: + web: + exposure: + include: + - "refresh" + - "bus-refresh" \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application-local.yml similarity index 88% rename from src/main/resources/application.yml rename to src/main/resources/application-local.yml index 94b9fe0..978cb7f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application-local.yml @@ -5,7 +5,7 @@ spring: name: apigateway-service config: activate: - on-profile: local, dev, local + on-profile: local import: optional:configserver:http://localhost:8888 management: endpoints: diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..4dc4473 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,16 @@ +server: + port: 8000 +spring: + application: + name: apigateway-service + config: + activate: + on-profile: prod + import: optional:configserver:http://config-service:8888 +management: + endpoints: + web: + exposure: + include: + - "refresh" + - "bus-refresh" \ No newline at end of file From 607cfdaca629db43b1b833da6508dfc53072e9b3 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:34:23 +0900 Subject: [PATCH 15/42] add: add the redis configuration --- build.gradle | 1 + src/main/resources/application-dev.yml | 13 ++++++++++++- src/main/resources/application-local.yml | 16 ++++++++++++++-- src/main/resources/application-prod.yml | 11 ++++++++++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 7f2adca..a902859 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.kafka:spring-kafka' + implementation 'redis.clients:jedis' } dependencyManagement { diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index d17808f..05b5d36 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,6 +1,8 @@ server: port: 8000 spring: + main: + web-application-type: reactive application: name: apigateway-service config: @@ -13,4 +15,13 @@ management: exposure: include: - "refresh" - - "bus-refresh" \ No newline at end of file + - "bus-refresh" + +cookie: + refresh: + http: + domain: http://localhost:8000 + token: + name: refresh-cookie + + diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 978cb7f..0a73976 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,8 +1,14 @@ server: port: 8000 spring: + main: + web-application-type: reactive + redis: + host: localhost + port: 6379 + application: - name: apigateway-service + name: apigateway-servixce config: activate: on-profile: local @@ -13,4 +19,10 @@ management: exposure: include: - "refresh" - - "bus-refresh" \ No newline at end of file + - "bus-refresh" +cookie: + refresh: + http: + domain: http://localhost:8000 + token: + name: refresh-cookie \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 4dc4473..2d1e4fb 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -1,6 +1,8 @@ server: port: 8000 spring: + main: + web-application-type: reactive application: name: apigateway-service config: @@ -13,4 +15,11 @@ management: exposure: include: - "refresh" - - "bus-refresh" \ No newline at end of file + - "bus-refresh" + +cookie: + refresh: + http: + domain: http://localhost:8000 + token: + name: refresh-cookie From be383dd771022586f03f3c14151cb6bdc800b130 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Mon, 11 Dec 2023 15:50:39 +0900 Subject: [PATCH 16/42] add: add the cofig --- .../bb/apigateway/common/SecurityConfig.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/kr/bb/apigateway/common/SecurityConfig.java diff --git a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java new file mode 100644 index 0000000..cca5fdf --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java @@ -0,0 +1,20 @@ +package kr.bb.apigateway.common; + +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.web.server.SecurityWebFilterChain; + +@EnableWebFluxSecurity +@EnableReactiveMethodSecurity +public class SecurityConfig { + + @Bean + public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + http + .csrf(csrf -> csrf.disable()); + return http.build(); + + } +} From f7c9a84b9d32fde648e1a86c3b34b368d26d90cc Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:05:28 +0900 Subject: [PATCH 17/42] add: add the cloud config yml --- .../bb/apigateway/common/LoggingFilter.java | 31 +++++++++++++++++++ src/main/resources/application-local.yml | 21 +++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/main/java/kr/bb/apigateway/common/LoggingFilter.java diff --git a/src/main/java/kr/bb/apigateway/common/LoggingFilter.java b/src/main/java/kr/bb/apigateway/common/LoggingFilter.java new file mode 100644 index 0000000..77a6058 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/LoggingFilter.java @@ -0,0 +1,31 @@ +package kr.bb.apigateway.common; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; + +import java.net.URI; +import java.util.Collections; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Slf4j +public class LoggingFilter implements GlobalFilter { + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + Set uris = exchange.getAttributeOrDefault(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, + Collections.emptySet()); + String originalUri = (uris.isEmpty()) ? "Unknown" : uris.iterator().next().toString(); + Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); + URI routeUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); + log.info("Incoming request " + originalUri + " is routed to id: " + route.getId() + + ", uri:" + routeUri); + return chain.filter(exchange); + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 0a73976..c0aef78 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,14 +1,19 @@ server: port: 8000 spring: + cloud: + gateway: + httpclient: + wiretap: true + httpserver: + wiretap: true main: web-application-type: reactive redis: host: localhost port: 6379 - application: - name: apigateway-servixce + name: apigateway-service config: activate: on-profile: local @@ -25,4 +30,14 @@ cookie: http: domain: http://localhost:8000 token: - name: refresh-cookie \ No newline at end of file + name: refresh-cookie + +logging: + level: + reactor: + netty: INFO + org: + springframework: + cloud: + gateway: TRACE + From 26046027116a704902d698d9d868fdffc4c47e0f Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Wed, 13 Dec 2023 09:43:12 +0900 Subject: [PATCH 18/42] remove: remove the shouldNoFilter at Filter --- .../common/util/ExtractAuthorizationTokenUtil.java | 1 - .../filter/SocialAuthorizationGatewayFilter.java | 9 --------- .../store/filter/StoreAuthorizationGatewayFilter.java | 9 --------- .../filter/SystemAdminAuthorizationGatewayFilter.java | 10 ---------- 4 files changed, 29 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java index ac60e59..e5a8819 100644 --- a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java @@ -1,7 +1,6 @@ package kr.bb.apigateway.common.util; import io.jsonwebtoken.Claims; -import javax.servlet.http.HttpServletRequest; import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java index edc0b20..db06597 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java @@ -17,12 +17,6 @@ public class SocialAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().getPath(); - - if (shouldNotFilter(requestURI)) { - return chain.filter(exchange); - } if (!isAuthorizedUser(exchange)) { return handleUnauthenticatedUser(exchange); @@ -31,9 +25,6 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange); } - private boolean shouldNotFilter(String requestURI) { - return !requestURI.contains("/social") || requestURI.contains("/social/login"); - } private boolean isAuthorizedUser(ServerWebExchange exchange) { String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java index 30fe86f..06ed727 100644 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java @@ -14,12 +14,7 @@ public class StoreAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().getPath(); - if (shouldNotFilter(requestURI)) { - return chain.filter(exchange); - } String role = getRoleFromHeader(exchange); if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals(role)) { @@ -30,10 +25,6 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange); } - private boolean shouldNotFilter(String requestURI) { - return !requestURI.contains("/stores") || requestURI.contains("/stores/login") || - requestURI.contains("/stores/signup") || requestURI.contains("/stores/emails"); - } private String getRoleFromHeader(ServerWebExchange exchange) { return ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java index 1aa65c5..6ee356c 100644 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java @@ -13,12 +13,6 @@ public class SystemAdminAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().getPath(); - - if (shouldNotFilter(requestURI)) { - return chain.filter(exchange); - } if (!isSystemAdmin(exchange)) { return handleUnauthorized(exchange); @@ -26,10 +20,6 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange); } - private boolean shouldNotFilter(String requestURI) { - return !requestURI.contains("/admin") || requestURI.contains("/admin/login"); - } - private boolean isSystemAdmin(ServerWebExchange exchange) { String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); return Role.ROLE_SYSTEM_ADMIN.name().equals(role); From c5e67a85e67d9518c3250385b5e598865a1d7fff Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:26:45 +0900 Subject: [PATCH 19/42] Revert "remove: remove the shouldNoFilter at Filter" This reverts commit 26046027116a704902d698d9d868fdffc4c47e0f. --- .../common/util/ExtractAuthorizationTokenUtil.java | 1 + .../filter/SocialAuthorizationGatewayFilter.java | 9 +++++++++ .../store/filter/StoreAuthorizationGatewayFilter.java | 9 +++++++++ .../filter/SystemAdminAuthorizationGatewayFilter.java | 10 ++++++++++ 4 files changed, 29 insertions(+) diff --git a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java index e5a8819..ac60e59 100644 --- a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java @@ -1,6 +1,7 @@ package kr.bb.apigateway.common.util; import io.jsonwebtoken.Claims; +import javax.servlet.http.HttpServletRequest; import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java index db06597..edc0b20 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java @@ -17,6 +17,12 @@ public class SocialAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String requestURI = request.getURI().getPath(); + + if (shouldNotFilter(requestURI)) { + return chain.filter(exchange); + } if (!isAuthorizedUser(exchange)) { return handleUnauthenticatedUser(exchange); @@ -25,6 +31,9 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange); } + private boolean shouldNotFilter(String requestURI) { + return !requestURI.contains("/social") || requestURI.contains("/social/login"); + } private boolean isAuthorizedUser(ServerWebExchange exchange) { String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java index 06ed727..30fe86f 100644 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java @@ -14,7 +14,12 @@ public class StoreAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String requestURI = request.getURI().getPath(); + if (shouldNotFilter(requestURI)) { + return chain.filter(exchange); + } String role = getRoleFromHeader(exchange); if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals(role)) { @@ -25,6 +30,10 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange); } + private boolean shouldNotFilter(String requestURI) { + return !requestURI.contains("/stores") || requestURI.contains("/stores/login") || + requestURI.contains("/stores/signup") || requestURI.contains("/stores/emails"); + } private String getRoleFromHeader(ServerWebExchange exchange) { return ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java index 6ee356c..1aa65c5 100644 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java @@ -13,6 +13,12 @@ public class SystemAdminAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String requestURI = request.getURI().getPath(); + + if (shouldNotFilter(requestURI)) { + return chain.filter(exchange); + } if (!isSystemAdmin(exchange)) { return handleUnauthorized(exchange); @@ -20,6 +26,10 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange); } + private boolean shouldNotFilter(String requestURI) { + return !requestURI.contains("/admin") || requestURI.contains("/admin/login"); + } + private boolean isSystemAdmin(ServerWebExchange exchange) { String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); return Role.ROLE_SYSTEM_ADMIN.name().equals(role); From 59a211cf8a2d722ccc021bac645530a1684e74f8 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Wed, 13 Dec 2023 16:07:28 +0900 Subject: [PATCH 20/42] fix: fix the if statement --- .../social/filter/SocialAuthorizationGatewayFilter.java | 7 +++---- .../store/filter/StoreAuthorizationGatewayFilter.java | 9 ++++++++- .../filter/SystemAdminAuthorizationGatewayFilter.java | 5 +++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java index edc0b20..1d58111 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java @@ -21,14 +21,13 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { String requestURI = request.getURI().getPath(); if (shouldNotFilter(requestURI)) { - return chain.filter(exchange); + chain.filter(exchange); } - - if (!isAuthorizedUser(exchange)) { + else if (!isAuthorizedUser(exchange)) { return handleUnauthenticatedUser(exchange); } - return chain.filter(exchange); + return chain.filter(exchange); } private boolean shouldNotFilter(String requestURI) { diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java index 30fe86f..455ce33 100644 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java @@ -7,9 +7,11 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +@Component public class StoreAuthorizationGatewayFilter implements GlobalFilter { @Override @@ -18,9 +20,14 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { String requestURI = request.getURI().getPath(); if (shouldNotFilter(requestURI)) { - return chain.filter(exchange); + chain.filter(exchange); + } else { + return roleHandler(exchange,chain); } + return chain.filter(exchange); + } + private Mono roleHandler(ServerWebExchange exchange,GatewayFilterChain chain) { String role = getRoleFromHeader(exchange); if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals(role)) { return handlePendingApproval(exchange); diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java index 1aa65c5..a8e2f7e 100644 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java @@ -7,9 +7,11 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +@Component public class SystemAdminAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { @@ -19,8 +21,7 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { if (shouldNotFilter(requestURI)) { return chain.filter(exchange); } - - if (!isSystemAdmin(exchange)) { + else if (!isSystemAdmin(exchange)) { return handleUnauthorized(exchange); } return chain.filter(exchange); From d38f024bb4749960d366de7ec62d5d7d06b73ba3 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Wed, 13 Dec 2023 16:22:04 +0900 Subject: [PATCH 21/42] chore: add the log --- .../filter/SocialAuthorizationGatewayFilter.java | 15 +++++++++------ .../filter/StoreAuthorizationGatewayFilter.java | 5 ++++- .../SystemAdminAuthorizationGatewayFilter.java | 5 ++++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java index 1d58111..936ccda 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java @@ -3,6 +3,7 @@ import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; import kr.bb.apigateway.common.valueobject.Role; +import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.http.HttpStatus; @@ -12,26 +13,28 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +@Slf4j @Component public class SocialAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().getPath(); + String requestURI = request.getURI().toString(); + log.warn("-------------requestURI :" + requestURI); if (shouldNotFilter(requestURI)) { - chain.filter(exchange); - } - else if (!isAuthorizedUser(exchange)) { + chain.filter(exchange); + } else if (!isAuthorizedUser(exchange)) { return handleUnauthenticatedUser(exchange); } - return chain.filter(exchange); + return chain.filter(exchange); } private boolean shouldNotFilter(String requestURI) { - return !requestURI.contains("/social") || requestURI.contains("/social/login"); + return !requestURI.contains("/social") || requestURI.contains("/social/login") + || requestURI.contains("/oauth"); } private boolean isAuthorizedUser(ServerWebExchange exchange) { diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java index 455ce33..11ce88e 100644 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java @@ -2,6 +2,7 @@ import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; import kr.bb.apigateway.store.valueobject.StoreManagerStatus; +import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.http.HttpStatus; @@ -11,13 +12,15 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +@Slf4j @Component public class StoreAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().getPath(); + String requestURI = request.getURI().toString(); + log.warn("-------------requestURI :" +requestURI); if (shouldNotFilter(requestURI)) { chain.filter(exchange); diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java index a8e2f7e..0438da3 100644 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java @@ -2,6 +2,7 @@ import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; import kr.bb.apigateway.common.valueobject.Role; +import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.http.HttpStatus; @@ -11,12 +12,14 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +@Slf4j @Component public class SystemAdminAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().getPath(); + String requestURI = request.getURI().toString(); + log.warn("-------------requestURI :" +requestURI); if (shouldNotFilter(requestURI)) { return chain.filter(exchange); From db5b51e2fee48a452138ffe614ff907f1f095fe4 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Wed, 13 Dec 2023 16:37:08 +0900 Subject: [PATCH 22/42] fix: fix component to configuration --- .../bb/apigateway/common/SecurityConfig.java | 27 +++++++++++++++++++ .../SocialAuthorizationGatewayFilter.java | 2 -- ...SystemAdminAuthorizationGatewayFilter.java | 2 -- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java index cca5fdf..2f42e2f 100644 --- a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java +++ b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java @@ -1,6 +1,12 @@ package kr.bb.apigateway.common; +import kr.bb.apigateway.social.filter.SocialAuthorizationGatewayFilter; +import kr.bb.apigateway.store.filter.StoreAuthorizationGatewayFilter; +import kr.bb.apigateway.systsem.filter.SystemAdminAuthorizationGatewayFilter; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; +import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; @@ -17,4 +23,25 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) return http.build(); } + + @Order(1) + @Qualifier("socialAuthorizationGatewayFilter") + @Bean + public GlobalFilter socialAuthorizationGatewayFilter() { + return new SocialAuthorizationGatewayFilter(); + } + + @Order(0) + @Qualifier("systemAdminAuthorizationGatewayFilter") + @Bean + public GlobalFilter systemAdminAuthorizationGatewayFilter() { + return new SystemAdminAuthorizationGatewayFilter(); + } + + @Order(2) + @Qualifier("storeAuthorizationGatewayFilter") + @Bean + public GlobalFilter storeAuthorizationGatewayFilter() { + return new StoreAuthorizationGatewayFilter(); + } } diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java index 936ccda..3d8c5fc 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java @@ -9,12 +9,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Slf4j -@Component public class SocialAuthorizationGatewayFilter implements GlobalFilter { @Override diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java index 0438da3..7ab7137 100644 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java @@ -8,12 +8,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Slf4j -@Component public class SystemAdminAuthorizationGatewayFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { From 8e7a458792dd3a8c74e14486f106fcabe788e033 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:04:11 +0900 Subject: [PATCH 23/42] fix: fix the should be passed URI not come through the filters --- .../filter/JwtValidationGatewayFilter.java | 52 ++++++++++++------- .../SocialAuthorizationGatewayFilter.java | 2 +- .../StoreAuthorizationGatewayFilter.java | 1 - 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java index cce4bec..3c7e7d7 100644 --- a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java @@ -29,30 +29,44 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { if (shouldNotFilter(requestURI)) { return chain.filter(exchange); - } + } else { + String token = ExtractAuthorizationTokenUtil.extractToken(exchange.getRequest()); + if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + return response.setComplete(); + } - String token = ExtractAuthorizationTokenUtil.extractToken(exchange.getRequest()); - if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { - ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(HttpStatus.UNAUTHORIZED); - return response.setComplete(); - } + try { + JwtUtil.isTokenValid(token); + } catch (ExpiredJwtException e) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + return response.setComplete(); + } - try { - JwtUtil.isTokenValid(token); - } catch (ExpiredJwtException e) { - ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(HttpStatus.UNAUTHORIZED); - return response.setComplete(); + return chain.filter(addUserIdHeaderAtRequest(exchange, JwtUtil.extractSubject(token))); } - - return chain.filter(addUserIdHeaderAtRequest(exchange,JwtUtil.extractSubject(token))); } private boolean shouldNotFilter(String requestURI) { - return requestURI.contains(SwaggerRequestURI.UI_URI) || + return shouldNotFilterSwaggerURI(requestURI) || + shouldNotFilterSystemPolicyURI(requestURI); + } + + private boolean shouldNotFilterSwaggerURI(String requestURI) { + return requestURI.contains(SwaggerRequestURI.RESOURCES) || + requestURI.contains(SwaggerRequestURI.UI_URI) || + requestURI.contains(SwaggerRequestURI.WEB_JARS) || requestURI.contains(SwaggerRequestURI.API_DOCS_URI) || - requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.EMAIL_ANT); + requestURI.contains(SwaggerRequestURI.FAVICON); + } + + private boolean shouldNotFilterSystemPolicyURI(String requestURI) { + return requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.EMAIL_ANT) || + requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.LOGIN_ANT) || + requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.SIGNUP_ANT); + } private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { @@ -60,10 +74,8 @@ private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, S .header("userId", userId) .build(); - ServerWebExchange modifiedExchange = exchange.mutate() + return exchange.mutate() .request(modifiedRequest) .build(); - - return modifiedExchange; } } \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java index 3d8c5fc..66f2371 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java @@ -32,7 +32,7 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { private boolean shouldNotFilter(String requestURI) { return !requestURI.contains("/social") || requestURI.contains("/social/login") - || requestURI.contains("/oauth"); + || requestURI.contains("/oauth2"); } private boolean isAuthorizedUser(ServerWebExchange exchange) { diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java index 11ce88e..d6a7459 100644 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java @@ -13,7 +13,6 @@ import reactor.core.publisher.Mono; @Slf4j -@Component public class StoreAuthorizationGatewayFilter implements GlobalFilter { @Override From 160ba62c7994754de6f78c40961db8fbfc8c2772 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 14 Dec 2023 11:17:13 +0900 Subject: [PATCH 24/42] add: the shouldNotFilter OAuth and Favicon --- .../common/filter/JwtValidationGatewayFilter.java | 10 +++++++++- .../common/valueobject/Oauth2RequestURI.java | 8 ++++++++ .../common/valueobject/SwaggerRequestURI.java | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/main/java/kr/bb/apigateway/common/valueobject/Oauth2RequestURI.java diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java index 3c7e7d7..85a5128 100644 --- a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java @@ -5,6 +5,7 @@ import kr.bb.apigateway.common.util.JwtUtil; import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; import kr.bb.apigateway.common.valueobject.JWTAuthenticationShouldNotFilterAntMatcher; +import kr.bb.apigateway.common.valueobject.Oauth2RequestURI; import kr.bb.apigateway.common.valueobject.SwaggerRequestURI; import lombok.RequiredArgsConstructor; import org.springframework.cloud.gateway.filter.GatewayFilterChain; @@ -51,7 +52,8 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { private boolean shouldNotFilter(String requestURI) { return shouldNotFilterSwaggerURI(requestURI) || - shouldNotFilterSystemPolicyURI(requestURI); + shouldNotFilterSystemPolicyURI(requestURI) || + shouldNotFilterOauth2(requestURI); } private boolean shouldNotFilterSwaggerURI(String requestURI) { @@ -69,6 +71,10 @@ private boolean shouldNotFilterSystemPolicyURI(String requestURI) { } + private boolean shouldNotFilterOauth2(String requestURI){ + return requestURI.contains(Oauth2RequestURI.OAUTH2_REQUEST); + } + private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() .header("userId", userId) @@ -78,4 +84,6 @@ private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, S .request(modifiedRequest) .build(); } + + } \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/Oauth2RequestURI.java b/src/main/java/kr/bb/apigateway/common/valueobject/Oauth2RequestURI.java new file mode 100644 index 0000000..b585494 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/valueobject/Oauth2RequestURI.java @@ -0,0 +1,8 @@ +package kr.bb.apigateway.common.valueobject; + +public class Oauth2RequestURI { + + public static String OAUTH2_REQUEST ="/oauth2"; + + +} diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/SwaggerRequestURI.java b/src/main/java/kr/bb/apigateway/common/valueobject/SwaggerRequestURI.java index ac933d4..be1e803 100644 --- a/src/main/java/kr/bb/apigateway/common/valueobject/SwaggerRequestURI.java +++ b/src/main/java/kr/bb/apigateway/common/valueobject/SwaggerRequestURI.java @@ -6,6 +6,6 @@ public class SwaggerRequestURI { public static final String API_DOCS_URI = "/v2/api-docs"; public static final String WEB_JARS = "/webjars"; public static final String RESOURCES = "/swagger-resources"; - public static final String FAVICON = "/favicon"; + public static final String FAVICON = "/favicon.ico"; } From c936d873cf0d6d514a700137061c7933142fbff7 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 14 Dec 2023 17:45:05 +0900 Subject: [PATCH 25/42] fix: temporary disable the filters --- .../filter/JwtValidationGatewayFilter.java | 140 +++++++++--------- .../common/valueobject/Oauth2RequestURI.java | 3 +- .../SocialAuthorizationGatewayFilter.java | 70 ++++----- .../StoreAuthorizationGatewayFilter.java | 99 ++++++------- 4 files changed, 155 insertions(+), 157 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java index 85a5128..f1898a9 100644 --- a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java @@ -17,73 +17,73 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -@RequiredArgsConstructor -@Component -public class JwtValidationGatewayFilter implements GlobalFilter { - - private final RedisBlackListTokenUtil redisBlackListTokenUtil; - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().getPath(); - - if (shouldNotFilter(requestURI)) { - return chain.filter(exchange); - } else { - String token = ExtractAuthorizationTokenUtil.extractToken(exchange.getRequest()); - if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { - ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(HttpStatus.UNAUTHORIZED); - return response.setComplete(); - } - - try { - JwtUtil.isTokenValid(token); - } catch (ExpiredJwtException e) { - ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(HttpStatus.UNAUTHORIZED); - return response.setComplete(); - } - - return chain.filter(addUserIdHeaderAtRequest(exchange, JwtUtil.extractSubject(token))); - } - } - - private boolean shouldNotFilter(String requestURI) { - return shouldNotFilterSwaggerURI(requestURI) || - shouldNotFilterSystemPolicyURI(requestURI) || - shouldNotFilterOauth2(requestURI); - } - - private boolean shouldNotFilterSwaggerURI(String requestURI) { - return requestURI.contains(SwaggerRequestURI.RESOURCES) || - requestURI.contains(SwaggerRequestURI.UI_URI) || - requestURI.contains(SwaggerRequestURI.WEB_JARS) || - requestURI.contains(SwaggerRequestURI.API_DOCS_URI) || - requestURI.contains(SwaggerRequestURI.FAVICON); - } - - private boolean shouldNotFilterSystemPolicyURI(String requestURI) { - return requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.EMAIL_ANT) || - requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.LOGIN_ANT) || - requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.SIGNUP_ANT); - - } - - private boolean shouldNotFilterOauth2(String requestURI){ - return requestURI.contains(Oauth2RequestURI.OAUTH2_REQUEST); - } - - private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { - ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() - .header("userId", userId) - .build(); - - return exchange.mutate() - .request(modifiedRequest) - .build(); - } - - -} \ No newline at end of file +//@RequiredArgsConstructor +//@Component +//public class JwtValidationGatewayFilter implements GlobalFilter { +// +// private final RedisBlackListTokenUtil redisBlackListTokenUtil; +// +// @Override +// public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { +// ServerHttpRequest request = exchange.getRequest(); +// String requestURI = request.getURI().getPath(); +// +// if (shouldNotFilter(requestURI)) { +// return chain.filter(exchange); +// } else { +// String token = ExtractAuthorizationTokenUtil.extractToken(exchange.getRequest()); +// if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { +// ServerHttpResponse response = exchange.getResponse(); +// response.setStatusCode(HttpStatus.UNAUTHORIZED); +// return response.setComplete(); +// } +// +// try { +// JwtUtil.isTokenValid(token); +// } catch (ExpiredJwtException e) { +// ServerHttpResponse response = exchange.getResponse(); +// response.setStatusCode(HttpStatus.UNAUTHORIZED); +// return response.setComplete(); +// } +// +// return chain.filter(addUserIdHeaderAtRequest(exchange, JwtUtil.extractSubject(token))); +// } +// } +// +// private boolean shouldNotFilter(String requestURI) { +// return shouldNotFilterSwaggerURI(requestURI) || +// shouldNotFilterSystemPolicyURI(requestURI) || +// shouldNotFilterOauth2(requestURI); +// } +// +// private boolean shouldNotFilterSwaggerURI(String requestURI) { +// return requestURI.contains(SwaggerRequestURI.RESOURCES) || +// requestURI.contains(SwaggerRequestURI.UI_URI) || +// requestURI.contains(SwaggerRequestURI.WEB_JARS) || +// requestURI.contains(SwaggerRequestURI.API_DOCS_URI) || +// requestURI.contains(SwaggerRequestURI.FAVICON); +// } +// +// private boolean shouldNotFilterSystemPolicyURI(String requestURI) { +// return requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.EMAIL_ANT) || +// requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.LOGIN_ANT) || +// requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.SIGNUP_ANT); +// +// } +// +// private boolean shouldNotFilterOauth2(String requestURI){ +// return requestURI.contains(Oauth2RequestURI.OAUTH2_REQUEST); +// } +// +// private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { +// ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() +// .header("userId", userId) +// .build(); +// +// return exchange.mutate() +// .request(modifiedRequest) +// .build(); +// } +// +// +//} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/valueobject/Oauth2RequestURI.java b/src/main/java/kr/bb/apigateway/common/valueobject/Oauth2RequestURI.java index b585494..be41e3e 100644 --- a/src/main/java/kr/bb/apigateway/common/valueobject/Oauth2RequestURI.java +++ b/src/main/java/kr/bb/apigateway/common/valueobject/Oauth2RequestURI.java @@ -2,7 +2,6 @@ public class Oauth2RequestURI { - public static String OAUTH2_REQUEST ="/oauth2"; - + public static String OAUTH2_REQUEST ="/api/auth/oauth2/authorization"; } diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java index 66f2371..f9498d8 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java @@ -11,38 +11,38 @@ import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; - -@Slf4j -public class SocialAuthorizationGatewayFilter implements GlobalFilter { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().toString(); - log.warn("-------------requestURI :" + requestURI); - - if (shouldNotFilter(requestURI)) { - chain.filter(exchange); - } else if (!isAuthorizedUser(exchange)) { - return handleUnauthenticatedUser(exchange); - } - - return chain.filter(exchange); - } - - private boolean shouldNotFilter(String requestURI) { - return !requestURI.contains("/social") || requestURI.contains("/social/login") - || requestURI.contains("/oauth2"); - } - - private boolean isAuthorizedUser(ServerWebExchange exchange) { - String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); - return Role.ROLE_SOCIAL_USER.name().equals(role); - } - - private Mono handleUnauthenticatedUser(ServerWebExchange exchange) { - ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(HttpStatus.UNAUTHORIZED); - return response.setComplete(); - } -} +// +//@Slf4j +//public class SocialAuthorizationGatewayFilter implements GlobalFilter { +// +// @Override +// public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { +// ServerHttpRequest request = exchange.getRequest(); +// String requestURI = request.getURI().toString(); +// log.warn("-------------requestURI :" + requestURI); +// +// if (shouldNotFilter(requestURI)) { +// chain.filter(exchange); +// } else if (!isAuthorizedUser(exchange)) { +// return handleUnauthenticatedUser(exchange); +// } +// +// return chain.filter(exchange); +// } +// +// private boolean shouldNotFilter(String requestURI) { +// return !requestURI.contains("/social") || requestURI.contains("/social/login") +// || requestURI.contains("/oauth2"); +// } +// +// private boolean isAuthorizedUser(ServerWebExchange exchange) { +// String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); +// return Role.ROLE_SOCIAL_USER.name().equals(role); +// } +// +// private Mono handleUnauthenticatedUser(ServerWebExchange exchange) { +// ServerHttpResponse response = exchange.getResponse(); +// response.setStatusCode(HttpStatus.UNAUTHORIZED); +// return response.setComplete(); +// } +//} diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java index d6a7459..b05cb2f 100644 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java @@ -8,56 +8,55 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -@Slf4j -public class StoreAuthorizationGatewayFilter implements GlobalFilter { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().toString(); - log.warn("-------------requestURI :" +requestURI); - - if (shouldNotFilter(requestURI)) { - chain.filter(exchange); - } else { - return roleHandler(exchange,chain); - } - return chain.filter(exchange); - } - - private Mono roleHandler(ServerWebExchange exchange,GatewayFilterChain chain) { - String role = getRoleFromHeader(exchange); - if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals(role)) { - return handlePendingApproval(exchange); - } else if (StoreManagerStatus.ROLE_STORE_MANAGER_DENIED.name().equals(role)) { - return handleDeniedRole(exchange); - } - return chain.filter(exchange); - } - - private boolean shouldNotFilter(String requestURI) { - return !requestURI.contains("/stores") || requestURI.contains("/stores/login") || - requestURI.contains("/stores/signup") || requestURI.contains("/stores/emails"); - } - - private String getRoleFromHeader(ServerWebExchange exchange) { - return ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); - } - - private Mono handlePendingApproval(ServerWebExchange exchange) { - ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(HttpStatus.UNAUTHORIZED); - return response.setComplete(); - } - - private Mono handleDeniedRole(ServerWebExchange exchange) { - ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(HttpStatus.FORBIDDEN); - return response.setComplete(); - } - -} +//@Slf4j +//public class StoreAuthorizationGatewayFilter implements GlobalFilter { +// +// @Override +// public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { +// ServerHttpRequest request = exchange.getRequest(); +// String requestURI = request.getURI().toString(); +// log.warn("-------------requestURI :" +requestURI); +// +// if (shouldNotFilter(requestURI)) { +// chain.filter(exchange); +// } else { +// return roleHandler(exchange,chain); +// } +// return chain.filter(exchange); +// } +// +// private Mono roleHandler(ServerWebExchange exchange,GatewayFilterChain chain) { +// String role = getRoleFromHeader(exchange); +// if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals(role)) { +// return handlePendingApproval(exchange); +// } else if (StoreManagerStatus.ROLE_STORE_MANAGER_DENIED.name().equals(role)) { +// return handleDeniedRole(exchange); +// } +// return chain.filter(exchange); +// } +// +// private boolean shouldNotFilter(String requestURI) { +// return !requestURI.contains("/stores") || requestURI.contains("/stores/login") || +// requestURI.contains("/stores/signup") || requestURI.contains("/stores/emails"); +// } +// +// private String getRoleFromHeader(ServerWebExchange exchange) { +// return ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); +// } +// +// private Mono handlePendingApproval(ServerWebExchange exchange) { +// ServerHttpResponse response = exchange.getResponse(); +// response.setStatusCode(HttpStatus.UNAUTHORIZED); +// return response.setComplete(); +// } +// +// private Mono handleDeniedRole(ServerWebExchange exchange) { +// ServerHttpResponse response = exchange.getResponse(); +// response.setStatusCode(HttpStatus.FORBIDDEN); +// return response.setComplete(); +// } +// +//} From e06dcb5276e02609a8399da626ccf1815648eb0f Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 14 Dec 2023 17:46:59 +0900 Subject: [PATCH 26/42] fix: temporary make disable the filters --- .../bb/apigateway/common/SecurityConfig.java | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java index 2f42e2f..59d97af 100644 --- a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java +++ b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java @@ -1,7 +1,6 @@ package kr.bb.apigateway.common; -import kr.bb.apigateway.social.filter.SocialAuthorizationGatewayFilter; -import kr.bb.apigateway.store.filter.StoreAuthorizationGatewayFilter; + import kr.bb.apigateway.systsem.filter.SystemAdminAuthorizationGatewayFilter; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cloud.gateway.filter.GlobalFilter; @@ -12,36 +11,36 @@ import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; -@EnableWebFluxSecurity -@EnableReactiveMethodSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { - http - .csrf(csrf -> csrf.disable()); - return http.build(); - - } - - @Order(1) - @Qualifier("socialAuthorizationGatewayFilter") - @Bean - public GlobalFilter socialAuthorizationGatewayFilter() { - return new SocialAuthorizationGatewayFilter(); - } - - @Order(0) - @Qualifier("systemAdminAuthorizationGatewayFilter") - @Bean - public GlobalFilter systemAdminAuthorizationGatewayFilter() { - return new SystemAdminAuthorizationGatewayFilter(); - } +//@EnableWebFluxSecurity +//@EnableReactiveMethodSecurity +//public class SecurityConfig { - @Order(2) - @Qualifier("storeAuthorizationGatewayFilter") - @Bean - public GlobalFilter storeAuthorizationGatewayFilter() { - return new StoreAuthorizationGatewayFilter(); - } -} +// @Bean +// public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { +// http +// .csrf(csrf -> csrf.disable()); +// return http.build(); +// +// } +// +// @Order(1) +// @Qualifier("socialAuthorizationGatewayFilter") +// @Bean +// public GlobalFilter socialAuthorizationGatewayFilter() { +// return new SocialAuthorizationGatewayFilter(); +// } +// +// @Order(0) +// @Qualifier("systemAdminAuthorizationGatewayFilter") +// @Bean +// public GlobalFilter systemAdminAuthorizationGatewayFilter() { +// return new SystemAdminAuthorizationGatewayFilter(); +// } +// +// @Order(2) +// @Qualifier("storeAuthorizationGatewayFilter") +// @Bean +// public GlobalFilter storeAuthorizationGatewayFilter() { +// return new StoreAuthorizationGatewayFilter(); +// } +//} From a2278ec8ac0eaf0451c9880db6cd3aadcc377878 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 14 Dec 2023 18:06:45 +0900 Subject: [PATCH 27/42] fix: disable rest of filter --- .../bb/apigateway/common/LoggingFilter.java | 2 + .../bb/apigateway/common/SecurityConfig.java | 1 - ...SystemAdminAuthorizationGatewayFilter.java | 66 +++++++++---------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/LoggingFilter.java b/src/main/java/kr/bb/apigateway/common/LoggingFilter.java index 77a6058..2d5928b 100644 --- a/src/main/java/kr/bb/apigateway/common/LoggingFilter.java +++ b/src/main/java/kr/bb/apigateway/common/LoggingFilter.java @@ -11,9 +11,11 @@ import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.route.Route; +import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +@Component @Slf4j public class LoggingFilter implements GlobalFilter { diff --git a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java index 59d97af..037824e 100644 --- a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java +++ b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java @@ -1,7 +1,6 @@ package kr.bb.apigateway.common; -import kr.bb.apigateway.systsem.filter.SystemAdminAuthorizationGatewayFilter; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java index 7ab7137..7ebca3a 100644 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java @@ -11,36 +11,36 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -@Slf4j -public class SystemAdminAuthorizationGatewayFilter implements GlobalFilter { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - String requestURI = request.getURI().toString(); - log.warn("-------------requestURI :" +requestURI); - - if (shouldNotFilter(requestURI)) { - return chain.filter(exchange); - } - else if (!isSystemAdmin(exchange)) { - return handleUnauthorized(exchange); - } - return chain.filter(exchange); - } - - private boolean shouldNotFilter(String requestURI) { - return !requestURI.contains("/admin") || requestURI.contains("/admin/login"); - } - - private boolean isSystemAdmin(ServerWebExchange exchange) { - String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); - return Role.ROLE_SYSTEM_ADMIN.name().equals(role); - } - - - private Mono handleUnauthorized(ServerWebExchange exchange) { - ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(HttpStatus.UNAUTHORIZED); - return response.setComplete(); - } -} +//@Slf4j +//public class SystemAdminAuthorizationGatewayFilter implements GlobalFilter { +// @Override +// public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { +// ServerHttpRequest request = exchange.getRequest(); +// String requestURI = request.getURI().toString(); +// log.warn("-------------requestURI :" +requestURI); +// +// if (shouldNotFilter(requestURI)) { +// return chain.filter(exchange); +// } +// else if (!isSystemAdmin(exchange)) { +// return handleUnauthorized(exchange); +// } +// return chain.filter(exchange); +// } +// +// private boolean shouldNotFilter(String requestURI) { +// return !requestURI.contains("/admin") || requestURI.contains("/admin/login"); +// } +// +// private boolean isSystemAdmin(ServerWebExchange exchange) { +// String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); +// return Role.ROLE_SYSTEM_ADMIN.name().equals(role); +// } +// +// +// private Mono handleUnauthorized(ServerWebExchange exchange) { +// ServerHttpResponse response = exchange.getResponse(); +// response.setStatusCode(HttpStatus.UNAUTHORIZED); +// return response.setComplete(); +// } +//} From 548a9c962ebbd64d2eb705aa345904c5ed48747b Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 14 Dec 2023 18:11:02 +0900 Subject: [PATCH 28/42] fix: fix to disable csrf token --- .../bb/apigateway/common/SecurityConfig.java | 46 +++++-------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java index 037824e..c03ebc1 100644 --- a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java +++ b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java @@ -1,45 +1,21 @@ package kr.bb.apigateway.common; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; -import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; -//@EnableWebFluxSecurity -//@EnableReactiveMethodSecurity -//public class SecurityConfig { +@EnableWebFluxSecurity +@EnableReactiveMethodSecurity +public class SecurityConfig { -// @Bean -// public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { -// http -// .csrf(csrf -> csrf.disable()); -// return http.build(); -// -// } -// -// @Order(1) -// @Qualifier("socialAuthorizationGatewayFilter") -// @Bean -// public GlobalFilter socialAuthorizationGatewayFilter() { -// return new SocialAuthorizationGatewayFilter(); -// } -// -// @Order(0) -// @Qualifier("systemAdminAuthorizationGatewayFilter") -// @Bean -// public GlobalFilter systemAdminAuthorizationGatewayFilter() { -// return new SystemAdminAuthorizationGatewayFilter(); -// } -// -// @Order(2) -// @Qualifier("storeAuthorizationGatewayFilter") -// @Bean -// public GlobalFilter storeAuthorizationGatewayFilter() { -// return new StoreAuthorizationGatewayFilter(); -// } -//} + @Bean + public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + http + .csrf(csrf -> csrf.disable()); + return http.build(); + + } +} From 9d03037b6b1765eb6b6833114a924e079eb0f026 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Thu, 14 Dec 2023 21:41:55 +0900 Subject: [PATCH 29/42] revert: not to use the filter --- build.gradle | 2 - .../filter/JwtValidationGatewayFilter.java | 93 +++++++------------ .../util/ExtractAuthorizationTokenUtil.java | 1 - 3 files changed, 36 insertions(+), 60 deletions(-) diff --git a/build.gradle b/build.gradle index a902859..b2d5447 100644 --- a/build.gradle +++ b/build.gradle @@ -37,8 +37,6 @@ dependencies { implementation "org.springframework.cloud:spring-cloud-starter-bus-kafka" implementation 'org.springframework.data:spring-data-redis' implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.kafka:spring-kafka' implementation 'redis.clients:jedis' } diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java index f1898a9..be5758c 100644 --- a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java @@ -4,12 +4,8 @@ import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; import kr.bb.apigateway.common.util.JwtUtil; import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; -import kr.bb.apigateway.common.valueobject.JWTAuthenticationShouldNotFilterAntMatcher; -import kr.bb.apigateway.common.valueobject.Oauth2RequestURI; -import kr.bb.apigateway.common.valueobject.SwaggerRequestURI; -import lombok.RequiredArgsConstructor; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -17,73 +13,56 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -//@RequiredArgsConstructor //@Component -//public class JwtValidationGatewayFilter implements GlobalFilter { +//public class JwtValidationGatewayFilterFactory extends +// AbstractGatewayFilterFactory { // // private final RedisBlackListTokenUtil redisBlackListTokenUtil; // -// @Override -// public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { -// ServerHttpRequest request = exchange.getRequest(); -// String requestURI = request.getURI().getPath(); +// public JwtValidationGatewayFilterFactory(RedisBlackListTokenUtil redisBlackListTokenUtil) { +// this.redisBlackListTokenUtil = redisBlackListTokenUtil; +// } // -// if (shouldNotFilter(requestURI)) { -// return chain.filter(exchange); -// } else { -// String token = ExtractAuthorizationTokenUtil.extractToken(exchange.getRequest()); +// @Override +// public GatewayFilter apply(Config config) { +// return (exchange, chain) -> { +// ServerHttpRequest request = exchange.getRequest(); +// String token = ExtractAuthorizationTokenUtil.extractToken(request); // if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { -// ServerHttpResponse response = exchange.getResponse(); -// response.setStatusCode(HttpStatus.UNAUTHORIZED); -// return response.setComplete(); -// } -// -// try { -// JwtUtil.isTokenValid(token); -// } catch (ExpiredJwtException e) { -// ServerHttpResponse response = exchange.getResponse(); -// response.setStatusCode(HttpStatus.UNAUTHORIZED); -// return response.setComplete(); +// return handleError(exchange, HttpStatus.UNAUTHORIZED); // } -// -// return chain.filter(addUserIdHeaderAtRequest(exchange, JwtUtil.extractSubject(token))); -// } +// else{ +// try { +// JwtUtil.isTokenValid(token); +// return chain.filter(addUserIdHeaderAtRequest(exchange, JwtUtil.extractSubject(token))); +// } catch (ExpiredJwtException e) { +// return handleError(exchange, HttpStatus.UNAUTHORIZED); +// } +// } +// }; // } // -// private boolean shouldNotFilter(String requestURI) { -// return shouldNotFilterSwaggerURI(requestURI) || -// shouldNotFilterSystemPolicyURI(requestURI) || -// shouldNotFilterOauth2(requestURI); -// } // -// private boolean shouldNotFilterSwaggerURI(String requestURI) { -// return requestURI.contains(SwaggerRequestURI.RESOURCES) || -// requestURI.contains(SwaggerRequestURI.UI_URI) || -// requestURI.contains(SwaggerRequestURI.WEB_JARS) || -// requestURI.contains(SwaggerRequestURI.API_DOCS_URI) || -// requestURI.contains(SwaggerRequestURI.FAVICON); +// private Mono handleError(ServerWebExchange exchange, HttpStatus status) { +// ServerHttpResponse response = exchange.getResponse(); +// response.setStatusCode(status); +// return response.setComplete(); // } // -// private boolean shouldNotFilterSystemPolicyURI(String requestURI) { -// return requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.EMAIL_ANT) || -// requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.LOGIN_ANT) || -// requestURI.contains(JWTAuthenticationShouldNotFilterAntMatcher.SIGNUP_ANT); -// +// private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { // } // -// private boolean shouldNotFilterOauth2(String requestURI){ -// return requestURI.contains(Oauth2RequestURI.OAUTH2_REQUEST); -// } // -// private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { -// ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() -// .header("userId", userId) -// .build(); +// public static class Config { // -// return exchange.mutate() -// .request(modifiedRequest) -// .build(); -// } +// private boolean shouldNotFilterURL; // +// public String getShouldNotURL() { +// return shouldNotFilterURL; +// } // +// public void setShouldNotURL(String shouldNotURL) { +// this.shouldNotFilterURL = shouldNotURL; +// } +// } //} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java index ac60e59..e5a8819 100644 --- a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java @@ -1,7 +1,6 @@ package kr.bb.apigateway.common.util; import io.jsonwebtoken.Claims; -import javax.servlet.http.HttpServletRequest; import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; From edd996d8f7916e309543df30980254120a9f1aa7 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Fri, 15 Dec 2023 09:41:22 +0900 Subject: [PATCH 30/42] refactor: refactor the gateway --- .../bb/apigateway/common/SecurityConfig.java | 21 ------ .../filter/JwtValidationGatewayFilter.java | 68 ----------------- .../JwtValidationGatewayFilterFactory.java | 74 +++++++++++++++++++ .../common/security/SecurityContextUtil.java | 18 ----- .../SystemAuthenticationSuccessHandler.java | 57 -------------- .../SocialAuthorizationGatewayFilter.java | 48 ------------ ...cialAuthorizationGatewayFilterFactory.java | 53 +++++++++++++ .../StoreAuthorizationGatewayFilter.java | 62 ---------------- ...toreAuthorizationGatewayFilterFactory.java | 66 +++++++++++++++++ ...SystemAdminAuthorizationGatewayFilter.java | 46 ------------ ...dminAuthorizationGatewayFilterFactory.java | 60 +++++++++++++++ 11 files changed, 253 insertions(+), 320 deletions(-) delete mode 100644 src/main/java/kr/bb/apigateway/common/SecurityConfig.java delete mode 100644 src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java create mode 100644 src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java delete mode 100644 src/main/java/kr/bb/apigateway/common/security/SecurityContextUtil.java delete mode 100644 src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java delete mode 100644 src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java create mode 100644 src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java delete mode 100644 src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java create mode 100644 src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java delete mode 100644 src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java create mode 100644 src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java diff --git a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java b/src/main/java/kr/bb/apigateway/common/SecurityConfig.java deleted file mode 100644 index c03ebc1..0000000 --- a/src/main/java/kr/bb/apigateway/common/SecurityConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package kr.bb.apigateway.common; - - -import org.springframework.context.annotation.Bean; -import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.web.server.SecurityWebFilterChain; - -@EnableWebFluxSecurity -@EnableReactiveMethodSecurity -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { - http - .csrf(csrf -> csrf.disable()); - return http.build(); - - } -} diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java deleted file mode 100644 index be5758c..0000000 --- a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilter.java +++ /dev/null @@ -1,68 +0,0 @@ -package kr.bb.apigateway.common.filter; - -import io.jsonwebtoken.ExpiredJwtException; -import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; -import kr.bb.apigateway.common.util.JwtUtil; -import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -//@Component -//public class JwtValidationGatewayFilterFactory extends -// AbstractGatewayFilterFactory { -// -// private final RedisBlackListTokenUtil redisBlackListTokenUtil; -// -// public JwtValidationGatewayFilterFactory(RedisBlackListTokenUtil redisBlackListTokenUtil) { -// this.redisBlackListTokenUtil = redisBlackListTokenUtil; -// } -// -// @Override -// public GatewayFilter apply(Config config) { -// return (exchange, chain) -> { -// ServerHttpRequest request = exchange.getRequest(); -// String token = ExtractAuthorizationTokenUtil.extractToken(request); -// if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { -// return handleError(exchange, HttpStatus.UNAUTHORIZED); -// } -// else{ -// try { -// JwtUtil.isTokenValid(token); -// return chain.filter(addUserIdHeaderAtRequest(exchange, JwtUtil.extractSubject(token))); -// } catch (ExpiredJwtException e) { -// return handleError(exchange, HttpStatus.UNAUTHORIZED); -// } -// } -// }; -// } -// -// -// private Mono handleError(ServerWebExchange exchange, HttpStatus status) { -// ServerHttpResponse response = exchange.getResponse(); -// response.setStatusCode(status); -// return response.setComplete(); -// } -// -// private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { -// } -// -// -// public static class Config { -// -// private boolean shouldNotFilterURL; -// -// public String getShouldNotURL() { -// return shouldNotFilterURL; -// } -// -// public void setShouldNotURL(String shouldNotURL) { -// this.shouldNotFilterURL = shouldNotURL; -// } -// } -//} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java new file mode 100644 index 0000000..ec4c260 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java @@ -0,0 +1,74 @@ +package kr.bb.apigateway.common.filter; + +import io.jsonwebtoken.ExpiredJwtException; +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.util.JwtUtil; +import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Component +public class JwtValidationGatewayFilterFactory extends + AbstractGatewayFilterFactory { + + private final RedisBlackListTokenUtil redisBlackListTokenUtil; + + public JwtValidationGatewayFilterFactory(RedisBlackListTokenUtil redisBlackListTokenUtil) { + this.redisBlackListTokenUtil = redisBlackListTokenUtil; + } + + @Override + public GatewayFilter apply(Config config) { + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + String token = ExtractAuthorizationTokenUtil.extractToken(request); + if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { + return handleError(exchange, HttpStatus.UNAUTHORIZED); + } else { + try { + JwtUtil.isTokenValid(token); + return chain.filter(addUserIdHeaderAtRequest(exchange, JwtUtil.extractSubject(token))); + } catch (ExpiredJwtException e) { + return handleError(exchange, HttpStatus.UNAUTHORIZED); + } + } + }; + } + + + private Mono handleError(ServerWebExchange exchange, HttpStatus status) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(status); + return response.setComplete(); + } + + private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { + ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() + .headers(httpHeaders -> httpHeaders.add("userId", userId)) + .build(); + + return exchange.mutate() + .request(modifiedRequest) + .build(); + } + + + public static class Config { + + private boolean shouldNotFilter; + + public boolean getShouldNotFilter() { + return shouldNotFilter; + } + + public void setShouldNotURL(boolean shouldNotFilter) { + this.shouldNotFilter = shouldNotFilter; + } + } +} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/security/SecurityContextUtil.java b/src/main/java/kr/bb/apigateway/common/security/SecurityContextUtil.java deleted file mode 100644 index bd369ab..0000000 --- a/src/main/java/kr/bb/apigateway/common/security/SecurityContextUtil.java +++ /dev/null @@ -1,18 +0,0 @@ -package kr.bb.apigateway.common.security; - -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -public class SecurityContextUtil { - - - - public static void setSecurityContextWithUserId(String userId) { - Authentication authentication = new UsernamePasswordAuthenticationToken(userId, null); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - - -} diff --git a/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java b/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java deleted file mode 100644 index a0d3004..0000000 --- a/src/main/java/kr/bb/apigateway/common/security/SystemAuthenticationSuccessHandler.java +++ /dev/null @@ -1,57 +0,0 @@ -package kr.bb.apigateway.common.security; - - -import java.io.IOException; -import java.util.Map; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import kr.bb.apigateway.common.util.JwtUtil; -import kr.bb.apigateway.common.valueobject.AuthId; -import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.stereotype.Component; - -@RequiredArgsConstructor -@Component -public class SystemAuthenticationSuccessHandler implements AuthenticationSuccessHandler { - - private final TokenHandler tokenHandler; - - - private String getRoleFromSecurityContext() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && !authentication.getAuthorities().isEmpty()) { - return authentication.getAuthorities().iterator().next().getAuthority(); - } - throw new IllegalArgumentException("토큰에 해당 유저의 역할이 담겨있지 않습니다."); - } - - private Map createClaimsRoleMap() { - return JwtUtil.addClaims( - SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME, getRoleFromSecurityContext()); - } - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - FilterChain chain, Authentication authentication) throws IOException, ServletException { - onAuthenticationSuccess(request, response, authentication); - chain.doFilter(request,response); - } - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - String token = tokenHandler.createToken(getIdFromPrincipal(authentication), - createClaimsRoleMap(), response); - response.setHeader(SecurityPolicyStaticValue.TOKEN_AUTHORIZAION_HEADER_NAME,SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_PREFIX +token ); - } - - private String getIdFromPrincipal(Authentication authentication){ - return ((AuthId)authentication.getPrincipal()).getValue().toString(); - } -} diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java deleted file mode 100644 index f9498d8..0000000 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilter.java +++ /dev/null @@ -1,48 +0,0 @@ -package kr.bb.apigateway.social.filter; - - -import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; -import kr.bb.apigateway.common.valueobject.Role; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; -// -//@Slf4j -//public class SocialAuthorizationGatewayFilter implements GlobalFilter { -// -// @Override -// public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { -// ServerHttpRequest request = exchange.getRequest(); -// String requestURI = request.getURI().toString(); -// log.warn("-------------requestURI :" + requestURI); -// -// if (shouldNotFilter(requestURI)) { -// chain.filter(exchange); -// } else if (!isAuthorizedUser(exchange)) { -// return handleUnauthenticatedUser(exchange); -// } -// -// return chain.filter(exchange); -// } -// -// private boolean shouldNotFilter(String requestURI) { -// return !requestURI.contains("/social") || requestURI.contains("/social/login") -// || requestURI.contains("/oauth2"); -// } -// -// private boolean isAuthorizedUser(ServerWebExchange exchange) { -// String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); -// return Role.ROLE_SOCIAL_USER.name().equals(role); -// } -// -// private Mono handleUnauthenticatedUser(ServerWebExchange exchange) { -// ServerHttpResponse response = exchange.getResponse(); -// response.setStatusCode(HttpStatus.UNAUTHORIZED); -// return response.setComplete(); -// } -//} diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java new file mode 100644 index 0000000..b19c1f7 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java @@ -0,0 +1,53 @@ +package kr.bb.apigateway.social.filter; + + +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.valueobject.Role; +import kr.bb.apigateway.social.exception.SocialAuthException; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Component +public class SocialAuthorizationGatewayFilterFactory extends + AbstractGatewayFilterFactory { + + @Override + public GatewayFilter apply(Config config) { + return (exchange, chain) -> { + if (!isAuthorizedUser(exchange)) { + return handleUnauthenticatedUser(exchange); + } + return chain.filter(exchange); + }; + } + + private boolean isAuthorizedUser(ServerWebExchange exchange) { + String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); + return Role.ROLE_SOCIAL_USER.name().equals(role); + } + + private Mono handleUnauthenticatedUser(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + throw new SocialAuthException("소셜 유저가 아닙니다."); + } + + public static class Config { + + private boolean shouldFilter; + + public boolean getShouldNotFilter() { + return shouldFilter; + } + + public void setShouldNotFilter(boolean shouldFilter) { + this.shouldFilter = shouldFilter; + } + } + +} diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java deleted file mode 100644 index b05cb2f..0000000 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilter.java +++ /dev/null @@ -1,62 +0,0 @@ -package kr.bb.apigateway.store.filter; - -import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; -import kr.bb.apigateway.store.valueobject.StoreManagerStatus; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -//@Slf4j -//public class StoreAuthorizationGatewayFilter implements GlobalFilter { -// -// @Override -// public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { -// ServerHttpRequest request = exchange.getRequest(); -// String requestURI = request.getURI().toString(); -// log.warn("-------------requestURI :" +requestURI); -// -// if (shouldNotFilter(requestURI)) { -// chain.filter(exchange); -// } else { -// return roleHandler(exchange,chain); -// } -// return chain.filter(exchange); -// } -// -// private Mono roleHandler(ServerWebExchange exchange,GatewayFilterChain chain) { -// String role = getRoleFromHeader(exchange); -// if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals(role)) { -// return handlePendingApproval(exchange); -// } else if (StoreManagerStatus.ROLE_STORE_MANAGER_DENIED.name().equals(role)) { -// return handleDeniedRole(exchange); -// } -// return chain.filter(exchange); -// } -// -// private boolean shouldNotFilter(String requestURI) { -// return !requestURI.contains("/stores") || requestURI.contains("/stores/login") || -// requestURI.contains("/stores/signup") || requestURI.contains("/stores/emails"); -// } -// -// private String getRoleFromHeader(ServerWebExchange exchange) { -// return ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); -// } -// -// private Mono handlePendingApproval(ServerWebExchange exchange) { -// ServerHttpResponse response = exchange.getResponse(); -// response.setStatusCode(HttpStatus.UNAUTHORIZED); -// return response.setComplete(); -// } -// -// private Mono handleDeniedRole(ServerWebExchange exchange) { -// ServerHttpResponse response = exchange.getResponse(); -// response.setStatusCode(HttpStatus.FORBIDDEN); -// return response.setComplete(); -// } -// -//} diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java new file mode 100644 index 0000000..81fc2f2 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java @@ -0,0 +1,66 @@ +package kr.bb.apigateway.store.filter; + + +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.store.exception.StoreManagerAuthException; +import kr.bb.apigateway.store.valueobject.StoreManagerStatus; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Slf4j +@Component +public class StoreAuthorizationGatewayFilterFactory extends + AbstractGatewayFilterFactory { + + @Override + public GatewayFilter apply(Config config) { + return this::roleHandler; + } + + + private Mono roleHandler(ServerWebExchange exchange, GatewayFilterChain chain) { + String role = getRoleFromHeader(exchange); + if (StoreManagerStatus.ROLE_STORE_MANAGER_PENDING.name().equals(role)) { + return handlePendingApproval(exchange); + } else if (StoreManagerStatus.ROLE_STORE_MANAGER_DENIED.name().equals(role)) { + return handleDeniedRole(exchange); + } + return chain.filter(exchange); + } + + private String getRoleFromHeader(ServerWebExchange exchange) { + return ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); + } + + private Mono handlePendingApproval(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + throw new StoreManagerAuthException("사용자의 권한이 대기 중입니다."); + } + + private Mono handleDeniedRole(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.FORBIDDEN); + throw new StoreManagerAuthException("사용자의 사업자 등록증이 거절되었습니다 재등록하세요."); + } + + public static class Config { + + private boolean shouldFilter; + + public boolean getShouldNotFilter() { + return shouldFilter; + } + + public void setShouldNotFilter(boolean shouldFilter) { + this.shouldFilter = shouldFilter; + } + } +} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java deleted file mode 100644 index 7ebca3a..0000000 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilter.java +++ /dev/null @@ -1,46 +0,0 @@ -package kr.bb.apigateway.systsem.filter; - -import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; -import kr.bb.apigateway.common.valueobject.Role; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -//@Slf4j -//public class SystemAdminAuthorizationGatewayFilter implements GlobalFilter { -// @Override -// public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { -// ServerHttpRequest request = exchange.getRequest(); -// String requestURI = request.getURI().toString(); -// log.warn("-------------requestURI :" +requestURI); -// -// if (shouldNotFilter(requestURI)) { -// return chain.filter(exchange); -// } -// else if (!isSystemAdmin(exchange)) { -// return handleUnauthorized(exchange); -// } -// return chain.filter(exchange); -// } -// -// private boolean shouldNotFilter(String requestURI) { -// return !requestURI.contains("/admin") || requestURI.contains("/admin/login"); -// } -// -// private boolean isSystemAdmin(ServerWebExchange exchange) { -// String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); -// return Role.ROLE_SYSTEM_ADMIN.name().equals(role); -// } -// -// -// private Mono handleUnauthorized(ServerWebExchange exchange) { -// ServerHttpResponse response = exchange.getResponse(); -// response.setStatusCode(HttpStatus.UNAUTHORIZED); -// return response.setComplete(); -// } -//} diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java new file mode 100644 index 0000000..60a3cbc --- /dev/null +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java @@ -0,0 +1,60 @@ +package kr.bb.apigateway.systsem.filter; + +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.valueobject.Role; +import kr.bb.apigateway.systsem.exception.SystemAdminAuthException; +import kr.bb.apigateway.systsem.filter.SystemAdminAuthorizationGatewayFilterFactory.Config; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Slf4j +@Component +public class SystemAdminAuthorizationGatewayFilterFactory extends + AbstractGatewayFilterFactory { + + public SystemAdminAuthorizationGatewayFilterFactory() { + super(Config.class); + } + + @Override + public GatewayFilter apply(Config config) { + return (exchange, chain) -> { + if (!isSystemAdmin(exchange)) { + return handleUnauthorized(exchange); + } + return chain.filter(exchange); + }; + } + + + private boolean isSystemAdmin(ServerWebExchange exchange) { + String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); + return Role.ROLE_SYSTEM_ADMIN.name().equals(role); + } + + private Mono handleUnauthorized(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + throw new SystemAdminAuthException("존재 하지 않는 시스템 어드민 유저입니다."); + } + + public static class Config { + + private boolean shouldFilter; + + public boolean getShouldNotFilter() { + return shouldFilter; + } + + public void setShouldNotFilter(boolean shouldFilter) { + this.shouldFilter = shouldFilter; + } + } +} \ No newline at end of file From d267d66e9c9d55b4af7e5a4f49f120330e900fe4 Mon Sep 17 00:00:00 2001 From: nowgnas Date: Fri, 15 Dec 2023 10:23:53 +0900 Subject: [PATCH 31/42] update deploy dev --- .github/workflows/deploy-dev.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 0146b73..d064994 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -1,9 +1,11 @@ +name: develop deployment on: push: branches: - 'develop' jobs: - build-and-push-docker-image: + deployment: + environment: develop runs-on: ubuntu-latest steps: - name: Checkout @@ -15,9 +17,10 @@ jobs: with: username: ${{ secrets.DEV_DOCKER_ID }} password: ${{ secrets.DEV_DOCKER_PW }} - - name: Build and push + - name: deploy id: docker_build uses: docker/build-push-action@v2 with: + platforms: linux/amd64,linux/arm64 push: true tags: nowgnas/bb:apigateway From 005bc919bf1ccf5092428463cc539d8803c9db40 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:26:29 +0900 Subject: [PATCH 32/42] fix: fix the filter config --- .../bb/apigateway/common/LoggingFilter.java | 3 +++ .../JwtValidationGatewayFilterFactory.java | 19 +++----------- ...cialAuthorizationGatewayFilterFactory.java | 26 +++++++++---------- ...toreAuthorizationGatewayFilterFactory.java | 24 ++++++++--------- ...dminAuthorizationGatewayFilterFactory.java | 25 +++++++----------- 5 files changed, 39 insertions(+), 58 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/LoggingFilter.java b/src/main/java/kr/bb/apigateway/common/LoggingFilter.java index 2d5928b..4e2a382 100644 --- a/src/main/java/kr/bb/apigateway/common/LoggingFilter.java +++ b/src/main/java/kr/bb/apigateway/common/LoggingFilter.java @@ -7,6 +7,7 @@ import java.net.URI; import java.util.Collections; import java.util.Set; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; @@ -30,4 +31,6 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ", uri:" + routeUri); return chain.filter(exchange); } + + } diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java index ec4c260..1b85945 100644 --- a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java @@ -4,6 +4,7 @@ import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; import kr.bb.apigateway.common.util.JwtUtil; import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; +import lombok.RequiredArgsConstructor; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.HttpStatus; @@ -15,16 +16,17 @@ @Component public class JwtValidationGatewayFilterFactory extends - AbstractGatewayFilterFactory { + AbstractGatewayFilterFactory { private final RedisBlackListTokenUtil redisBlackListTokenUtil; public JwtValidationGatewayFilterFactory(RedisBlackListTokenUtil redisBlackListTokenUtil) { + super(NameConfig.class); this.redisBlackListTokenUtil = redisBlackListTokenUtil; } @Override - public GatewayFilter apply(Config config) { + public GatewayFilter apply(NameConfig config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); String token = ExtractAuthorizationTokenUtil.extractToken(request); @@ -58,17 +60,4 @@ private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, S .build(); } - - public static class Config { - - private boolean shouldNotFilter; - - public boolean getShouldNotFilter() { - return shouldNotFilter; - } - - public void setShouldNotURL(boolean shouldNotFilter) { - this.shouldNotFilter = shouldNotFilter; - } - } } \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java index b19c1f7..078eb69 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java @@ -4,6 +4,7 @@ import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; import kr.bb.apigateway.common.valueobject.Role; import kr.bb.apigateway.social.exception.SocialAuthException; +import lombok.Data; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.HttpStatus; @@ -14,10 +15,19 @@ @Component public class SocialAuthorizationGatewayFilterFactory extends - AbstractGatewayFilterFactory { + AbstractGatewayFilterFactory { + + + public SocialAuthorizationGatewayFilterFactory() { + } + + public SocialAuthorizationGatewayFilterFactory( + Class configClass) { + super(configClass); + } @Override - public GatewayFilter apply(Config config) { + public GatewayFilter apply(NameConfig config) { return (exchange, chain) -> { if (!isAuthorizedUser(exchange)) { return handleUnauthenticatedUser(exchange); @@ -37,17 +47,5 @@ private Mono handleUnauthenticatedUser(ServerWebExchange exchange) { throw new SocialAuthException("소셜 유저가 아닙니다."); } - public static class Config { - - private boolean shouldFilter; - - public boolean getShouldNotFilter() { - return shouldFilter; - } - - public void setShouldNotFilter(boolean shouldFilter) { - this.shouldFilter = shouldFilter; - } - } } diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java index 81fc2f2..51cac88 100644 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java @@ -4,6 +4,7 @@ import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; import kr.bb.apigateway.store.exception.StoreManagerAuthException; import kr.bb.apigateway.store.valueobject.StoreManagerStatus; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; @@ -17,10 +18,18 @@ @Slf4j @Component public class StoreAuthorizationGatewayFilterFactory extends - AbstractGatewayFilterFactory { + AbstractGatewayFilterFactory { + + public StoreAuthorizationGatewayFilterFactory() { + } + + public StoreAuthorizationGatewayFilterFactory( + Class configClass) { + super(configClass); + } @Override - public GatewayFilter apply(Config config) { + public GatewayFilter apply(NameConfig config) { return this::roleHandler; } @@ -51,16 +60,5 @@ private Mono handleDeniedRole(ServerWebExchange exchange) { throw new StoreManagerAuthException("사용자의 사업자 등록증이 거절되었습니다 재등록하세요."); } - public static class Config { - - private boolean shouldFilter; - public boolean getShouldNotFilter() { - return shouldFilter; - } - - public void setShouldNotFilter(boolean shouldFilter) { - this.shouldFilter = shouldFilter; - } - } } \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java index 60a3cbc..cc84fb7 100644 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java @@ -3,12 +3,10 @@ import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; import kr.bb.apigateway.common.valueobject.Role; import kr.bb.apigateway.systsem.exception.SystemAdminAuthException; -import kr.bb.apigateway.systsem.filter.SystemAdminAuthorizationGatewayFilterFactory.Config; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; @@ -17,14 +15,20 @@ @Slf4j @Component public class SystemAdminAuthorizationGatewayFilterFactory extends - AbstractGatewayFilterFactory { + AbstractGatewayFilterFactory { + public SystemAdminAuthorizationGatewayFilterFactory() { - super(Config.class); + super(NameConfig.class); + } + + public SystemAdminAuthorizationGatewayFilterFactory( + Class configClass) { + super(configClass); } @Override - public GatewayFilter apply(Config config) { + public GatewayFilter apply(NameConfig config) { return (exchange, chain) -> { if (!isSystemAdmin(exchange)) { return handleUnauthorized(exchange); @@ -45,16 +49,5 @@ private Mono handleUnauthorized(ServerWebExchange exchange) { throw new SystemAdminAuthException("존재 하지 않는 시스템 어드민 유저입니다."); } - public static class Config { - private boolean shouldFilter; - - public boolean getShouldNotFilter() { - return shouldFilter; - } - - public void setShouldNotFilter(boolean shouldFilter) { - this.shouldFilter = shouldFilter; - } - } } \ No newline at end of file From d3e3abd4d57395be6ae9f439b07033705dbf548f Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Mon, 18 Dec 2023 12:45:26 +0900 Subject: [PATCH 33/42] feat: add the gatway JWT token optionalFilter --- ...o.java => AccessTokenExpiredResponse.java} | 4 +- .../common/dto/GatewayTokenHandlerUtil.java | 66 +++++++++++++++++++ .../filter/JwtOptionalGatewayFilter.java | 65 ++++++++++++++++++ .../JwtValidationGatewayFilterFactory.java | 52 +++++++-------- .../util/ExtractAuthorizationTokenUtil.java | 9 +++ 5 files changed, 165 insertions(+), 31 deletions(-) rename src/main/java/kr/bb/apigateway/common/dto/{RenewAccessTokenDto.java => AccessTokenExpiredResponse.java} (82%) create mode 100644 src/main/java/kr/bb/apigateway/common/dto/GatewayTokenHandlerUtil.java create mode 100644 src/main/java/kr/bb/apigateway/common/filter/JwtOptionalGatewayFilter.java diff --git a/src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java b/src/main/java/kr/bb/apigateway/common/dto/AccessTokenExpiredResponse.java similarity index 82% rename from src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java rename to src/main/java/kr/bb/apigateway/common/dto/AccessTokenExpiredResponse.java index e75cdd4..fb599c7 100644 --- a/src/main/java/kr/bb/apigateway/common/dto/RenewAccessTokenDto.java +++ b/src/main/java/kr/bb/apigateway/common/dto/AccessTokenExpiredResponse.java @@ -12,8 +12,8 @@ @AllArgsConstructor @NoArgsConstructor @Getter -public class RenewAccessTokenDto { - T authId; +public class AccessTokenExpiredResponse { + ID id; Role role; } diff --git a/src/main/java/kr/bb/apigateway/common/dto/GatewayTokenHandlerUtil.java b/src/main/java/kr/bb/apigateway/common/dto/GatewayTokenHandlerUtil.java new file mode 100644 index 0000000..419f207 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/dto/GatewayTokenHandlerUtil.java @@ -0,0 +1,66 @@ +package kr.bb.apigateway.common.dto; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.ExpiredJwtException; +import java.util.HashMap; +import java.util.Map; +import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class GatewayTokenHandlerUtil { + + private GatewayTokenHandlerUtil(){ + + } + + + public static ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { + ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() + .headers(httpHeaders -> httpHeaders.add("userId", userId)) + .build(); + + return exchange.mutate() + .request(modifiedRequest) + .build(); + } + + + public static Mono handleExpiredToken(ExpiredJwtException exception, ServerWebExchange exchange, + HttpStatus status) { + ServerHttpResponse response = exchange.getResponse(); + + response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + + byte[] errorDto = expiredErrorDtoByte(exception); + DataBuffer buffer = response.bufferFactory().wrap(errorDto); + + response.setStatusCode(status); + return response.writeWith(Mono.just(buffer)); + } + + public static byte[] expiredErrorDtoByte(ExpiredJwtException exception) { + ObjectMapper mapper = new ObjectMapper(); + Map jsonResponse = new HashMap<>(); + jsonResponse.put("message", "Expired"); + jsonResponse.put("id", Long.valueOf(exception.getClaims().getSubject())); + jsonResponse.put("role", + exception.getClaims().get(SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME, String.class)); + + byte[] responseBytes; + try { + responseBytes = mapper.writeValueAsBytes(jsonResponse); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("토큰 만료 반환 데이터 Json Error"); + } + return responseBytes; + } +} diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtOptionalGatewayFilter.java b/src/main/java/kr/bb/apigateway/common/filter/JwtOptionalGatewayFilter.java new file mode 100644 index 0000000..9b5b18b --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtOptionalGatewayFilter.java @@ -0,0 +1,65 @@ +package kr.bb.apigateway.common.filter; + +import io.jsonwebtoken.ExpiredJwtException; +import kr.bb.apigateway.common.dto.GatewayTokenHandlerUtil; +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.util.JwtUtil; +import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Component +public class JwtOptionalGatewayFilter extends + AbstractGatewayFilterFactory { + + private final RedisBlackListTokenUtil redisBlackListTokenUtil; + + public JwtOptionalGatewayFilter(RedisBlackListTokenUtil redisBlackListTokenUtil) { + super(NameConfig.class); + this.redisBlackListTokenUtil = redisBlackListTokenUtil; + } + + @Override + public GatewayFilter apply(NameConfig config) { + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + if (ExtractAuthorizationTokenUtil.isThereToken(request)) { + return handleWhenThereIsToken(exchange, chain); + } else { + return chain.filter(exchange); + } + }; + } + + private void checkTokenIsBlackListed(String token) { + if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { + throw new IllegalArgumentException("로그아웃 처리된 토큰은 사용할 수 없습니다."); + } + } + + private Mono handleWhenThereIsToken(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + + String token = ExtractAuthorizationTokenUtil.extractToken(request); + checkTokenIsBlackListed(token); + + try { + JwtUtil.isTokenValid(token); + return chain.filter( + GatewayTokenHandlerUtil.addUserIdHeaderAtRequest(exchange, + JwtUtil.extractSubject(token))); + } catch (ExpiredJwtException e) { + return GatewayTokenHandlerUtil.handleExpiredToken(e, exchange, HttpStatus.UNAUTHORIZED); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("올바르지 않은 토큰입니다."); + } + } + + +} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java index 1b85945..8e05172 100644 --- a/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtValidationGatewayFilterFactory.java @@ -1,15 +1,15 @@ package kr.bb.apigateway.common.filter; import io.jsonwebtoken.ExpiredJwtException; +import kr.bb.apigateway.common.dto.GatewayTokenHandlerUtil; import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; import kr.bb.apigateway.common.util.JwtUtil; import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; -import lombok.RequiredArgsConstructor; import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -25,39 +25,33 @@ public JwtValidationGatewayFilterFactory(RedisBlackListTokenUtil redisBlackListT this.redisBlackListTokenUtil = redisBlackListTokenUtil; } + @Override public GatewayFilter apply(NameConfig config) { - return (exchange, chain) -> { - ServerHttpRequest request = exchange.getRequest(); - String token = ExtractAuthorizationTokenUtil.extractToken(request); - if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { - return handleError(exchange, HttpStatus.UNAUTHORIZED); - } else { - try { - JwtUtil.isTokenValid(token); - return chain.filter(addUserIdHeaderAtRequest(exchange, JwtUtil.extractSubject(token))); - } catch (ExpiredJwtException e) { - return handleError(exchange, HttpStatus.UNAUTHORIZED); - } - } - }; + return this::handleWhenThereIsToken; } - - private Mono handleError(ServerWebExchange exchange, HttpStatus status) { - ServerHttpResponse response = exchange.getResponse(); - response.setStatusCode(status); - return response.setComplete(); + private void checkTokenIsBlackListed(String token) { + if (redisBlackListTokenUtil.isTokenBlacklisted(token)) { + throw new IllegalArgumentException("로그아웃 처리된 토큰은 사용할 수 없습니다."); + } } - private ServerWebExchange addUserIdHeaderAtRequest(ServerWebExchange exchange, String userId) { - ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() - .headers(httpHeaders -> httpHeaders.add("userId", userId)) - .build(); - - return exchange.mutate() - .request(modifiedRequest) - .build(); + private Mono handleWhenThereIsToken(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + + String token = ExtractAuthorizationTokenUtil.extractToken(request); + checkTokenIsBlackListed(token); + + try { + JwtUtil.isTokenValid(token); + return chain.filter( + GatewayTokenHandlerUtil.addUserIdHeaderAtRequest(exchange, JwtUtil.extractSubject(token))); + } catch (ExpiredJwtException e) { + return GatewayTokenHandlerUtil.handleExpiredToken(e, exchange, HttpStatus.UNAUTHORIZED); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("올바르지 않은 토큰입니다."); + } } } \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java index e5a8819..d5fd92d 100644 --- a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java @@ -4,6 +4,7 @@ import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; public class ExtractAuthorizationTokenUtil { @@ -12,6 +13,14 @@ private ExtractAuthorizationTokenUtil() { } + public static boolean isThereToken(ServerHttpRequest request) { + HttpHeaders headers = request.getHeaders(); + String authorizationHeader = headers.getFirst( + SecurityPolicyStaticValue.TOKEN_AUTHORIZAION_HEADER_NAME); + return authorizationHeader != null && authorizationHeader.startsWith( + SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_PREFIX); + } + public static String extractToken(ServerHttpRequest request) { HttpHeaders headers = request.getHeaders(); String authorizationHeader = headers.getFirst( From 0120f291fa1ed96012e99dca753b2c4bbf78599a Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:21:17 +0900 Subject: [PATCH 34/42] add: add the socialOptionalAuthorizationGatewayFilter --- ...onalAuthorizationGatewayFilterFactory.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/java/kr/bb/apigateway/social/filter/SocialOptionalAuthorizationGatewayFilterFactory.java diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialOptionalAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/social/filter/SocialOptionalAuthorizationGatewayFilterFactory.java new file mode 100644 index 0000000..bbb32a6 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialOptionalAuthorizationGatewayFilterFactory.java @@ -0,0 +1,59 @@ +package kr.bb.apigateway.social.filter; + +import kr.bb.apigateway.common.util.ExtractAuthorizationTokenUtil; +import kr.bb.apigateway.common.valueobject.Role; +import kr.bb.apigateway.social.exception.SocialAuthException; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Component +public class SocialOptionalAuthorizationGatewayFilterFactory extends + AbstractGatewayFilterFactory { + + + public SocialOptionalAuthorizationGatewayFilterFactory() { + } + + public SocialOptionalAuthorizationGatewayFilterFactory( + Class configClass) { + super(configClass); + } + + @Override + public GatewayFilter apply(NameConfig config) { + return (exchange, chain) -> { + if (!ExtractAuthorizationTokenUtil.isThereToken(exchange.getRequest())) { + return chain.filter(exchange); + } else { + return handleWhenThereIsToken(exchange, chain); + } + }; + } + + private Mono handleWhenThereIsToken(ServerWebExchange exchange, GatewayFilterChain chain) + { + if (!isAuthorizedUser(exchange)) { + handleUnauthenticatedUser(exchange); + } + return chain.filter(exchange); + } + + private boolean isAuthorizedUser (ServerWebExchange exchange){ + String role = ExtractAuthorizationTokenUtil.extractRole(exchange.getRequest()); + return Role.ROLE_SOCIAL_USER.name().equals(role); + } + + private Mono handleUnauthenticatedUser (ServerWebExchange exchange){ + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + throw new SocialAuthException("소셜 유저가 아닙니다."); + } + + + } From 162998da947a12b386b69f3ed35f5c9ef37731db Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:46:46 +0900 Subject: [PATCH 35/42] refactor: refactor JwtUtil use the encrpyted key not Secret api --- build.gradle | 6 +- .../ApigatewayServiceApplication.java | 7 ++ .../apigateway/common/config/RedisConfig.java | 28 +++++ .../IssueRefreshRefreshTokenInCookie.java | 42 -------- .../JwtAccessTokenCreateProcessor.java | 28 ----- .../JwtAccessTokenDeleteStrategy.java | 7 -- .../common/security/RefreshTokenStrategy.java | 10 -- ...AccessTokenInRedisBlackListWhenLogout.java | 19 ---- .../common/security/TokenHandler.java | 29 ----- .../util/ExtractAuthorizationTokenUtil.java | 3 +- .../kr/bb/apigateway/common/util/JwtUtil.java | 100 ++++-------------- src/main/resources/application-local.yml | 4 + 12 files changed, 63 insertions(+), 220 deletions(-) create mode 100644 src/main/java/kr/bb/apigateway/common/config/RedisConfig.java delete mode 100644 src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java delete mode 100644 src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java delete mode 100644 src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenDeleteStrategy.java delete mode 100644 src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java delete mode 100644 src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java delete mode 100644 src/main/java/kr/bb/apigateway/common/security/TokenHandler.java diff --git a/build.gradle b/build.gradle index b2d5447..9f7a240 100644 --- a/build.gradle +++ b/build.gradle @@ -36,9 +36,13 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation "org.springframework.cloud:spring-cloud-starter-bus-kafka" implementation 'org.springframework.data:spring-data-redis' - implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' implementation 'org.springframework.kafka:spring-kafka' implementation 'redis.clients:jedis' + implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.1' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' + } dependencyManagement { diff --git a/src/main/java/kr/bb/apigateway/ApigatewayServiceApplication.java b/src/main/java/kr/bb/apigateway/ApigatewayServiceApplication.java index 6b26dfe..a319c47 100644 --- a/src/main/java/kr/bb/apigateway/ApigatewayServiceApplication.java +++ b/src/main/java/kr/bb/apigateway/ApigatewayServiceApplication.java @@ -2,7 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; +import org.springframework.context.annotation.Bean; +import reactor.core.publisher.Mono; @SpringBootApplication @EnableEurekaClient @@ -12,4 +15,8 @@ public static void main(String[] args) { SpringApplication.run(ApigatewayServiceApplication.class, args); } + @Bean + KeyResolver userKeyResolver() { + return exchange -> Mono.just("1"); + } } diff --git a/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java b/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java new file mode 100644 index 0000000..b1ee518 --- /dev/null +++ b/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java @@ -0,0 +1,28 @@ +package kr.bb.apigateway.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +public class RedisConfig { + + @Bean + public LettuceConnectionFactory connectionFactory() { + return new LettuceConnectionFactory(); + } + + @Bean + public RedisTemplate deliveryRedisTemplate( + RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + return template; + } + + + + +} diff --git a/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java b/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java deleted file mode 100644 index 4cc00fb..0000000 --- a/src/main/java/kr/bb/apigateway/common/security/IssueRefreshRefreshTokenInCookie.java +++ /dev/null @@ -1,42 +0,0 @@ -package kr.bb.apigateway.common.security; - -import java.time.Duration; -import javax.servlet.http.HttpServletResponse; -import kr.bb.apigateway.common.util.CookieUtil; -import kr.bb.apigateway.common.util.JwtUtil; -import kr.bb.apigateway.common.util.RedisRefreshTokenUtil; -import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - - -@RequiredArgsConstructor -@Component -public class IssueRefreshRefreshTokenInCookie implements - RefreshTokenStrategy { - - @Value("${cookie.refresh.http.domain}") - private String domain; - @Value("${cookie.refresh.token.name}") - private String refreshCookieName; - - private final RedisRefreshTokenUtil redisRefreshTokenUtil; - - @Override - public void createRefreshToken(String userId,HttpServletResponse response) { - String refreshToken = JwtUtil.generateRefreshToken(String.valueOf(userId)); - redisRefreshTokenUtil.saveRefreshToken(userId, refreshToken, - Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME)); - response.addCookie(CookieUtil.createHttpOnlyCookie(refreshCookieName, refreshToken, - Duration.ofDays(1), domain)); - } - - @Override - public void invalidateRefreshToken(String id, HttpServletResponse response) { - CookieUtil.deleteCookie(refreshCookieName, domain); - redisRefreshTokenUtil.deleteRefreshToken(id); - } - - -} diff --git a/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java b/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java deleted file mode 100644 index be62069..0000000 --- a/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenCreateProcessor.java +++ /dev/null @@ -1,28 +0,0 @@ -package kr.bb.apigateway.common.security; - -import java.util.Map; -import kr.bb.apigateway.common.util.JwtUtil; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - - -@RequiredArgsConstructor -@Component -public class JwtAccessTokenCreateProcessor { - - private String createAccessTokenWithoutClaims(String subject){ - return JwtUtil.generateAccessToken(subject); - } - private String createAccessTokenWithClaims(String subject , Map claimsList){ - return JwtUtil.generateAccessTokenWithClaims(subject,claimsList); - } - - public String createAccessToken(String subject , Map claimsList){ - if(claimsList==null) return createAccessTokenWithoutClaims(subject); - else{ - return createAccessTokenWithClaims(subject,claimsList); - } - } - - -} diff --git a/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenDeleteStrategy.java b/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenDeleteStrategy.java deleted file mode 100644 index 6757b2a..0000000 --- a/src/main/java/kr/bb/apigateway/common/security/JwtAccessTokenDeleteStrategy.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.bb.apigateway.common.security; - - -public interface JwtAccessTokenDeleteStrategy { - public void invalidateAccessToken(String token); - -} diff --git a/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java b/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java deleted file mode 100644 index 7498831..0000000 --- a/src/main/java/kr/bb/apigateway/common/security/RefreshTokenStrategy.java +++ /dev/null @@ -1,10 +0,0 @@ -package kr.bb.apigateway.common.security; - -import javax.servlet.http.HttpServletResponse; -import org.springframework.stereotype.Component; - -@Component -public interface RefreshTokenStrategy { - public void createRefreshToken(String id, HttpServletResponse response); - public void invalidateRefreshToken(String id, HttpServletResponse response); -} \ No newline at end of file diff --git a/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java b/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java deleted file mode 100644 index 64d59dd..0000000 --- a/src/main/java/kr/bb/apigateway/common/security/RegisterAccessTokenInRedisBlackListWhenLogout.java +++ /dev/null @@ -1,19 +0,0 @@ -package kr.bb.apigateway.common.security; - - -import kr.bb.apigateway.common.util.RedisBlackListTokenUtil; -import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -@RequiredArgsConstructor -@Component -public class RegisterAccessTokenInRedisBlackListWhenLogout implements - JwtAccessTokenDeleteStrategy { - private final RedisBlackListTokenUtil redisBlackListTokenUtil; - @Override - public void invalidateAccessToken(String token) { - redisBlackListTokenUtil.addTokenToBlacklist(token, - Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME)); - } -} diff --git a/src/main/java/kr/bb/apigateway/common/security/TokenHandler.java b/src/main/java/kr/bb/apigateway/common/security/TokenHandler.java deleted file mode 100644 index 89e5fcc..0000000 --- a/src/main/java/kr/bb/apigateway/common/security/TokenHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package kr.bb.apigateway.common.security; - -import java.util.Map; -import javax.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -@RequiredArgsConstructor -@Component -public class TokenHandler { - - private final RefreshTokenStrategy refreshTokenStrategy; - private final JwtAccessTokenCreateProcessor accessTokenStrategy; - private final JwtAccessTokenDeleteStrategy deleteStrategy; - - - public String createToken(String id, - Map claimList,HttpServletResponse response) { - refreshTokenStrategy.createRefreshToken(id,response); - return accessTokenStrategy.createAccessToken(id, claimList); - } - - public void invalidateToken(String id, String token, HttpServletResponse response) { - deleteStrategy.invalidateAccessToken(token); - refreshTokenStrategy.invalidateRefreshToken(id, response); - } - - -} diff --git a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java index d5fd92d..c4abcd5 100644 --- a/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/ExtractAuthorizationTokenUtil.java @@ -4,7 +4,6 @@ import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; public class ExtractAuthorizationTokenUtil { @@ -40,7 +39,7 @@ public static String extractUserId(ServerHttpRequest request) { public static String extractRole(ServerHttpRequest request) { String token = ExtractAuthorizationTokenUtil.extractToken(request); - Claims claims = JwtUtil.extractClaims(token); + Claims claims = JwtUtil.extractAccessTokenClaims(token); return (String) claims.get(SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME); } diff --git a/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java index 3ad15b9..734d84a 100644 --- a/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/JwtUtil.java @@ -6,114 +6,50 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import kr.bb.apigateway.common.valueobject.SecurityPolicyStaticValue; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class JwtUtil { + public static String accessKey; + public static String refreshKey; - private JwtUtil(){ + private JwtUtil() { } - private static SecretKey accessSecret; - private static SecretKey refreshSecret; - - - public static String generateAccessTokenWithClaims(String subject, - Map claimsList) { - Date now = new Date(); - - initAccessKey(); - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(now) - .setExpiration(Date.from(Instant.now().plusSeconds( - Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME)))) - .signWith(accessSecret) - .addClaims(claimsList) - .compact(); - } - - public static Map addClaims(String id, Object value) { - Map claims = new HashMap<>(); - claims.put(id, value); - return claims; + @Value("${encrypt.key.access}") + private void setAccessKey(String accessKey) { + JwtUtil.accessKey = accessKey; } - public static String - generateAccessToken(String subject) { - Date now = new Date(); - - initAccessKey(); - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(now) - .setExpiration(Date.from(Instant.now().plusSeconds( - Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME)))) - .signWith(accessSecret) - .compact(); - } - - public static String generateRefreshToken(String subject) { - Date now = new Date(); - initRefreshKey(); - - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(now) - .setExpiration(Date.from(Instant.now().plusSeconds( - Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME)))) - .signWith(refreshSecret) - .compact(); + @Value("${encrypt.key.refresh}") + private void setRefreshKey(String refreshKey) { + JwtUtil.refreshKey = refreshKey; } public static String extractSubject(String token) { - return extractClaims(token).getSubject(); + return extractAccessTokenClaims(token).getSubject(); } - public static Claims extractClaims(String token) { - return Jwts.parserBuilder().setSigningKey(accessSecret).build().parseClaimsJws(token) + public static Claims extractAccessTokenClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(accessKey.getBytes()).build() + .parseClaimsJws(token) .getBody(); } public static boolean isTokenValid(String token) { try { - extractClaims(token); + extractAccessTokenClaims(token); return true; } catch (ExpiredJwtException e) { - throw new ExpiredJwtException(e.getHeader(),e.getClaims(),e.getMessage()) { + throw new ExpiredJwtException(e.getHeader(), e.getClaims(), e.getMessage()) { }; } catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) { throw new IllegalArgumentException("올바르지 않은 접근입니다."); } } - private static void initRefreshKey() { - try { - refreshSecret = KeyGenerator.getInstance("HmacSHA256").generateKey(); - - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("plz init the secret key"); - } - } - - - private static void initAccessKey() { - try { - accessSecret = KeyGenerator.getInstance("HmacSHA256").generateKey(); - - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("plz init the secret key"); - } - - } - } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index c0aef78..33e8de0 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -41,3 +41,7 @@ logging: cloud: gateway: TRACE +encrypt: + key: + access: access-keyaccess-keyaccess-keyaccess-keyaccess-keyaccess-keyaccess-key + refresh: refresh-keyrefresh-keyrefresh-keyrefresh-keyrefresh-keyrefresh-keyfresh-key \ No newline at end of file From 9eb295471c52d20a3d1ffe0c0cd08ac3554547b0 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Tue, 19 Dec 2023 17:27:14 +0900 Subject: [PATCH 36/42] refactor: refactor the Authorization filter --- build.gradle | 1 + .../apigateway/common/filter/JwtOptionalGatewayFilter.java | 2 +- .../filter/SocialAuthorizationGatewayFilterFactory.java | 7 +------ .../filter/StoreAuthorizationGatewayFilterFactory.java | 7 ++----- .../SystemAdminAuthorizationGatewayFilterFactory.java | 4 ---- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 9f7a240..6263522 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation "org.springframework.cloud:spring-cloud-starter-bus-kafka" implementation 'org.springframework.data:spring-data-redis' + implementation 'org.springframework.cloud:spring-cloud-loadbalancer' implementation 'org.springframework.kafka:spring-kafka' implementation 'redis.clients:jedis' implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive' diff --git a/src/main/java/kr/bb/apigateway/common/filter/JwtOptionalGatewayFilter.java b/src/main/java/kr/bb/apigateway/common/filter/JwtOptionalGatewayFilter.java index 9b5b18b..589f65d 100644 --- a/src/main/java/kr/bb/apigateway/common/filter/JwtOptionalGatewayFilter.java +++ b/src/main/java/kr/bb/apigateway/common/filter/JwtOptionalGatewayFilter.java @@ -16,7 +16,7 @@ @Component public class JwtOptionalGatewayFilter extends - AbstractGatewayFilterFactory { + AbstractGatewayFilterFactory { private final RedisBlackListTokenUtil redisBlackListTokenUtil; diff --git a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java index 078eb69..18331bc 100644 --- a/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java +++ b/src/main/java/kr/bb/apigateway/social/filter/SocialAuthorizationGatewayFilterFactory.java @@ -17,13 +17,8 @@ public class SocialAuthorizationGatewayFilterFactory extends AbstractGatewayFilterFactory { - public SocialAuthorizationGatewayFilterFactory() { - } - - public SocialAuthorizationGatewayFilterFactory( - Class configClass) { - super(configClass); + super(NameConfig.class); } @Override diff --git a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java index 51cac88..b631200 100644 --- a/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java +++ b/src/main/java/kr/bb/apigateway/store/filter/StoreAuthorizationGatewayFilterFactory.java @@ -20,12 +20,9 @@ public class StoreAuthorizationGatewayFilterFactory extends AbstractGatewayFilterFactory { - public StoreAuthorizationGatewayFilterFactory() { - } - public StoreAuthorizationGatewayFilterFactory( - Class configClass) { - super(configClass); + public StoreAuthorizationGatewayFilterFactory() { + super(NameConfig.class); } @Override diff --git a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java index cc84fb7..10edb95 100644 --- a/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java +++ b/src/main/java/kr/bb/apigateway/systsem/filter/SystemAdminAuthorizationGatewayFilterFactory.java @@ -22,10 +22,6 @@ public SystemAdminAuthorizationGatewayFilterFactory() { super(NameConfig.class); } - public SystemAdminAuthorizationGatewayFilterFactory( - Class configClass) { - super(configClass); - } @Override public GatewayFilter apply(NameConfig config) { From a45361fb9873e62e4d5e271e1c21ac3135d2fe0e Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Tue, 19 Dec 2023 17:29:51 +0900 Subject: [PATCH 37/42] remove: remove un-neccesary dependency --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6263522..9f7a240 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,6 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation "org.springframework.cloud:spring-cloud-starter-bus-kafka" implementation 'org.springframework.data:spring-data-redis' - implementation 'org.springframework.cloud:spring-cloud-loadbalancer' implementation 'org.springframework.kafka:spring-kafka' implementation 'redis.clients:jedis' implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive' From c1511038ea67f6a7c00dec3a4add8b46fd6f88bc Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Tue, 19 Dec 2023 19:43:49 +0900 Subject: [PATCH 38/42] add: add the redis config --- .../apigateway/common/config/RedisConfig.java | 35 +++++++++++++++---- .../common/util/RedisRefreshTokenUtil.java | 18 ++++++---- src/main/resources/application-dev.yml | 7 +--- src/main/resources/application-local.yml | 5 ++- src/main/resources/application-prod.yml | 7 ---- 5 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java b/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java index b1ee518..f2b5527 100644 --- a/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java +++ b/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java @@ -1,24 +1,45 @@ package kr.bb.apigateway.common.config; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericToStringSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { + @Value("${spring.redis.host}") + private String host; + @Value("${spring.redis.port}") + private int port; + @Value("${spring.redis.password}") + private String password; + + + @Bean - public LettuceConnectionFactory connectionFactory() { - return new LettuceConnectionFactory(); + JedisConnectionFactory jedisConnectionFactory() { + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration( + host, port); + redisStandaloneConfiguration.setHostName(host); + redisStandaloneConfiguration.setPort(port); + redisStandaloneConfiguration.setPassword(password); + return new JedisConnectionFactory(redisStandaloneConfiguration); } @Bean - public RedisTemplate deliveryRedisTemplate( - RedisConnectionFactory connectionFactory) { + public RedisTemplate redisTemplate( + JedisConnectionFactory jedisConnectionFactory) { RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(connectionFactory); + template.setConnectionFactory(jedisConnectionFactory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new StringRedisSerializer()); + template.setHashKeySerializer(new GenericToStringSerializer<>(String.class)); + template.setHashValueSerializer(new GenericToStringSerializer<>(String.class)); return template; } diff --git a/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java index 238d5d8..f9e076a 100644 --- a/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java @@ -1,22 +1,28 @@ package kr.bb.apigateway.common.util; import java.util.concurrent.TimeUnit; -import lombok.RequiredArgsConstructor; +import lombok.NoArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; -@RequiredArgsConstructor +@NoArgsConstructor @Component -public class -RedisRefreshTokenUtil { +public class RedisRefreshTokenUtil { + + private RedisTemplate redisTemplate; + + public RedisRefreshTokenUtil( + RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } - private final RedisTemplate redisTemplate; public void saveRefreshToken(String userId, String refreshToken, long expirationTimeInSeconds) { String key = getRefreshTokenKey(userId); - redisTemplate.opsForValue().set(key, refreshToken, expirationTimeInSeconds, TimeUnit.SECONDS); + redisTemplate.opsForValue() + .set(key, refreshToken, expirationTimeInSeconds, TimeUnit.SECONDS); } public String getRefreshToken(String userId) { diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 05b5d36..2455566 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -17,11 +17,6 @@ management: - "refresh" - "bus-refresh" -cookie: - refresh: - http: - domain: http://localhost:8000 - token: - name: refresh-cookie + diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 33e8de0..a561d55 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,6 +1,7 @@ server: port: 8000 spring: + cloud: gateway: httpclient: @@ -10,8 +11,10 @@ spring: main: web-application-type: reactive redis: - host: localhost + host: redis port: 6379 + password: 123456 + application: name: apigateway-service config: diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 2d1e4fb..859a00b 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -16,10 +16,3 @@ management: include: - "refresh" - "bus-refresh" - -cookie: - refresh: - http: - domain: http://localhost:8000 - token: - name: refresh-cookie From 718db55ee49e2f42cbd85554d5ff16cf1b31ffd7 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Tue, 19 Dec 2023 20:25:50 +0900 Subject: [PATCH 39/42] refactor: refactor the jedis config to redis config --- .../bb/apigateway/common/config/RedisConfig.java | 14 +++++++------- src/main/resources/application-local.yml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java b/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java index f2b5527..e318f78 100644 --- a/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java +++ b/src/main/java/kr/bb/apigateway/common/config/RedisConfig.java @@ -3,8 +3,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericToStringSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -22,20 +23,19 @@ public class RedisConfig { @Bean - JedisConnectionFactory jedisConnectionFactory() { - RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration( - host, port); + RedisConnectionFactory jedisConnectionFactory() { + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(host); redisStandaloneConfiguration.setPort(port); redisStandaloneConfiguration.setPassword(password); - return new JedisConnectionFactory(redisStandaloneConfiguration); + return new LettuceConnectionFactory(redisStandaloneConfiguration); } @Bean public RedisTemplate redisTemplate( - JedisConnectionFactory jedisConnectionFactory) { + RedisConnectionFactory redisConnectionFactory) { RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(jedisConnectionFactory); + template.setConnectionFactory(redisConnectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new StringRedisSerializer()); template.setHashKeySerializer(new GenericToStringSerializer<>(String.class)); diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index a561d55..8653750 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -11,7 +11,7 @@ spring: main: web-application-type: reactive redis: - host: redis + host: localhost port: 6379 password: 123456 From 6bca27dde2f4e2198c1aa04d0d90ca62c85dd944 Mon Sep 17 00:00:00 2001 From: JIUNG GU <60885635+JIUNG9@users.noreply.github.com> Date: Tue, 19 Dec 2023 21:05:26 +0900 Subject: [PATCH 40/42] fix: fix the redis key type --- .../kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java b/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java index f9e076a..fb563ed 100644 --- a/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java +++ b/src/main/java/kr/bb/apigateway/common/util/RedisRefreshTokenUtil.java @@ -2,6 +2,7 @@ import java.util.concurrent.TimeUnit; import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -10,10 +11,11 @@ @Component public class RedisRefreshTokenUtil { - private RedisTemplate redisTemplate; + private RedisTemplate redisTemplate; + @Autowired public RedisRefreshTokenUtil( - RedisTemplate redisTemplate) { + RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } From be33422293ae9df05af8ca5d899f748740cbc880 Mon Sep 17 00:00:00 2001 From: nowgnas Date: Wed, 3 Jan 2024 20:57:25 +0900 Subject: [PATCH 41/42] :bookmark: Add release --- .github/workflows/release-drafter-config.yml | 31 ++++++++++ .github/workflows/release.yml | 61 ++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 .github/workflows/release-drafter-config.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release-drafter-config.yml b/.github/workflows/release-drafter-config.yml new file mode 100644 index 0000000..f944a0a --- /dev/null +++ b/.github/workflows/release-drafter-config.yml @@ -0,0 +1,31 @@ +name-template: '🚀 v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +categories: + - title: '💡 Features' + labels: + - '🌈 Feature' + - title: '🐛 Bug fixes' + labels: + - '⚠️ Bug' + - title: '🤯 Inner Changes' + labels: + - '📦 Chore' + - '📃 Docs' +change-template: '- $TITLE #$NUMBER @$AUTHOR ' +change-title-escapes: '\<*_&' +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch +template: | + ## 🚀 이번 버전에서는 어떤 게 바뀌었을까요? + --- + $CHANGES +no-changes-template: '앗! 변경사항이 없어요. 😖' \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1280cb0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: Release Tag + +on: + push: + branches: + - main + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter-config.yml + env: + GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }} + + deployment: + name: Setup, Build, and Deploy + runs-on: ubuntu-latest + permissions: + packages: write + contents: write + id-token: write + steps: + - name: Bump version and push tag + id: tag_version + uses: mathieudutour/github-tag-action@v6.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + # Set the new tag as a variable + - name: Set New Tag Variable + id: set_new_tag + run: echo "NEW_TAG=${{ steps.tag_version.outputs.new_tag }}" >> $GITHUB_ENV + + - name: Create a GitHub release + uses: ncipollo/release-action@v1 + with: + tag: ${{ steps.tag_version.outputs.new_tag }} + name: Release ${{ steps.tag_version.outputs.new_tag }} + body: ${{ steps.tag_version.outputs.changelog }} + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DEV_DOCKER_ID }} + password: ${{ secrets.DEV_DOCKER_PW }} + + - name: deploy + id: docker_build + uses: docker/build-push-action@v2 + with: + platforms: linux/amd64,linux/arm64 + push: true + tags: nowgnas/bb-apigateway:${{ steps.tag_version.outputs.new_tag }}, nowgnas/bb-apigateway:latest From 4325e8562016dabdf7fe812a4f2b73bf2e542716 Mon Sep 17 00:00:00 2001 From: nowgnas Date: Sun, 7 Jan 2024 16:34:40 +0900 Subject: [PATCH 42/42] :bookmark: Add release change log automation --- .github/release.yml | 33 ++++++++++++++++++++ .github/workflows/release-drafter-config.yml | 31 ------------------ .github/workflows/release.yml | 2 +- 3 files changed, 34 insertions(+), 32 deletions(-) create mode 100644 .github/release.yml delete mode 100644 .github/workflows/release-drafter-config.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..7eefe55 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,33 @@ +name-template: "apigateway v$RESOLVED_VERSION" +tag-template: "v$RESOLVED_VERSION" +categories: + - title: "🆕 새로운 기능이 추가되었어요!" + label: "✨ Feature" + - title: "🐞 자잘한 버그를 수정했습니다." + label: "🐞 Bugfix" + - title: "🫶🏻 앱 사용성 개선에 힘썼습니다." + label: "🫶🏻 Improvement" + - title: "🛠️ 더 나은 코드를 위해 노력하고 있습니다." + labels: + - "🔨 Refactor" + - "⚙️ Setting" + - title: "ETC" + labels: + - "*" +change-template: "* $TITLE (#$NUMBER) by @$AUTHOR" +change-title-escapes: '\<*_&#@`' +exclude-labels: + - "Main" +version-resolver: + major: + labels: + - "Major" + minor: + labels: + - "Minor" + patch: + labels: + - "Patch" + default: patch +template: | + $CHANGES diff --git a/.github/workflows/release-drafter-config.yml b/.github/workflows/release-drafter-config.yml deleted file mode 100644 index f944a0a..0000000 --- a/.github/workflows/release-drafter-config.yml +++ /dev/null @@ -1,31 +0,0 @@ -name-template: '🚀 v$RESOLVED_VERSION' -tag-template: 'v$RESOLVED_VERSION' -categories: - - title: '💡 Features' - labels: - - '🌈 Feature' - - title: '🐛 Bug fixes' - labels: - - '⚠️ Bug' - - title: '🤯 Inner Changes' - labels: - - '📦 Chore' - - '📃 Docs' -change-template: '- $TITLE #$NUMBER @$AUTHOR ' -change-title-escapes: '\<*_&' -version-resolver: - major: - labels: - - 'major' - minor: - labels: - - 'minor' - patch: - labels: - - 'patch' - default: patch -template: | - ## 🚀 이번 버전에서는 어떤 게 바뀌었을까요? - --- - $CHANGES -no-changes-template: '앗! 변경사항이 없어요. 😖' \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1280cb0..2c83205 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: release-drafter/release-drafter@v5 with: - config-name: release-drafter-config.yml + config-name: release.yml env: GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}