From f0da2aa295f8020543cdaa0daa2f49d8f97c332c Mon Sep 17 00:00:00 2001 From: KodaHye Date: Mon, 16 Dec 2024 23:23:22 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=B4=88=EA=B8=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TalkTalk_BE/build.gradle" | 30 +++ .../TalkTalk_BE/gradlew" | 252 ++++++++++++++++++ .../TalkTalk_BE/gradlew.bat" | 94 +++++++ .../TalkTalk_BE/settings.gradle" | 1 + .../talktalk_be/TalkTalkBeApplication.java" | 14 + .../src/main/resources/application.yaml" | 19 ++ .../TalkTalkBeApplicationTests.java" | 8 + 7 files changed, 418 insertions(+) create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew.bat" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/settings.gradle" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/TalkTalkBeApplicationTests.java" diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" new file mode 100644 index 0000000..b9d8d05 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" @@ -0,0 +1,30 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.3.6' + id 'io.spring.dependency-management' version '1.1.6' +} + +group = 'com.example' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + runtimeOnly 'com.mysql:mysql-connector-j' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew" new file mode 100644 index 0000000..f5feea6 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew" @@ -0,0 +1,252 @@ +#!/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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# 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/platforms/jvm/plugins-application/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 -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || 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/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew.bat" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew.bat" new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew.bat" @@ -0,0 +1,94 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@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. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +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/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/settings.gradle" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/settings.gradle" new file mode 100644 index 0000000..b9d126c --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/settings.gradle" @@ -0,0 +1 @@ +rootProject.name = 'TalkTalk_BE' diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" new file mode 100644 index 0000000..a5a69c9 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" @@ -0,0 +1,14 @@ +package com.example.talktalk_be; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@SpringBootApplication +@EnableJpaAuditing +public class TalkTalkBeApplication { + + public static void main(String[] args) { + SpringApplication.run(TalkTalkBeApplication.class, args); + } +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml" new file mode 100644 index 0000000..d03e2f2 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml" @@ -0,0 +1,19 @@ +spring: + profiles: + include: oauth + jpa: + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true + show_sql: true + open-in-view: false + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:${MYSQL_PORT}/${MYSQL_SCHEMA} + username: ${MYSQL_USERNAME} + password: ${MYSQL_PASSWORD} + + config: + import: optional:file:.env[.properties] \ No newline at end of file diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/TalkTalkBeApplicationTests.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/TalkTalkBeApplicationTests.java" new file mode 100644 index 0000000..60884c7 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/TalkTalkBeApplicationTests.java" @@ -0,0 +1,8 @@ +package com.example.talktalk_be; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class TalkTalkBeApplicationTests { +} From 05984945ebb66b73d50c0a07bbe1339928860183 Mon Sep 17 00:00:00 2001 From: KodaHye Date: Wed, 18 Dec 2024 23:21:18 +0900 Subject: [PATCH 02/12] =?UTF-8?q?chore:=20swagger=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TalkTalk_BE/build.gradle" | 1 + 1 file changed, 1 insertion(+) diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" index b9d8d05..487a182 100644 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" @@ -20,6 +20,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' runtimeOnly 'com.mysql:mysql-connector-j' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' From d7edabd0401a04753fd114466a42758945f5fee7 Mon Sep 17 00:00:00 2001 From: KodaHye Date: Thu, 19 Dec 2024 22:53:44 +0900 Subject: [PATCH 03/12] =?UTF-8?q?chore:=20config=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talktalk_be/config/SwaggerConfig.java" | 22 +++++++++++++++++++ .../talktalk_be/config/WebConfig.java" | 16 ++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/WebConfig.java" diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java" new file mode 100644 index 0000000..a131f8d --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java" @@ -0,0 +1,22 @@ +package com.example.talktalk_be.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + @Bean + public OpenAPI openAPI() { + return new OpenAPI().components(new Components()).info(apiInfo()); + } + + private Info apiInfo() { + return new Info() + .title("TalkTalk Swagger") + .description("톡톡 REST API") + .version("0.1"); + } +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/WebConfig.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/WebConfig.java" new file mode 100644 index 0000000..361c9f5 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/WebConfig.java" @@ -0,0 +1,16 @@ +package com.example.talktalk_be.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration // Spring 서버 전역적으로 CORS 설정 +public class WebConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("*") + .allowCredentials(true); + } +} From 793a8ac8e87472aa2bb4a96ab1437b46a3209214 Mon Sep 17 00:00:00 2001 From: KodaHye Date: Thu, 19 Dec 2024 22:54:44 +0900 Subject: [PATCH 04/12] =?UTF-8?q?env:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20API?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talktalk_be/src/main/resources/application.yaml" | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) rename "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml" => "\352\263\240\353\213\244\355\230\234_TalkTalk/talktalk_be/src/main/resources/application.yaml" (63%) diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/talktalk_be/src/main/resources/application.yaml" similarity index 63% rename from "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml" rename to "\352\263\240\353\213\244\355\230\234_TalkTalk/talktalk_be/src/main/resources/application.yaml" index d03e2f2..010655e 100644 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml" +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/talktalk_be/src/main/resources/application.yaml" @@ -1,3 +1,6 @@ +#logging: +# config: classpath:log4j2.xml + spring: profiles: include: oauth @@ -16,4 +19,10 @@ spring: password: ${MYSQL_PASSWORD} config: - import: optional:file:.env[.properties] \ No newline at end of file + import: optional:file:.env[.properties] + +oauth: + kakao: + rest_api: ${KAKAO_REST_API_KEY} + redirect_uri: ${KAKAO_REDIRECT_URI} + client_secret: ${KAKAO_CLIENT_SECRET} From 767867a4b68eea1411a4869f2569e0d460f47a90 Mon Sep 17 00:00:00 2001 From: KodaHye Date: Thu, 19 Dec 2024 22:56:47 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=EC=9D=B8=EA=B0=80=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=ED=86=B5=ED=95=9C=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TalkTalk_BE/build.gradle" | 19 +++++++++++ .../talktalk_be/TalkTalkBeApplication.java" | 2 ++ .../member/controller/AuthController.java" | 29 ++++++++++++++++ .../dto/response/KakaoTokenResponseDto.java" | 34 +++++++++++++++++++ .../openFeignClient/KakaoAuthClient.java" | 19 +++++++++++ 5 files changed, 103 insertions(+) create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoTokenResponseDto.java" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/openFeignClient/KakaoAuthClient.java" diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" index 487a182..94ce158 100644 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" @@ -17,15 +17,34 @@ repositories { mavenCentral() } + dependencies { +// implementation 'org.springframework.boot:spring-boot-starter-log4j2' + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' runtimeOnly 'com.mysql:mysql-connector-j' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' } tasks.named('test') { useJUnitPlatform() } + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:2023.0.2" + } +} + +//configurations { +// all { +// exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' +// } +//} + + diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" index a5a69c9..97a8665 100644 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication @EnableJpaAuditing +@EnableFeignClients public class TalkTalkBeApplication { public static void main(String[] args) { diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" new file mode 100644 index 0000000..333c19b --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" @@ -0,0 +1,29 @@ +package com.example.talktalk_be.domain.member.controller; + +import com.example.talktalk_be.domain.member.dto.response.KakaoAuthCodeResponseDto; +import com.example.talktalk_be.domain.member.dto.response.KakaoTokenResponseDto; +import com.example.talktalk_be.domain.member.openFeignClient.KakaoAuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/auth/kakao") +public class AuthController { + + private final KakaoAuthService kakaoAuthService; + + @GetMapping("/redirect") + public ResponseEntity getAuthCode() { + return ResponseEntity.ok().build(); + } + + @GetMapping("/get-tokens") + public ResponseEntity getAuthorizationCode(@RequestParam String code) { + return ResponseEntity.ok(kakaoAuthService.getKakaoToken(code)); + } +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoTokenResponseDto.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoTokenResponseDto.java" new file mode 100644 index 0000000..3f376c1 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoTokenResponseDto.java" @@ -0,0 +1,34 @@ +package com.example.talktalk_be.domain.member.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class KakaoTokenResponseDto { + @JsonProperty("token_type") + private String tokenType; + @JsonProperty("access_token") + private String accessToken; + @JsonProperty("id_token") + private String idToken; + @JsonProperty("expires_in") + private Integer expiresIn; + @JsonProperty("refresh_token") + private String refreshToken; + @JsonProperty("refresh_token_expires_in") + private Integer refreshTokenExpiresIn; + @JsonProperty("scope") + private String scope; + + @Builder + public KakaoTokenResponseDto(String tokenType, String accessToken, String idToken, Integer expiresIn, String refreshToken, Integer refreshTokenExpiresIn, String scope) { + this.tokenType = tokenType; + this.accessToken = accessToken; + this.idToken = idToken; + this.expiresIn = expiresIn; + this.refreshToken = refreshToken; + this.refreshTokenExpiresIn = refreshTokenExpiresIn; + this.scope = scope; + } +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/openFeignClient/KakaoAuthClient.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/openFeignClient/KakaoAuthClient.java" new file mode 100644 index 0000000..98d7aff --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/openFeignClient/KakaoAuthClient.java" @@ -0,0 +1,19 @@ +package com.example.talktalk_be.domain.openFeignClient; + +import com.example.talktalk_be.domain.member.dto.response.KakaoTokenResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "kakao-auth-service", url = "https://kauth.kakao.com/oauth") +public interface KakaoAuthClient { + + @Operation(summary = "카카오 로그인을 위한 토큰을 받는 api") + @PostMapping("/token") + KakaoTokenResponseDto getKakaoToken(@RequestParam("grant_type") String grantType, + @RequestParam("code") String code, + @RequestParam("client_id") String clientId, + @RequestParam("redirect_uri") String redirectUri, + @RequestParam("client_secret") String clientSecret); +} From ab66a92f7250280774dfc0825a61271e40ad99f2 Mon Sep 17 00:00:00 2001 From: KodaHye Date: Fri, 20 Dec 2024 22:46:34 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/AuthController.java" | 20 ++++-- .../response/KakaoMemberInfoResponseDto.java" | 40 +++++++++++ .../openFeignClient/KakaoAuthClient.java" | 2 +- .../member/openFeignClient/KakaoClient.java" | 14 ++++ .../member/persistence/entity/Member.java" | 61 +++++++++++++++++ .../member/service/KakaoAuthService.java" | 68 +++++++++++++++++++ .../global/entity/BaseEntity.java" | 22 ++++++ 7 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoMemberInfoResponseDto.java" rename "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/openFeignClient/KakaoAuthClient.java" => "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoAuthClient.java" (93%) create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoClient.java" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/KakaoAuthService.java" create mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java" diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" index 333c19b..92dea57 100644 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" @@ -1,8 +1,9 @@ package com.example.talktalk_be.domain.member.controller; -import com.example.talktalk_be.domain.member.dto.response.KakaoAuthCodeResponseDto; +import com.example.talktalk_be.domain.member.dto.response.KakaoMemberInfoResponseDto; +import com.example.talktalk_be.domain.member.dto.response.KakaoRedirectUrlResponseDto; import com.example.talktalk_be.domain.member.dto.response.KakaoTokenResponseDto; -import com.example.talktalk_be.domain.member.openFeignClient.KakaoAuthService; +import com.example.talktalk_be.domain.member.service.KakaoAuthService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -18,12 +19,19 @@ public class AuthController { private final KakaoAuthService kakaoAuthService; @GetMapping("/redirect") - public ResponseEntity getAuthCode() { - return ResponseEntity.ok().build(); + public ResponseEntity getAuthCode() { + return ResponseEntity.ok(kakaoAuthService.getRedirectUrl()); } - @GetMapping("/get-tokens") + @GetMapping("/access_token") public ResponseEntity getAuthorizationCode(@RequestParam String code) { - return ResponseEntity.ok(kakaoAuthService.getKakaoToken(code)); + return ResponseEntity.ok(kakaoAuthService.getToken(code)); + } + + // 토큰 정보 보기로 액세스 토큰의 유효성을 검증한 이후, 사용자 정보 가져오기를 요청해 + // 필요한 사용자 정보를 받아 서비스 회원 가입 및 로그인 진행 + @GetMapping("/login") + public ResponseEntity loginByToken(@RequestParam("access_token") String accessToken) { + return ResponseEntity.ok(kakaoAuthService.loginByToken(accessToken)); } } diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoMemberInfoResponseDto.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoMemberInfoResponseDto.java" new file mode 100644 index 0000000..107b364 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoMemberInfoResponseDto.java" @@ -0,0 +1,40 @@ +package com.example.talktalk_be.domain.member.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.*; + +@Getter +@ToString +@AllArgsConstructor +public class KakaoMemberInfoResponseDto { + @JsonProperty("id") + private Long id; + @JsonProperty("kakao_account") + private KakaoAccount kakaoAccount; + @JsonProperty("properties") + private JsonNode properties; +// private String nickname; +// private String email; + + @Getter + @AllArgsConstructor + public static class KakaoAccount { + @JsonProperty("profile_nickname_needs_agreement") + private Boolean profileNicknameNeedsAgreement; + private Profile profile; + @JsonProperty("is_email_valid") + private Boolean isEmailValid; + @JsonProperty("is_email_verified") + private Boolean isEmailVerified; + private String email; + } + + @Getter + @AllArgsConstructor + public static class Profile{ + private String nickname; + @JsonProperty("profile_image_url") + private String profileImageUrl; + } +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/openFeignClient/KakaoAuthClient.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoAuthClient.java" similarity index 93% rename from "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/openFeignClient/KakaoAuthClient.java" rename to "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoAuthClient.java" index 98d7aff..2bf5422 100644 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/openFeignClient/KakaoAuthClient.java" +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoAuthClient.java" @@ -1,4 +1,4 @@ -package com.example.talktalk_be.domain.openFeignClient; +package com.example.talktalk_be.domain.member.openFeignClient; import com.example.talktalk_be.domain.member.dto.response.KakaoTokenResponseDto; import io.swagger.v3.oas.annotations.Operation; diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoClient.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoClient.java" new file mode 100644 index 0000000..00814da --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoClient.java" @@ -0,0 +1,14 @@ +package com.example.talktalk_be.domain.member.openFeignClient; + +import com.example.talktalk_be.domain.member.dto.response.KakaoMemberInfoResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient(name = "kakao-service", url = "https://kapi.kakao.com") +public interface KakaoClient { + @Operation(summary = "액세스 토큰으로 사용자 정보 가져오기") + @GetMapping("/v2/user/me") + KakaoMemberInfoResponseDto getInformation(@RequestHeader("Authorization") String accessToken); +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java" new file mode 100644 index 0000000..e220926 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java" @@ -0,0 +1,61 @@ +package com.example.talktalk_be.domain.member.persistence.entity; + +import com.example.talktalk_be.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor +public class Member extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Long loginId; + +// @Column(nullable = false, length = 200, unique = true) + private String email; + +// @Column(nullable = false, length = 200) + private String password; + +// @Column(nullable = false, length = 200) + private String nickname; + +// @Column(nullable = false, length = 20) + private String name; + +// @Column(nullable = false, length = 20) + private String phoneNumber; + +// @Column(length = 20) + private String profileImagePath; // 이미지 처음에는 없음 + +// @Column(nullable = false, length = 2000) +// @Column(length = 2000) + private String token; + + // 소셜로그인 때문에 필요함 + @Column(length = 20) + private String provider; + @Column(length = 100) + private String providerId; + + @Builder + public Member(Long id, Long loginId, String email, String password, String nickname, String name, String phoneNumber, String profileImagePath, String token, String provider, String providerId) { + this.id = id; + this.loginId = loginId; + this.email = email; + this.password = password; + this.nickname = nickname; + this.name = name; + this.phoneNumber = phoneNumber; + this.profileImagePath = profileImagePath; + this.token = token; + this.provider = provider; + this.providerId = providerId; + } +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/KakaoAuthService.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/KakaoAuthService.java" new file mode 100644 index 0000000..1a51779 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/KakaoAuthService.java" @@ -0,0 +1,68 @@ +package com.example.talktalk_be.domain.member.service; + +import com.example.talktalk_be.domain.member.dto.response.KakaoMemberInfoResponseDto; +import com.example.talktalk_be.domain.member.dto.response.KakaoRedirectUrlResponseDto; +import com.example.talktalk_be.domain.member.dto.response.KakaoTokenResponseDto; +import com.example.talktalk_be.domain.member.openFeignClient.KakaoAuthClient; +import com.example.talktalk_be.domain.member.openFeignClient.KakaoClient; +import com.example.talktalk_be.domain.member.persistence.entity.Member; +import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.cloud.openfeign.FeignAutoConfiguration; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +@ImportAutoConfiguration({FeignAutoConfiguration.class}) +public class KakaoAuthService { + + @Value("${oauth.kakao.rest_api}") + private String clientId; + @Value("${oauth.kakao.redirect_uri}") + private String redirectUri; + @Value("${oauth.kakao.client_secret}") + private String clientSecret; + private String grantType = "authorization_code"; + + private final KakaoAuthClient kakaoAuthClient; + private final KakaoClient kakaoClient; + private final MemberRepository memberRepository; + + public KakaoRedirectUrlResponseDto getRedirectUrl() { + StringBuilder sb = new StringBuilder(); + sb.append("https://kauth.kakao.com/oauth/authorize?") + .append("client_id=" + clientId) + .append("&redirect_rul=" + redirectUri) + .append("&response_type=code"); + + return KakaoRedirectUrlResponseDto.builder() + .url(sb.toString()).build(); + } + + public KakaoTokenResponseDto getToken(String code) { + return kakaoAuthClient.getKakaoToken(grantType, code, clientId, redirectUri, clientSecret); + } + + public KakaoMemberInfoResponseDto loginByToken(String accessToken) { + + KakaoMemberInfoResponseDto requestInfo = kakaoClient.getInformation("BEARER " + accessToken); + + // kakaoMemberId로 회원이 있다면 로그인. 없다면 회원가입. + Optional member = memberRepository.findByLoginId(requestInfo.getId()); + if(member.isEmpty()) { + Member saveMember = Member.builder() + .loginId(requestInfo.getId()) + .nickname(requestInfo.getKakaoAccount().getProfile().getNickname()).build(); + memberRepository.save(saveMember); + } +// return kakaoClient.getInformation("BEARER " + accessToken); + return requestInfo; + } + +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java" new file mode 100644 index 0000000..46eaea2 --- /dev/null +++ "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java" @@ -0,0 +1,22 @@ +package com.example.talktalk_be.global.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class BaseEntity { + @CreatedDate + @Column(nullable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(nullable = false) + private LocalDateTime updatedAt; +} From c71109e4f5f7062ddcf458848ef351ddb8fb0cb8 Mon Sep 17 00:00:00 2001 From: KodaHye Date: Wed, 8 Jan 2025 00:47:45 +0900 Subject: [PATCH 07/12] =?UTF-8?q?env:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B8=B0=EB=B3=B8=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TalkTalk_BE/build.gradle | 11 +- .../talktalk_be/TalkTalkBeApplication.java | 0 .../talktalk_be/config/SwaggerConfig.java | 0 .../respository/MemberRepository.java | 17 ++ .../domain/member/service/MemberService.java | 39 +++ .../talktalk_be/global/entity/BaseEntity.java | 0 .../src/main/resources/application.yaml | 19 +- .../TalkTalk_BE/gradlew" | 252 ------------------ .../TalkTalk_BE/gradlew.bat" | 94 ------- .../TalkTalk_BE/settings.gradle" | 1 - .../talktalk_be/config/WebConfig.java" | 16 -- 11 files changed, 75 insertions(+), 374 deletions(-) rename "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" => KodaHye_TalkTalk/TalkTalk_BE/build.gradle (83%) rename "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" => KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java (100%) rename "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java" => KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java (100%) create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/respository/MemberRepository.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/MemberService.java rename "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java" => KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java (100%) rename "\352\263\240\353\213\244\355\230\234_TalkTalk/talktalk_be/src/main/resources/application.yaml" => KodaHye_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml (52%) delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew" delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew.bat" delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/settings.gradle" delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/WebConfig.java" diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" b/KodaHye_TalkTalk/TalkTalk_BE/build.gradle similarity index 83% rename from "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" rename to KodaHye_TalkTalk/TalkTalk_BE/build.gradle index 94ce158..0341ee1 100644 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/build.gradle" +++ b/KodaHye_TalkTalk/TalkTalk_BE/build.gradle @@ -20,6 +20,8 @@ repositories { dependencies { // implementation 'org.springframework.boot:spring-boot-starter-log4j2' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'com.auth0:java-jwt:4.4.0' implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' @@ -28,6 +30,8 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' compileOnly 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' } @@ -41,10 +45,3 @@ dependencyManagement { } } -//configurations { -// all { -// exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' -// } -//} - - diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java similarity index 100% rename from "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java" rename to KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/TalkTalkBeApplication.java diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java" b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java similarity index 100% rename from "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java" rename to KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SwaggerConfig.java diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/respository/MemberRepository.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/respository/MemberRepository.java new file mode 100644 index 0000000..9cc6ed8 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/respository/MemberRepository.java @@ -0,0 +1,17 @@ +package com.example.talktalk_be.domain.member.persistence.respository; + +import com.example.talktalk_be.domain.member.persistence.entity.Member; +import com.example.talktalk_be.domain.member.persistence.entity.enums.SocialType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import javax.swing.text.html.Option; +import java.util.Optional; + +@Repository +public interface MemberRepository extends JpaRepository { + Optional findBySocialTypeAndSocialId(SocialType socialType, Long socialId); + Optional findByEmail(String email); + Optional findByNickname(String nickname); + Optional findByRefreshToken(String refreshToken); +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/MemberService.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/MemberService.java new file mode 100644 index 0000000..c21c590 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/MemberService.java @@ -0,0 +1,39 @@ +package com.example.talktalk_be.domain.member.service; +import com.example.talktalk_be.domain.member.dto.request.SignUpRequestDto; +import com.example.talktalk_be.domain.member.persistence.entity.Member; +import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + + public void signUp(SignUpRequestDto request) throws Exception { + if(memberRepository.findByEmail(request.getEmail()).isPresent()) { + throw new Exception("이미 존재하는 이메일입니다."); + } + + if(memberRepository.findByNickname(request.getNickname()).isPresent()) { + throw new Exception("이미 존재하는 닉네임입니다."); + } + + Member member = Member.builder() + .email(request.getEmail()) + .nickname(request.getNickname()) + .name(request.getName()) + .password(request.getPassword()) + .phoneNumber(request.getPhoneNumber()) + .build(); + + member.encodePassword(passwordEncoder); + + memberRepository.save(member); + } +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java" b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java similarity index 100% rename from "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java" rename to KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/entity/BaseEntity.java diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/talktalk_be/src/main/resources/application.yaml" b/KodaHye_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml similarity index 52% rename from "\352\263\240\353\213\244\355\230\234_TalkTalk/talktalk_be/src/main/resources/application.yaml" rename to KodaHye_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml index 010655e..2f5b7dc 100644 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/talktalk_be/src/main/resources/application.yaml" +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/resources/application.yaml @@ -21,8 +21,19 @@ spring: config: import: optional:file:.env[.properties] +jwt: + secretKey: ${JWT_SECRET_KEY} # 서버가 가지고 있는 개인 키 (secretKey를 통해 JWT 생성 시 암호화) + access: + expiration: 3600000 # 1시간 + header: Authorization + refresh: + expiration: 1209600000 # 2wn + header: Authorization-refresh + oauth: - kakao: - rest_api: ${KAKAO_REST_API_KEY} - redirect_uri: ${KAKAO_REDIRECT_URI} - client_secret: ${KAKAO_CLIENT_SECRET} + client: + registration: + kakao: + rest_api: ${KAKAO_REST_API_KEY} + redirect_uri: ${KAKAO_REDIRECT_URI} + client_secret: ${KAKAO_CLIENT_SECRET} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew" deleted file mode 100644 index f5feea6..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew" +++ /dev/null @@ -1,252 +0,0 @@ -#!/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. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# 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/platforms/jvm/plugins-application/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 -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || 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/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew.bat" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew.bat" deleted file mode 100644 index 9d21a21..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/gradlew.bat" +++ /dev/null @@ -1,94 +0,0 @@ -@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 -@rem SPDX-License-Identifier: Apache-2.0 -@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. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -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/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/settings.gradle" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/settings.gradle" deleted file mode 100644 index b9d126c..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/settings.gradle" +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'TalkTalk_BE' diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/WebConfig.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/WebConfig.java" deleted file mode 100644 index 361c9f5..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/WebConfig.java" +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.talktalk_be.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration // Spring 서버 전역적으로 CORS 설정 -public class WebConfig implements WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOriginPatterns("*") - .allowedMethods("*") - .allowCredentials(true); - } -} From 24b7731213e017341bdc0893fb184f95cdca221d Mon Sep 17 00:00:00 2001 From: KodaHye Date: Wed, 8 Jan 2025 00:53:50 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 28 ++++++++ .../member/dto/request/SignUpRequestDto.java | 12 ++++ .../member/persistence/entity/Member.java | 72 +++++++++++++++++++ .../member/persistence/entity/enums/Role.java | 8 +++ .../persistence/entity/enums/SocialType.java | 8 +++ .../member/persistence/entity/Member.java" | 61 ---------------- 6 files changed, 128 insertions(+), 61 deletions(-) create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/MemberController.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/request/SignUpRequestDto.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/enums/Role.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/enums/SocialType.java delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java" diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/MemberController.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/MemberController.java new file mode 100644 index 0000000..d74a4e6 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/MemberController.java @@ -0,0 +1,28 @@ +package com.example.talktalk_be.domain.member.controller; + +import com.example.talktalk_be.domain.member.dto.request.SignUpRequestDto; +import com.example.talktalk_be.domain.member.service.MemberService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/member") +public class MemberController { + + private final MemberService memberService; + + @PostMapping("/sign-up") + public ResponseEntity signUp(@RequestBody SignUpRequestDto signUpRequestDto) throws Exception { + + log.info(">> sing-up 컨트롤러 진입"); + memberService.signUp(signUpRequestDto); + return ResponseEntity.ok().build(); + } +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/request/SignUpRequestDto.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/request/SignUpRequestDto.java new file mode 100644 index 0000000..0ed5fc1 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/request/SignUpRequestDto.java @@ -0,0 +1,12 @@ +package com.example.talktalk_be.domain.member.dto.request; + +import lombok.Getter; + +@Getter +public class SignUpRequestDto { + private String email; + private String password; + private String nickname; + private String phoneNumber; + private String name; +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java new file mode 100644 index 0000000..e163dbc --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java @@ -0,0 +1,72 @@ +package com.example.talktalk_be.domain.member.persistence.entity; + +import com.example.talktalk_be.domain.member.persistence.entity.enums.Role; +import com.example.talktalk_be.domain.member.persistence.entity.enums.SocialType; +import com.example.talktalk_be.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Getter +@Entity +@NoArgsConstructor +public class Member extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 200, unique = true) + private String email; + + @Column(nullable = false, length = 200) + private String password; + + @Column(nullable = false, length = 200) + private String nickname; + + @Column(nullable = false, length = 20) + private String name; + + @Column(nullable = false, length = 20) + private String phoneNumber; + + @Column(length = 20) + private String profileImagePath; // 이미지 처음에는 없음 + + @Enumerated(EnumType.STRING) + private Role role; + + @Enumerated(EnumType.STRING) + private SocialType socialType; // Kakao, Naver, Google + private Long socialId; // 로그인한 소셜 타입의 식별자 값 (일반 로그인: null) + private String refreshToken; + + @Builder + public Member(Long id, String email, String password, String nickname, String name, String phoneNumber, + String profileImagePath, Role role, SocialType socialType, Long socialId) { + this.id = id; + this.email = email; + this.password = password; + this.nickname = nickname; + this.name = name; + this.phoneNumber = phoneNumber; + this.profileImagePath = profileImagePath; + this.role = role; + this.socialType = socialType; + this.socialId = socialId; + } + + public void encodePassword(PasswordEncoder passwordEncoder) { + this.password = passwordEncoder.encode(password); + } + + public void updateRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public void destroyRefreshToken() { + this.refreshToken = null; + } +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/enums/Role.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/enums/Role.java new file mode 100644 index 0000000..95b8641 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/enums/Role.java @@ -0,0 +1,8 @@ +package com.example.talktalk_be.domain.member.persistence.entity.enums; + +import lombok.Getter; + +@Getter +public enum Role { + USER, ADMIN +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/enums/SocialType.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/enums/SocialType.java new file mode 100644 index 0000000..539e4a6 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/enums/SocialType.java @@ -0,0 +1,8 @@ +package com.example.talktalk_be.domain.member.persistence.entity.enums; + +import lombok.Getter; + +@Getter +public enum SocialType { + KAKAO, GOOGLE +} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java" deleted file mode 100644 index e220926..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/persistence/entity/Member.java" +++ /dev/null @@ -1,61 +0,0 @@ -package com.example.talktalk_be.domain.member.persistence.entity; - -import com.example.talktalk_be.global.entity.BaseEntity; -import jakarta.persistence.*; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@Entity -@NoArgsConstructor -public class Member extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private Long loginId; - -// @Column(nullable = false, length = 200, unique = true) - private String email; - -// @Column(nullable = false, length = 200) - private String password; - -// @Column(nullable = false, length = 200) - private String nickname; - -// @Column(nullable = false, length = 20) - private String name; - -// @Column(nullable = false, length = 20) - private String phoneNumber; - -// @Column(length = 20) - private String profileImagePath; // 이미지 처음에는 없음 - -// @Column(nullable = false, length = 2000) -// @Column(length = 2000) - private String token; - - // 소셜로그인 때문에 필요함 - @Column(length = 20) - private String provider; - @Column(length = 100) - private String providerId; - - @Builder - public Member(Long id, Long loginId, String email, String password, String nickname, String name, String phoneNumber, String profileImagePath, String token, String provider, String providerId) { - this.id = id; - this.loginId = loginId; - this.email = email; - this.password = password; - this.nickname = nickname; - this.name = name; - this.phoneNumber = phoneNumber; - this.profileImagePath = profileImagePath; - this.token = token; - this.provider = provider; - this.providerId = providerId; - } -} From aa5951b632e59991bd2adf35dad986811f0db647 Mon Sep 17 00:00:00 2001 From: KodaHye Date: Wed, 8 Jan 2025 00:54:29 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=EA=B8=B0=EB=B3=B8=20json=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84=20(jwt=20?= =?UTF-8?q?=EB=AF=B8=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talktalk_be/config/SecurityConfig.java | 85 +++++++++++++++++++ .../member/dto/request/LoginRequestDto.java | 14 +++ .../member/dto/response/LoginResponseDto.java | 5 ++ .../domain/member/service/LoginService.java | 29 +++++++ ...nUsernamePasswordAuthenticationFilter.java | 55 ++++++++++++ .../login/handler/LoginFailureHandler.java | 21 +++++ .../LoginSuccessJWTProvideHandler.java | 23 +++++ 7 files changed, 232 insertions(+) create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/request/LoginRequestDto.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/LoginResponseDto.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/LoginService.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/filter/JsonUsernamePasswordAuthenticationFilter.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginFailureHandler.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java new file mode 100644 index 0000000..342d47c --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java @@ -0,0 +1,85 @@ +package com.example.talktalk_be.config; + +import com.example.talktalk_be.domain.member.service.LoginService; +import com.example.talktalk_be.global.login.filter.JsonUsernamePasswordAuthenticationFilter; +import com.example.talktalk_be.global.login.handler.LoginFailureHandler; +import com.example.talktalk_be.global.login.handler.LoginSuccessJWTProvideHandler; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter; + +/* +인가 설정을 진행하는 클래스 + */ + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final LoginService loginService; + private final ObjectMapper objectMapper; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http + .csrf(AbstractHttpConfigurer::disable) // 이거 해줘야 post 403 오류 발생안함 + .authorizeHttpRequests((request) -> request + .requestMatchers("/login").permitAll() + .requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/api/member/sign-up").permitAll() // 모든 사용자가 권한이 없어도 허용함 + .requestMatchers("admin").hasRole("ADMIN") // 어드민 페이지 + .requestMatchers("my/**").hasAnyRole("ADMIN", "USER") + .anyRequest().authenticated() // 그 외 페이지는 로그인이 되었다면 허용 + ); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + @Bean + public LoginSuccessJWTProvideHandler loginSuccessJWTProvideHandler() { + return new LoginSuccessJWTProvideHandler(); + } + + @Bean + public LoginFailureHandler loginFailureHandler() { + return new LoginFailureHandler(); + } + + @Bean + public AuthenticationManager authenticationManager() { + // AuthenticationProvider을 지정하는데, FormLogin과 동일하게 DaoAuthenticationProvider 사용 + // DaoAuthenticationProvider 사용 + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setPasswordEncoder(passwordEncoder()); + provider.setUserDetailsService(loginService); + // AuthenticationManager도 formLogin과 마찬가지로 ProviderManager 사용 + return new ProviderManager(provider); + } + + @Bean + public JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticationFilter() { + JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordLoginFilter = new JsonUsernamePasswordAuthenticationFilter(objectMapper); + jsonUsernamePasswordLoginFilter.setAuthenticationManager(authenticationManager()); + jsonUsernamePasswordLoginFilter.setAuthenticationSuccessHandler(loginSuccessJWTProvideHandler()); + jsonUsernamePasswordLoginFilter.setAuthenticationFailureHandler(loginFailureHandler()); + return jsonUsernamePasswordLoginFilter; + } +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/request/LoginRequestDto.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/request/LoginRequestDto.java new file mode 100644 index 0000000..7b72cb1 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/request/LoginRequestDto.java @@ -0,0 +1,14 @@ +package com.example.talktalk_be.domain.member.dto.request; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class LoginRequestDto { + private String email; + private String password; +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/LoginResponseDto.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/LoginResponseDto.java new file mode 100644 index 0000000..4e7e9bb --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/LoginResponseDto.java @@ -0,0 +1,5 @@ +package com.example.talktalk_be.domain.member.dto.response; + +public class LoginResponseDto { + +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/LoginService.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/LoginService.java new file mode 100644 index 0000000..4a10cfa --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/LoginService.java @@ -0,0 +1,29 @@ +package com.example.talktalk_be.domain.member.service; + +import com.example.talktalk_be.domain.member.persistence.entity.Member; +import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +/* +UserDetailsService를 상속받아 UserDetails를 반환하는 클래스 + */ + +@Service +@RequiredArgsConstructor +public class LoginService implements UserDetailsService { + private final MemberRepository memberRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Member member = memberRepository.findByEmail(username).orElseThrow(() -> new UsernameNotFoundException("해당 이메일을 갖는 유저가 없습니다.")); + + return User.withUsername(member.getEmail()) + .password(member.getPassword()) + .build(); + } +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/filter/JsonUsernamePasswordAuthenticationFilter.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/filter/JsonUsernamePasswordAuthenticationFilter.java new file mode 100644 index 0000000..9261964 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/filter/JsonUsernamePasswordAuthenticationFilter.java @@ -0,0 +1,55 @@ +package com.example.talktalk_be.global.login.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.util.StreamUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + + +public class JsonUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { + + private static final String DEFAULT_LOGIN_REQUEST_URL = "/login"; + private static final String HTTP_METHOD = "POST"; + private static final String CONTENT_TYPE = "application/json"; + private final ObjectMapper objectMapper; + private static final String USERNAME_KEY = "username"; // 회원 로그인 시 이메일 요청 키 JSON key: "email" + private static final String PASSWORD_KEY = "password"; + + private static final AntPathRequestMatcher DEFAULT_LOGIN_PATH_REQUEST_MATCHER = + new AntPathRequestMatcher(DEFAULT_LOGIN_REQUEST_URL, HTTP_METHOD); + + public JsonUsernamePasswordAuthenticationFilter(ObjectMapper objectMapper) { + super(DEFAULT_LOGIN_PATH_REQUEST_MATCHER); // /oauth2/login/*의 요청에, GET으로 온 요청을 처리하기 위해 설정 + this.objectMapper = objectMapper; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { + + if(request.getContentType() == null || !request.getContentType().equals(CONTENT_TYPE)) { + throw new AuthenticationServiceException("Authentication Content-Type not supported: " + request.getContentType()); + } + + String messageBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); + + Map usernamePasswordMap = objectMapper.readValue(messageBody, Map.class); + + String email = usernamePasswordMap.get(USERNAME_KEY); + String password = usernamePasswordMap.get(PASSWORD_KEY); + + // principal과 credentials 전달 + UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(email, password); + return this.getAuthenticationManager().authenticate(authRequest); + } +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginFailureHandler.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginFailureHandler.java new file mode 100644 index 0000000..ce0f01b --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginFailureHandler.java @@ -0,0 +1,21 @@ +package com.example.talktalk_be.global.login.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; + +import java.io.IOException; + +@Slf4j +public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write("fail"); + log.info(">> 로그인 실패"); + } +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java new file mode 100644 index 0000000..2bce3dd --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java @@ -0,0 +1,23 @@ +package com.example.talktalk_be.global.login.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; + +import java.io.IOException; + +@Slf4j +public class LoginSuccessJWTProvideHandler extends SimpleUrlAuthenticationSuccessHandler { + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + + log.info(">> 로그인에 성공. JWT 발급. 로그인한 회원의 emails: {}", userDetails.getUsername()); + + response.getWriter().write("success"); + } +} From a6c5fafed875baf1e724d93e62825d05ce7b0fe9 Mon Sep 17 00:00:00 2001 From: KodaHye <72763127+KodaHye@users.noreply.github.com> Date: Wed, 8 Jan 2025 00:56:39 +0900 Subject: [PATCH 10/12] =?UTF-8?q?delete:=20=EA=B3=A0=EB=8B=A4=ED=98=9C=5FT?= =?UTF-8?q?alkTalk/TalkTalk=5FBE/src=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/AuthController.java" | 37 ---------- .../response/KakaoMemberInfoResponseDto.java" | 40 ----------- .../dto/response/KakaoTokenResponseDto.java" | 34 ---------- .../openFeignClient/KakaoAuthClient.java" | 19 ------ .../member/openFeignClient/KakaoClient.java" | 14 ---- .../member/service/KakaoAuthService.java" | 68 ------------------- .../TalkTalkBeApplicationTests.java" | 8 --- 7 files changed, 220 deletions(-) delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoMemberInfoResponseDto.java" delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoTokenResponseDto.java" delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoAuthClient.java" delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoClient.java" delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/KakaoAuthService.java" delete mode 100644 "\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/TalkTalkBeApplicationTests.java" diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" deleted file mode 100644 index 92dea57..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/controller/AuthController.java" +++ /dev/null @@ -1,37 +0,0 @@ -package com.example.talktalk_be.domain.member.controller; - -import com.example.talktalk_be.domain.member.dto.response.KakaoMemberInfoResponseDto; -import com.example.talktalk_be.domain.member.dto.response.KakaoRedirectUrlResponseDto; -import com.example.talktalk_be.domain.member.dto.response.KakaoTokenResponseDto; -import com.example.talktalk_be.domain.member.service.KakaoAuthService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/auth/kakao") -public class AuthController { - - private final KakaoAuthService kakaoAuthService; - - @GetMapping("/redirect") - public ResponseEntity getAuthCode() { - return ResponseEntity.ok(kakaoAuthService.getRedirectUrl()); - } - - @GetMapping("/access_token") - public ResponseEntity getAuthorizationCode(@RequestParam String code) { - return ResponseEntity.ok(kakaoAuthService.getToken(code)); - } - - // 토큰 정보 보기로 액세스 토큰의 유효성을 검증한 이후, 사용자 정보 가져오기를 요청해 - // 필요한 사용자 정보를 받아 서비스 회원 가입 및 로그인 진행 - @GetMapping("/login") - public ResponseEntity loginByToken(@RequestParam("access_token") String accessToken) { - return ResponseEntity.ok(kakaoAuthService.loginByToken(accessToken)); - } -} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoMemberInfoResponseDto.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoMemberInfoResponseDto.java" deleted file mode 100644 index 107b364..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoMemberInfoResponseDto.java" +++ /dev/null @@ -1,40 +0,0 @@ -package com.example.talktalk_be.domain.member.dto.response; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.*; - -@Getter -@ToString -@AllArgsConstructor -public class KakaoMemberInfoResponseDto { - @JsonProperty("id") - private Long id; - @JsonProperty("kakao_account") - private KakaoAccount kakaoAccount; - @JsonProperty("properties") - private JsonNode properties; -// private String nickname; -// private String email; - - @Getter - @AllArgsConstructor - public static class KakaoAccount { - @JsonProperty("profile_nickname_needs_agreement") - private Boolean profileNicknameNeedsAgreement; - private Profile profile; - @JsonProperty("is_email_valid") - private Boolean isEmailValid; - @JsonProperty("is_email_verified") - private Boolean isEmailVerified; - private String email; - } - - @Getter - @AllArgsConstructor - public static class Profile{ - private String nickname; - @JsonProperty("profile_image_url") - private String profileImageUrl; - } -} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoTokenResponseDto.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoTokenResponseDto.java" deleted file mode 100644 index 3f376c1..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/dto/response/KakaoTokenResponseDto.java" +++ /dev/null @@ -1,34 +0,0 @@ -package com.example.talktalk_be.domain.member.dto.response; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Builder; -import lombok.Getter; - -@Getter -public class KakaoTokenResponseDto { - @JsonProperty("token_type") - private String tokenType; - @JsonProperty("access_token") - private String accessToken; - @JsonProperty("id_token") - private String idToken; - @JsonProperty("expires_in") - private Integer expiresIn; - @JsonProperty("refresh_token") - private String refreshToken; - @JsonProperty("refresh_token_expires_in") - private Integer refreshTokenExpiresIn; - @JsonProperty("scope") - private String scope; - - @Builder - public KakaoTokenResponseDto(String tokenType, String accessToken, String idToken, Integer expiresIn, String refreshToken, Integer refreshTokenExpiresIn, String scope) { - this.tokenType = tokenType; - this.accessToken = accessToken; - this.idToken = idToken; - this.expiresIn = expiresIn; - this.refreshToken = refreshToken; - this.refreshTokenExpiresIn = refreshTokenExpiresIn; - this.scope = scope; - } -} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoAuthClient.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoAuthClient.java" deleted file mode 100644 index 2bf5422..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoAuthClient.java" +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.talktalk_be.domain.member.openFeignClient; - -import com.example.talktalk_be.domain.member.dto.response.KakaoTokenResponseDto; -import io.swagger.v3.oas.annotations.Operation; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; - -@FeignClient(name = "kakao-auth-service", url = "https://kauth.kakao.com/oauth") -public interface KakaoAuthClient { - - @Operation(summary = "카카오 로그인을 위한 토큰을 받는 api") - @PostMapping("/token") - KakaoTokenResponseDto getKakaoToken(@RequestParam("grant_type") String grantType, - @RequestParam("code") String code, - @RequestParam("client_id") String clientId, - @RequestParam("redirect_uri") String redirectUri, - @RequestParam("client_secret") String clientSecret); -} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoClient.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoClient.java" deleted file mode 100644 index 00814da..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/openFeignClient/KakaoClient.java" +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.talktalk_be.domain.member.openFeignClient; - -import com.example.talktalk_be.domain.member.dto.response.KakaoMemberInfoResponseDto; -import io.swagger.v3.oas.annotations.Operation; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestHeader; - -@FeignClient(name = "kakao-service", url = "https://kapi.kakao.com") -public interface KakaoClient { - @Operation(summary = "액세스 토큰으로 사용자 정보 가져오기") - @GetMapping("/v2/user/me") - KakaoMemberInfoResponseDto getInformation(@RequestHeader("Authorization") String accessToken); -} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/KakaoAuthService.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/KakaoAuthService.java" deleted file mode 100644 index 1a51779..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/domain/member/service/KakaoAuthService.java" +++ /dev/null @@ -1,68 +0,0 @@ -package com.example.talktalk_be.domain.member.service; - -import com.example.talktalk_be.domain.member.dto.response.KakaoMemberInfoResponseDto; -import com.example.talktalk_be.domain.member.dto.response.KakaoRedirectUrlResponseDto; -import com.example.talktalk_be.domain.member.dto.response.KakaoTokenResponseDto; -import com.example.talktalk_be.domain.member.openFeignClient.KakaoAuthClient; -import com.example.talktalk_be.domain.member.openFeignClient.KakaoClient; -import com.example.talktalk_be.domain.member.persistence.entity.Member; -import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.cloud.openfeign.FeignAutoConfiguration; -import org.springframework.stereotype.Service; - -import java.util.Optional; - -@Slf4j -@Service -@RequiredArgsConstructor -@ImportAutoConfiguration({FeignAutoConfiguration.class}) -public class KakaoAuthService { - - @Value("${oauth.kakao.rest_api}") - private String clientId; - @Value("${oauth.kakao.redirect_uri}") - private String redirectUri; - @Value("${oauth.kakao.client_secret}") - private String clientSecret; - private String grantType = "authorization_code"; - - private final KakaoAuthClient kakaoAuthClient; - private final KakaoClient kakaoClient; - private final MemberRepository memberRepository; - - public KakaoRedirectUrlResponseDto getRedirectUrl() { - StringBuilder sb = new StringBuilder(); - sb.append("https://kauth.kakao.com/oauth/authorize?") - .append("client_id=" + clientId) - .append("&redirect_rul=" + redirectUri) - .append("&response_type=code"); - - return KakaoRedirectUrlResponseDto.builder() - .url(sb.toString()).build(); - } - - public KakaoTokenResponseDto getToken(String code) { - return kakaoAuthClient.getKakaoToken(grantType, code, clientId, redirectUri, clientSecret); - } - - public KakaoMemberInfoResponseDto loginByToken(String accessToken) { - - KakaoMemberInfoResponseDto requestInfo = kakaoClient.getInformation("BEARER " + accessToken); - - // kakaoMemberId로 회원이 있다면 로그인. 없다면 회원가입. - Optional member = memberRepository.findByLoginId(requestInfo.getId()); - if(member.isEmpty()) { - Member saveMember = Member.builder() - .loginId(requestInfo.getId()) - .nickname(requestInfo.getKakaoAccount().getProfile().getNickname()).build(); - memberRepository.save(saveMember); - } -// return kakaoClient.getInformation("BEARER " + accessToken); - return requestInfo; - } - -} diff --git "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/TalkTalkBeApplicationTests.java" "b/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/TalkTalkBeApplicationTests.java" deleted file mode 100644 index 60884c7..0000000 --- "a/\352\263\240\353\213\244\355\230\234_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/TalkTalkBeApplicationTests.java" +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.talktalk_be; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class TalkTalkBeApplicationTests { -} From 839a69eb6ff883e73fc1d3a1563682f33234c3ae Mon Sep 17 00:00:00 2001 From: KodaHye Date: Thu, 9 Jan 2025 03:33:52 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EB=B0=9C=EA=B8=89=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talktalk_be/config/SecurityConfig.java | 6 +- .../talktalk_be/global/jwt/JwtService.java | 26 ++ .../global/jwt/JwtServiceImpl.java | 153 ++++++++++ .../LoginSuccessJWTProvideHandler.java | 28 +- .../global/jwt/JwtServiceImplTest.java | 289 ++++++++++++++++++ 5 files changed, 499 insertions(+), 3 deletions(-) create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtService.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtServiceImpl.java create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/global/jwt/JwtServiceImplTest.java diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java index 342d47c..0b68a49 100644 --- a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java @@ -1,6 +1,8 @@ package com.example.talktalk_be.config; +import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; import com.example.talktalk_be.domain.member.service.LoginService; +import com.example.talktalk_be.global.jwt.JwtService; import com.example.talktalk_be.global.login.filter.JsonUsernamePasswordAuthenticationFilter; import com.example.talktalk_be.global.login.handler.LoginFailureHandler; import com.example.talktalk_be.global.login.handler.LoginSuccessJWTProvideHandler; @@ -28,6 +30,8 @@ @RequiredArgsConstructor public class SecurityConfig { + private final JwtService jwtService; + private final MemberRepository memberRepository; private final LoginService loginService; private final ObjectMapper objectMapper; @@ -55,7 +59,7 @@ public PasswordEncoder passwordEncoder() { @Bean public LoginSuccessJWTProvideHandler loginSuccessJWTProvideHandler() { - return new LoginSuccessJWTProvideHandler(); + return new LoginSuccessJWTProvideHandler(jwtService, memberRepository); } @Bean diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtService.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtService.java new file mode 100644 index 0000000..f765474 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtService.java @@ -0,0 +1,26 @@ +package com.example.talktalk_be.global.jwt; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.util.Optional; + +public interface JwtService { + String createAccessToken(String username); + String createRefreshToken(); + void updateRefreshToken(String username, String refreshToken); + void destroyRefreshToken(String username); + +// void sendToken(HttpServletResponse response, String accessToken, String refreshToken) throws Exception; + void sendAccessTokenAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken); + void sendAccessToken(HttpServletResponse response, String accessToken); + + Optional extractAccessToken(HttpServletRequest request) throws IOException, ServletException; + Optional extractRefreshToken(HttpServletRequest request) throws IOException, ServletException; + Optional extractUsername(String accessToken); + void setAccessTokenHeader(HttpServletResponse response, String accessToken); + void setRefreshTokenHeader(HttpServletResponse response, String refreshTokens); + boolean isTokenValid(String token); +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtServiceImpl.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtServiceImpl.java new file mode 100644 index 0000000..11bf848 --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtServiceImpl.java @@ -0,0 +1,153 @@ +package com.example.talktalk_be.global.jwt; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.example.talktalk_be.domain.member.persistence.entity.Member; +import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class JwtServiceImpl implements JwtService{ + + @Value("${jwt.secretKey}") + private String secret; + @Value("${jwt.access.expiration}") + private long accessTokenValidityInSeconds; + @Value("${jwt.refresh.expiration}") + private long refreshTokenValidityInSeconds; + @Value("${jwt.access.header}") + private String accessHeader; + @Value("${jwt.refresh.header}") + private String refreshHeader; + + private static final String ACCESS_TOKEN_SUBJECT = "AccessToken"; + private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken"; + private static final String USERNAME_CLAIM = "username"; + private static final String BEARER = "Bearer"; + + private final MemberRepository memberRepository; + + @Override + public String createAccessToken(String username) { + return JWT.create() + .withSubject(ACCESS_TOKEN_SUBJECT) + .withExpiresAt(new Date(System.currentTimeMillis() + accessTokenValidityInSeconds)) + .withClaim(USERNAME_CLAIM, username) + .sign(Algorithm.HMAC512(secret)); + } + + @Override + public String createRefreshToken() { + return JWT.create() + .withSubject(REFRESH_TOKEN_SUBJECT) + .withExpiresAt(new Date(System.currentTimeMillis() + refreshTokenValidityInSeconds)) + .sign(Algorithm.HMAC512(secret)); + } + + @Override + public void updateRefreshToken(String username, String refreshToken) { + memberRepository.findByEmail(username) + .ifPresentOrElse( + member -> member.updateRefreshToken(refreshToken), + () -> new Exception(username + "이메일을 갖는 회원이 없습니다.") + ); + } + + @Override + public void destroyRefreshToken(String username) { + memberRepository.findByEmail(username) + .ifPresentOrElse( + Member::destroyRefreshToken, + () -> new Exception(username + "이메일을 갖는 회원이 없습니다.") + ); + } + + @Override + public void sendAccessTokenAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken) { + response.setStatus(HttpServletResponse.SC_OK); + + setAccessTokenHeader(response, accessToken); + setRefreshTokenHeader(response, refreshToken); + + Map tokenMap = new HashMap<>(); + tokenMap.put(ACCESS_TOKEN_SUBJECT, accessToken); + tokenMap.put(REFRESH_TOKEN_SUBJECT, refreshToken); + } + + @Override + public void sendAccessToken(HttpServletResponse response, String accessToken) { + response.setStatus(HttpServletResponse.SC_OK); + + setAccessTokenHeader(response, accessToken); + + Map tokenMap = new HashMap<>(); + tokenMap.put(ACCESS_TOKEN_SUBJECT, accessToken); + } + + @Override + public Optional extractAccessToken(HttpServletRequest request) throws IOException, ServletException { + return Optional.ofNullable(request.getHeader(accessHeader)).filter( + accessToken -> accessToken.startsWith(BEARER) + ).map(accessToken -> accessToken.replace(BEARER, "")); + } + + @Override + public Optional extractRefreshToken(HttpServletRequest request) throws IOException, ServletException { + return Optional.ofNullable(request.getHeader(refreshHeader)).filter( + refreshToken -> refreshToken.startsWith(BEARER) + ).map(refreshToken -> refreshToken.replace(BEARER, "")); + } + + @Override + public Optional extractUsername(String accessToken) { + try { + return Optional.ofNullable( + JWT.require(Algorithm.HMAC512(secret)) + .build() + .verify(accessToken) + .getClaim(USERNAME_CLAIM) + .asString() + ); + } catch (Exception e) { + log.error(e.getMessage()); + return Optional.empty(); + } + } + + @Override + public void setAccessTokenHeader(HttpServletResponse response, String accessToken) { + response.setHeader(accessHeader, accessToken); + } + + @Override + public void setRefreshTokenHeader(HttpServletResponse response, String refreshToken) { + response.setHeader(refreshHeader, refreshToken); + } + + @Override + public boolean isTokenValid(String token) { + try { + JWT.require(Algorithm.HMAC512(secret)).build().verify(token); + return true; + } catch (Exception e) { + log.error("유효하지 않은 Token 입니다.", e.getMessage()); + return false; + } + } +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java index 2bce3dd..ff62596 100644 --- a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java @@ -1,8 +1,11 @@ package com.example.talktalk_be.global.login.handler; +import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; +import com.example.talktalk_be.global.jwt.JwtService; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; @@ -10,14 +13,35 @@ import java.io.IOException; +@RequiredArgsConstructor @Slf4j public class LoginSuccessJWTProvideHandler extends SimpleUrlAuthenticationSuccessHandler { + + private final JwtService jwtService; + private final MemberRepository memberRepository; + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - UserDetails userDetails = (UserDetails) authentication.getPrincipal(); - log.info(">> 로그인에 성공. JWT 발급. 로그인한 회원의 emails: {}", userDetails.getUsername()); + String username = extractUsername(authentication); + String accessToken = jwtService.createAccessToken(username); + String refreshToken = jwtService.createRefreshToken(); + + jwtService.sendAccessTokenAndRefreshToken(response, accessToken, refreshToken); + + memberRepository.findByEmail(username).ifPresent( + member -> jwtService.updateRefreshToken(username, refreshToken) + ); + + log.info(">> 로그인 성공: {}", username); + log.info(">> AccessToken 발급: {}", accessToken); + log.info(">> RefreshToken 발급: {}", refreshToken); response.getWriter().write("success"); } + + private String extractUsername(Authentication authentication) { + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + return userDetails.getUsername(); + } } diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/global/jwt/JwtServiceImplTest.java b/KodaHye_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/global/jwt/JwtServiceImplTest.java new file mode 100644 index 0000000..78b33ac --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/test/java/com/example/talktalk_be/global/jwt/JwtServiceImplTest.java @@ -0,0 +1,289 @@ +package com.example.talktalk_be.global.jwt; + + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.example.talktalk_be.domain.member.persistence.entity.Member; +import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityManager; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.mock.MockType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@Transactional +class JwtServiceImplTest { + + @Autowired JwtServiceImpl jwtService; + @Autowired MemberRepository memberRepository; + @Autowired EntityManager em; + + @Value("${jwt.secretKey}") + private String secret; + @Value("${jwt.access.expiration}") + private long accessTokenValidityInSeconds; + @Value("${jwt.refresh.expiration}") + private long refreshTokenValidityInSeconds; + @Value("${jwt.access.header}") + private String accessHeader; + @Value("${jwt.refresh.header}") + private String refreshHeader; + + private static final String ACCESS_TOKEN_SUBJECT = "AccessToken"; + private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken"; + private static final String USERNAME_CLAIM = "username"; + private static final String BEARER = "Bearer"; + + String username = "dahyezzang"; + + @BeforeEach + public void init() { + Member member = Member.builder() + .email(username) + .password("dahyezzang") + .nickname("dahyezzang") + .name("dahyezzang") + .phoneNumber("dahyezzang").build(); + + memberRepository.save(member); + clear(); + } + + private void clear() { + em.flush(); + em.clear(); + } + + private DecodedJWT getVerity(String token) { + return JWT.require(Algorithm.HMAC512(secret)).build().verify(token); + } + + @Test + public void createAccessToken_AccessToken_발급() throws Exception { + //given + String accessToken = jwtService.createAccessToken(username); + + DecodedJWT verify = JWT.require(Algorithm.HMAC512(secret)).build().verify(accessToken); + String subject = verify.getSubject(); + String findUsername = verify.getClaim(USERNAME_CLAIM).asString(); + + assertThat(findUsername).isEqualTo(username); + assertThat(subject).isEqualTo(ACCESS_TOKEN_SUBJECT); + } + + @Test + public void createRefreshToken_RefreshToken_발급() throws Exception { + //given + String refreshToken = jwtService.createRefreshToken(); + DecodedJWT verify = JWT.require(Algorithm.HMAC512(secret)).build().verify(refreshToken); + String subject = verify.getSubject(); + String username = verify.getClaim(USERNAME_CLAIM).asString(); + assertThat(subject).isEqualTo(REFRESH_TOKEN_SUBJECT); + assertThat(username).isNull(); + } + + @Test + public void updateRefreshToken_refreshToken_업데이트() throws Exception { + //given + String refreshToken = jwtService.createRefreshToken(); + jwtService.updateRefreshToken(username, refreshToken); + clear(); + Thread.sleep(3000); + + //when + String reIssuedRefreshToken = jwtService.createRefreshToken(); + jwtService.updateRefreshToken(username, reIssuedRefreshToken); + clear(); + + //then + assertThrows(Exception.class, () -> memberRepository.findByRefreshToken(refreshToken).get());// + assertThat(memberRepository.findByRefreshToken(reIssuedRefreshToken).get().getEmail()).isEqualTo(username); + } + + + + @Test + public void destroyRefreshToken_refreshToken_제거() throws Exception { + //given + String refreshToken = jwtService.createRefreshToken(); + jwtService.updateRefreshToken(username, refreshToken); + clear(); + + //when + jwtService.destroyRefreshToken(username); + clear(); + + //then + assertThrows(Exception.class, () -> memberRepository.findByRefreshToken(refreshToken).get()); + + Member member = memberRepository.findByEmail(username).get(); + assertThat(member.getRefreshToken()).isNull(); + } + @Test + public void 토큰_유효성_검사() throws Exception { + //given + String accessToken = jwtService.createAccessToken(username); + String refreshToken = jwtService.createRefreshToken(); + + //when, then + assertThat(jwtService.isTokenValid(accessToken)).isTrue(); + assertThat(jwtService.isTokenValid(refreshToken)).isTrue(); + assertThat(jwtService.isTokenValid(accessToken+"d")).isFalse(); + assertThat(jwtService.isTokenValid(accessToken+"d")).isFalse(); + + } + + + @Test + public void setAccessTokenHeader_AccessToken_헤더_설정() throws Exception { + MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); + + String accessToken = jwtService.createAccessToken(username); + String refreshToken = jwtService.createRefreshToken(); + + jwtService.setAccessTokenHeader(mockHttpServletResponse, accessToken); + + + //when + jwtService.sendAccessTokenAndRefreshToken(mockHttpServletResponse,accessToken,refreshToken); + + //then + String headerAccessToken = mockHttpServletResponse.getHeader(accessHeader); + + assertThat(headerAccessToken).isEqualTo(accessToken); + } + + @Test + public void setRefreshTokenHeader_RefreshToken_헤더_설정() throws Exception { + MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); + + String accessToken = jwtService.createAccessToken(username); + String refreshToken = jwtService.createRefreshToken(); + + jwtService.setRefreshTokenHeader(mockHttpServletResponse, refreshToken); + + + //when + jwtService.sendAccessTokenAndRefreshToken(mockHttpServletResponse,accessToken,refreshToken); + + //then + String headerRefreshToken = mockHttpServletResponse.getHeader(refreshHeader); + + assertThat(headerRefreshToken).isEqualTo(refreshToken); + } + + + + + + @Test + public void sendAccessAndRefreshToken_토큰_전송() throws Exception { + //given + MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); + + String accessToken = jwtService.createAccessToken(username); + String refreshToken = jwtService.createRefreshToken(); + + + //when + jwtService.sendAccessTokenAndRefreshToken(mockHttpServletResponse,accessToken,refreshToken); + + + //then + String headerAccessToken = mockHttpServletResponse.getHeader(accessHeader); + String headerRefreshToken = mockHttpServletResponse.getHeader(refreshHeader); + + + + assertThat(headerAccessToken).isEqualTo(accessToken); + assertThat(headerRefreshToken).isEqualTo(refreshToken); + + } + + + @Test + public void extractAccessToken_AccessToken_추출() throws Exception { + //given + String accessToken = jwtService.createAccessToken(username); + String refreshToken = jwtService.createRefreshToken(); + HttpServletRequest httpServletRequest = setRequest(accessToken, refreshToken); + + //when + String extractAccessToken = jwtService.extractAccessToken(httpServletRequest).orElseThrow(()-> new Exception("토큰이 없습니다")); + + + //then + assertThat(extractAccessToken).isEqualTo(accessToken); + assertThat(JWT.require(Algorithm.HMAC512(secret)).build().verify(extractAccessToken).getClaim(USERNAME_CLAIM).asString()).isEqualTo(username); + } + + + @Test + public void extractRefreshToken_RefreshToken_추출() throws Exception { + //given + String accessToken = jwtService.createAccessToken(username); + String refreshToken = jwtService.createRefreshToken(); + HttpServletRequest httpServletRequest = setRequest(accessToken, refreshToken); + + + //when + String extractRefreshToken = jwtService.extractRefreshToken(httpServletRequest).orElseThrow(()-> new Exception("토큰이 없습니다")); + + + //then + assertThat(extractRefreshToken).isEqualTo(refreshToken); + assertThat(JWT.require(Algorithm.HMAC512(secret)).build().verify(extractRefreshToken).getSubject()).isEqualTo(REFRESH_TOKEN_SUBJECT); + } + + + private HttpServletRequest setRequest(String accessToken, String refreshToken) throws IOException { + + MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); + + jwtService.sendAccessTokenAndRefreshToken(mockHttpServletResponse,accessToken,refreshToken); + + String headerAccessToken = mockHttpServletResponse.getHeader(accessHeader); + String headerRefreshToken = mockHttpServletResponse.getHeader(refreshHeader); + + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + + httpServletRequest.addHeader(accessHeader, BEARER+headerAccessToken); + httpServletRequest.addHeader(refreshHeader, BEARER+headerRefreshToken); + + return httpServletRequest; + } + + + @Test + public void extractUsername_Username_추출() throws Exception { + //given + String accessToken = jwtService.createAccessToken(username); + String refreshToken = jwtService.createRefreshToken(); + HttpServletRequest httpServletRequest = setRequest(accessToken, refreshToken); + + String requestAccessToken = jwtService.extractAccessToken(httpServletRequest).orElseThrow(()->new Exception("토큰이 없습니다")); + + //when + String extractUsername = jwtService.extractUsername(requestAccessToken).orElseThrow(()->new Exception("토큰이 없습니다")); + + + //then + assertThat(extractUsername).isEqualTo(username); + } +} From 5b9e2f6829e64c90f8b36be18d3d716265e9ef27 Mon Sep 17 00:00:00 2001 From: KodaHye Date: Sun, 12 Jan 2025 23:47:56 +0900 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20JwtAuthenticationFilter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../talktalk_be/config/SecurityConfig.java | 25 ++++- .../JwtAuthenticationProcessingFilter.java | 103 ++++++++++++++++++ .../global/jwt/{ => service}/JwtService.java | 3 +- .../jwt/{ => service}/JwtServiceImpl.java | 4 +- .../LoginSuccessJWTProvideHandler.java | 2 +- 5 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/filter/JwtAuthenticationProcessingFilter.java rename KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/{ => service}/JwtService.java (87%) rename KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/{ => service}/JwtServiceImpl.java (98%) diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java index 0b68a49..3ed058e 100644 --- a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/config/SecurityConfig.java @@ -2,7 +2,8 @@ import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; import com.example.talktalk_be.domain.member.service.LoginService; -import com.example.talktalk_be.global.jwt.JwtService; +import com.example.talktalk_be.global.jwt.filter.JwtAuthenticationProcessingFilter; +import com.example.talktalk_be.global.jwt.service.JwtService; import com.example.talktalk_be.global.login.filter.JsonUsernamePasswordAuthenticationFilter; import com.example.talktalk_be.global.login.handler.LoginFailureHandler; import com.example.talktalk_be.global.login.handler.LoginSuccessJWTProvideHandler; @@ -16,18 +17,19 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; /* 인가 설정을 진행하는 클래스 */ @Configuration -@EnableWebSecurity @RequiredArgsConstructor +@EnableWebSecurity(debug = true) public class SecurityConfig { private final JwtService jwtService; @@ -39,16 +41,21 @@ public class SecurityConfig { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http + .formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) // 이거 해줘야 post 403 오류 발생안함 + .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests((request) -> request .requestMatchers("/login").permitAll() - .requestMatchers("/api/auth/**").permitAll() +// .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/api/member/sign-up").permitAll() // 모든 사용자가 권한이 없어도 허용함 - .requestMatchers("admin").hasRole("ADMIN") // 어드민 페이지 - .requestMatchers("my/**").hasAnyRole("ADMIN", "USER") +// .requestMatchers("admin").hasRole("ADMIN") // 어드민 페이지 +// .requestMatchers("my/**").hasAnyRole("ADMIN", "USER") .anyRequest().authenticated() // 그 외 페이지는 로그인이 되었다면 허용 ); + http.addFilterAfter(jsonUsernamePasswordAuthenticationFilter(), LogoutFilter.class); + http.addFilterBefore(jwtAuthenticationProcessingFilter(), JsonUsernamePasswordAuthenticationFilter.class); return http.build(); } @@ -86,4 +93,10 @@ public JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticati jsonUsernamePasswordLoginFilter.setAuthenticationFailureHandler(loginFailureHandler()); return jsonUsernamePasswordLoginFilter; } + + @Bean + public JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter() { + JwtAuthenticationProcessingFilter jsonUsernamePasswordLoginFilter = new JwtAuthenticationProcessingFilter(jwtService, memberRepository); + return jsonUsernamePasswordLoginFilter; + } } diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/filter/JwtAuthenticationProcessingFilter.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/filter/JwtAuthenticationProcessingFilter.java new file mode 100644 index 0000000..ad9f5bf --- /dev/null +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/filter/JwtAuthenticationProcessingFilter.java @@ -0,0 +1,103 @@ +package com.example.talktalk_be.global.jwt.filter; + +import com.example.talktalk_be.domain.member.persistence.entity.Member; +import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; +import com.example.talktalk_be.global.jwt.service.JwtService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/* +OncePerRequestFilter +- 모든 서블릿 컨테이너에서 요청 디스패치당 단일 실행을 보장하는 것을 목표로 하는 필터 기본 클래스 + */ + +@RequiredArgsConstructor +public class JwtAuthenticationProcessingFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + private final MemberRepository memberRepository; + + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + private final String NO_CHECK_URL = "/login"; // /login으로 들어오는 요청에 대해서는 작동하지 않음 + + /* + 1. 리프레시 토큰이 오는 경우 + - 유효하다면 AccessToken 재발급 후, 필터 진행 X + 2. 리프레시 토큰은 없고, AccessToken만 있는 경우 + - 유저 정보 저장 후 필터 계속 진행 + */ + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + if(request.getRequestURI().equals(NO_CHECK_URL)) { + filterChain.doFilter(request, response); + return; + } + + String refreshToken = jwtService + .extractRefreshToken(request) + .filter(jwtService::isTokenValid) + .orElse(null); // RefreshToken이 없거나 유효하지 않다면 null 반환 + + if(refreshToken != null) { + // RefreshToken이 유효하다면 해당 refreshToken을 가진 + // 유저정보를 찾아오고, 존재한다면 AccessToken을 재발급 + // refreshToken만 보낸 경우에는 인증을 처리하지 안헥 하기 위해 바로 return 함 + checkRefreshTokenAndReIssueAccessToken(response, refreshToken); + return; + } + + // refreshToken이 없다면 AccessToken을 검사하는 로직 + // request에서 AccessToken을 추출한 후, 존재한다면 AccessToken을 통해 username 추출 + // username이 추출되었다면 해당 회원을 찾아서 그 정보를 가지고 인증 처리 + checkAccessTokenAndAuthentication(request, response, filterChain); // 4 + } + + private void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + jwtService.extractAccessToken(request).filter(jwtService::isTokenValid).ifPresent( + accessToken -> jwtService.extractUsername(accessToken).ifPresent( + username -> memberRepository.findByEmail(username).ifPresent( + member -> saveAuthentication(member) + ) + ) + ); + + filterChain.doFilter(request, response); + } + + private void saveAuthentication(Member member) { + UserDetails user = User.builder() + .username(member.getEmail()) + .password(member.getPassword()) +// 우선 role은 빼놓자! +// .roles(member.getRole().name()) + .build(); + + Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, authoritiesMapper.mapAuthorities(user.getAuthorities())); + + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authentication); + SecurityContextHolder.setContext(context); + } + + private void checkRefreshTokenAndReIssueAccessToken(HttpServletResponse response, String refreshToken) { + memberRepository.findByRefreshToken(refreshToken).ifPresent( + member -> jwtService.sendAccessToken(response, jwtService.createAccessToken(member.getEmail())) + ); + + } +} diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtService.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/service/JwtService.java similarity index 87% rename from KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtService.java rename to KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/service/JwtService.java index f765474..4211b2d 100644 --- a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtService.java +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/service/JwtService.java @@ -1,4 +1,4 @@ -package com.example.talktalk_be.global.jwt; +package com.example.talktalk_be.global.jwt.service; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -13,7 +13,6 @@ public interface JwtService { void updateRefreshToken(String username, String refreshToken); void destroyRefreshToken(String username); -// void sendToken(HttpServletResponse response, String accessToken, String refreshToken) throws Exception; void sendAccessTokenAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken); void sendAccessToken(HttpServletResponse response, String accessToken); diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtServiceImpl.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/service/JwtServiceImpl.java similarity index 98% rename from KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtServiceImpl.java rename to KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/service/JwtServiceImpl.java index 11bf848..ec728aa 100644 --- a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/JwtServiceImpl.java +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/jwt/service/JwtServiceImpl.java @@ -1,4 +1,4 @@ -package com.example.talktalk_be.global.jwt; +package com.example.talktalk_be.global.jwt.service; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; @@ -39,7 +39,7 @@ public class JwtServiceImpl implements JwtService{ private static final String ACCESS_TOKEN_SUBJECT = "AccessToken"; private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken"; private static final String USERNAME_CLAIM = "username"; - private static final String BEARER = "Bearer"; + private static final String BEARER = "Bearer "; private final MemberRepository memberRepository; diff --git a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java index ff62596..edd9e03 100644 --- a/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java +++ b/KodaHye_TalkTalk/TalkTalk_BE/src/main/java/com/example/talktalk_be/global/login/handler/LoginSuccessJWTProvideHandler.java @@ -1,7 +1,7 @@ package com.example.talktalk_be.global.login.handler; import com.example.talktalk_be.domain.member.persistence.respository.MemberRepository; -import com.example.talktalk_be.global.jwt.JwtService; +import com.example.talktalk_be.global.jwt.service.JwtService; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse;