From 9f582b937f75e1337cc96c58cabf20b472b808c5 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Thu, 18 Jul 2024 16:18:41 +0900 Subject: [PATCH 01/17] =?UTF-8?q?CI:=20yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workflows/dev_deploy_docker_beanstalk.yml | 176 +++++++++--------- 1 file changed, 84 insertions(+), 92 deletions(-) diff --git a/.github/workflows/dev_deploy_docker_beanstalk.yml b/.github/workflows/dev_deploy_docker_beanstalk.yml index 900dcb3..4752a7e 100644 --- a/.github/workflows/dev_deploy_docker_beanstalk.yml +++ b/.github/workflows/dev_deploy_docker_beanstalk.yml @@ -1,92 +1,84 @@ -#name: suppin dev Docker Beanstalk CI/CD # Workflow 이름 -# -#on: -# push: -# branches: -# - dev -# pull_request: -# branches: -# - dev -# types: [closed] -# workflow_dispatch: # (2).수동 실행도 가능하도록 -# -#jobs: -# build: -# runs-on: ubuntu-latest # (3).OS환경 -# if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) -# -# -# steps: -# - name: Checkout current repository -# uses: actions/checkout@v2 -# -# - name: Set up JDK 17 -# uses: actions/setup-java@v1 -# with: -# java-version: 17 -# -# - name: Grant execute permission for gradlew -# run: chmod +x ./gradlew -# shell: bash -# -# - name: Build with Gradle -# run: ./gradlew clean build -# shell: bash -# -# - name: Configure AWS credentials -# uses: aws-actions/configure-aws-credentials@v1 -# with: -# aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} -# aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -# aws-region: ap-northeast-2 -# -# - name: Login to Amazon ECR -# id: login-ecr -# uses: aws-actions/amazon-ecr-login@v1 -# -# - name: Build, tag, and push image to Amazon ECR -# id: build-image -# env: -# ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} -# ECR_REPOSITORY: tree-dev -# IMAGE_TAG: latest -# run: | -# # Docker 이미지 빌드 -# docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . -# -# # 빌드한 이미지를 Amazon ECR로 푸시 -# docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -# -# # 빌드된 이미지의 정보 출력 -# echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" -# -# - name: Get current time -# uses: 1466587594/get-current-time@v2 -# id: current-time -# with: -# format: YYYYMMDD_HH-mm-ss -# utcOffset: "+09:00" -# -# - name: Generate deployment package -# run: | -# mkdir -p deploy -# cp -r .ebextensions deploy/.ebextensions -# cp Dockerrun.aws.json deploy/Dockerrun.aws.json -# cp -r .platform deploy/.platform -# cd deploy && zip -r deploy.zip . -# -# - name: Beanstalk Deploy -# uses: einaregilsson/beanstalk-deploy@v14 -# with: -# aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} -# aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} -# application_name: suppin-dev -# environment_name: suppin-dev-env -# version_label: github-action-${{ steps.current-time.outputs.formattedTime }} -# region: ap-northeast-2 -# deployment_package: deploy/deploy.zip -# wait_for_deployment: false -# -# -# -# +name: suppin dev Docker Beanstalk CI/CD # Workflow 이름 + +on: + +jobs: + build: + runs-on: ubuntu-latest # (3).OS환경 + if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) + + + steps: + - name: Checkout current repository + uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + shell: bash + + - name: Build with Gradle + run: ./gradlew clean build + shell: bash + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: tree-dev + IMAGE_TAG: latest + run: | + # Docker 이미지 빌드 + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + + # 빌드한 이미지를 Amazon ECR로 푸시 + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + + # 빌드된 이미지의 정보 출력 + echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" + + - name: Get current time + uses: 1466587594/get-current-time@v2 + id: current-time + with: + format: YYYYMMDD_HH-mm-ss + utcOffset: "+09:00" + + - name: Generate deployment package + run: | + mkdir -p deploy + cp -r .ebextensions deploy/.ebextensions + cp Dockerrun.aws.json deploy/Dockerrun.aws.json + cp -r .platform deploy/.platform + cd deploy && zip -r deploy.zip . + + - name: Beanstalk Deploy + uses: einaregilsson/beanstalk-deploy@v14 + with: + aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + application_name: suppin-dev + environment_name: suppin-dev-env + version_label: github-action-${{ steps.current-time.outputs.formattedTime }} + region: ap-northeast-2 + deployment_package: deploy/deploy.zip + wait_for_deployment: false + + + + From 46caa261f9a2663deee92092faf5fd76e64fef19 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Tue, 23 Jul 2024 23:20:31 +0900 Subject: [PATCH 02/17] =?UTF-8?q?Fix:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/cmc/suppin/answer/domain/AnonymousParticipant.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java b/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java index 589b8ac..e54de55 100644 --- a/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java +++ b/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java @@ -18,8 +18,11 @@ public class AnonymousParticipant extends BaseDateTimeEntity { @JoinColumn(name = "survey_id") private Survey survey; - @Column(columnDefinition = "VARCHAR(255)", nullable = false) - private String sessionId; + @Column(columnDefinition = "VARCHAR(13)", nullable = false) + private String phoneNumber; + + @Column(nullable = false) + private Boolean isAgreed; @OneToMany(mappedBy = "anonymousParticipant") private List answerList = new ArrayList<>(); From 782c5f3d38b7ad0f8dfe939b0d4a4ae2f358433a Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Tue, 23 Jul 2024 23:21:25 +0900 Subject: [PATCH 03/17] =?UTF-8?q?Feat:=20=EC=9C=A0=EC=A0=80=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=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 --- .../suppin/member/domain/repository/MemberRepository.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java diff --git a/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java new file mode 100644 index 0000000..c0c6309 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java @@ -0,0 +1,8 @@ +package com.cmc.suppin.member.domain.repository; + +import com.cmc.suppin.member.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + +} From 8d1c57fe2b88dc3cbf1ffcfe18811b5292cbccd1 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 19:40:20 +0900 Subject: [PATCH 04/17] =?UTF-8?q?Fix:=20java=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../suppin/answer/domain/AnonymousParticipant.java | 2 +- .../java/com/cmc/suppin/answer/domain/Answer.java | 2 +- .../com/cmc/suppin/answer/domain/AnswerOption.java | 2 +- .../com/cmc/suppin/comment/domain/Comment.java | 2 +- .../java/com/cmc/suppin/event/domain/Event.java | 2 +- .../suppin/global/domain/BaseDateTimeEntity.java | 6 +++--- .../suppin/global/exception/ExceptionAdvice.java | 14 +++++++------- .../java/com/cmc/suppin/member/controller/.gitkeep | 0 .../com/cmc/suppin/survey/domain/Question.java | 2 +- .../cmc/suppin/survey/domain/QuestionOption.java | 2 +- .../java/com/cmc/suppin/survey/domain/Survey.java | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) delete mode 100644 src/main/java/com/cmc/suppin/member/controller/.gitkeep diff --git a/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java b/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java index e54de55..23e80d7 100644 --- a/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java +++ b/src/main/java/com/cmc/suppin/answer/domain/AnonymousParticipant.java @@ -2,8 +2,8 @@ import com.cmc.suppin.global.domain.BaseDateTimeEntity; import com.cmc.suppin.survey.domain.Survey; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cmc/suppin/answer/domain/Answer.java b/src/main/java/com/cmc/suppin/answer/domain/Answer.java index 3fcdffa..cefb10d 100644 --- a/src/main/java/com/cmc/suppin/answer/domain/Answer.java +++ b/src/main/java/com/cmc/suppin/answer/domain/Answer.java @@ -2,8 +2,8 @@ import com.cmc.suppin.global.domain.BaseDateTimeEntity; import com.cmc.suppin.survey.domain.Question; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cmc/suppin/answer/domain/AnswerOption.java b/src/main/java/com/cmc/suppin/answer/domain/AnswerOption.java index 2f06669..4240668 100644 --- a/src/main/java/com/cmc/suppin/answer/domain/AnswerOption.java +++ b/src/main/java/com/cmc/suppin/answer/domain/AnswerOption.java @@ -1,8 +1,8 @@ package com.cmc.suppin.answer.domain; import com.cmc.suppin.survey.domain.QuestionOption; +import jakarta.persistence.*; -import javax.persistence.*; @Entity public class AnswerOption { diff --git a/src/main/java/com/cmc/suppin/comment/domain/Comment.java b/src/main/java/com/cmc/suppin/comment/domain/Comment.java index f6f2734..855e686 100644 --- a/src/main/java/com/cmc/suppin/comment/domain/Comment.java +++ b/src/main/java/com/cmc/suppin/comment/domain/Comment.java @@ -2,8 +2,8 @@ import com.cmc.suppin.event.domain.Event; import com.cmc.suppin.global.domain.BaseDateTimeEntity; +import jakarta.persistence.*; -import javax.persistence.*; @Entity public class Comment extends BaseDateTimeEntity { diff --git a/src/main/java/com/cmc/suppin/event/domain/Event.java b/src/main/java/com/cmc/suppin/event/domain/Event.java index 8e150f8..3affeb8 100644 --- a/src/main/java/com/cmc/suppin/event/domain/Event.java +++ b/src/main/java/com/cmc/suppin/event/domain/Event.java @@ -5,8 +5,8 @@ import com.cmc.suppin.global.enums.EventType; import com.cmc.suppin.member.domain.Member; import com.cmc.suppin.survey.domain.Survey; +import jakarta.persistence.*; -import javax.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cmc/suppin/global/domain/BaseDateTimeEntity.java b/src/main/java/com/cmc/suppin/global/domain/BaseDateTimeEntity.java index 196f352..b3eeb7a 100644 --- a/src/main/java/com/cmc/suppin/global/domain/BaseDateTimeEntity.java +++ b/src/main/java/com/cmc/suppin/global/domain/BaseDateTimeEntity.java @@ -1,13 +1,13 @@ package com.cmc.suppin.global.domain; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import javax.persistence.Column; -import javax.persistence.EntityListeners; -import javax.persistence.MappedSuperclass; import java.time.LocalDateTime; @EntityListeners(AuditingEntityListener.class) diff --git a/src/main/java/com/cmc/suppin/global/exception/ExceptionAdvice.java b/src/main/java/com/cmc/suppin/global/exception/ExceptionAdvice.java index 8a76b7b..99a7a54 100644 --- a/src/main/java/com/cmc/suppin/global/exception/ExceptionAdvice.java +++ b/src/main/java/com/cmc/suppin/global/exception/ExceptionAdvice.java @@ -1,24 +1,21 @@ package com.cmc.suppin.global.exception; +import com.cmc.suppin.global.exception.status.ErrorStatus; +import com.cmc.suppin.global.presentation.ApiResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import com.cmc.suppin.global.exception.status.ErrorStatus; -import com.cmc.suppin.global.presentation.ApiResponse; -import javax.servlet.http.HttpServletRequest; -import javax.validation.ConstraintViolationException; -import java.util.LinkedHashMap; import java.util.Map; -import java.util.Optional; @Slf4j @RestControllerAdvice(annotations = {RestController.class}) @@ -34,6 +31,7 @@ public ResponseEntity validation(ConstraintViolationException e, WebRequ return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY, request); } + /* @Override public ResponseEntity handleMethodArgumentNotValid( MethodArgumentNotValidException e, HttpHeaders headers, HttpStatus status, WebRequest request) { @@ -49,6 +47,8 @@ public ResponseEntity handleMethodArgumentNotValid( return handleExceptionInternalArgs(e, HttpHeaders.EMPTY, ErrorStatus.valueOf("_BAD_REQUEST"), request, errors); } + */ + @org.springframework.web.bind.annotation.ExceptionHandler public ResponseEntity exception(Exception e, WebRequest request) { diff --git a/src/main/java/com/cmc/suppin/member/controller/.gitkeep b/src/main/java/com/cmc/suppin/member/controller/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/cmc/suppin/survey/domain/Question.java b/src/main/java/com/cmc/suppin/survey/domain/Question.java index 173e01d..42b34c9 100644 --- a/src/main/java/com/cmc/suppin/survey/domain/Question.java +++ b/src/main/java/com/cmc/suppin/survey/domain/Question.java @@ -2,8 +2,8 @@ import com.cmc.suppin.answer.domain.Answer; import com.cmc.suppin.global.enums.QuestionType; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cmc/suppin/survey/domain/QuestionOption.java b/src/main/java/com/cmc/suppin/survey/domain/QuestionOption.java index 1fc91e6..f940722 100644 --- a/src/main/java/com/cmc/suppin/survey/domain/QuestionOption.java +++ b/src/main/java/com/cmc/suppin/survey/domain/QuestionOption.java @@ -1,8 +1,8 @@ package com.cmc.suppin.survey.domain; import com.cmc.suppin.answer.domain.AnswerOption; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cmc/suppin/survey/domain/Survey.java b/src/main/java/com/cmc/suppin/survey/domain/Survey.java index b5948ea..7fa4fb6 100644 --- a/src/main/java/com/cmc/suppin/survey/domain/Survey.java +++ b/src/main/java/com/cmc/suppin/survey/domain/Survey.java @@ -3,8 +3,8 @@ import com.cmc.suppin.answer.domain.AnonymousParticipant; import com.cmc.suppin.event.domain.Event; import com.cmc.suppin.global.domain.BaseDateTimeEntity; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; From 59576363fe5c36dcf9b00ce691570ef031581e83 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 19:41:55 +0900 Subject: [PATCH 05/17] =?UTF-8?q?Update:=20=EB=B2=84=EC=A0=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20spring=20security=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 57 ++++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/build.gradle b/build.gradle index 03057f5..8e3b645 100644 --- a/build.gradle +++ b/build.gradle @@ -1,49 +1,54 @@ plugins { - id 'java' - id 'org.springframework.boot' version '2.7.7' - id 'io.spring.dependency-management' version '1.1.4' + id 'java' + id 'org.springframework.boot' version '3.2.8' + id 'io.spring.dependency-management' version '1.1.6' } group = 'com.cmc' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '17' - targetCompatibility = '17' - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + sourceCompatibility = '17' + targetCompatibility = '17' + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'javax.servlet:javax.servlet-api:4.0.1' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springdoc:springdoc-openapi-ui:1.6.11' - implementation 'org.springframework.boot:spring-boot-starter-validation' - - compileOnly 'org.projectlombok:lombok' - developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + + // JWT dependencies + implementation 'io.jsonwebtoken:jjwt-api:0.12.2' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.2' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.2' + + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + runtimeOnly 'com.mysql:mysql-connector-j' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } jar { - enabled = false + enabled = false } From 616394620538173f7c6b19c5977785ce97be6824 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 19:43:10 +0900 Subject: [PATCH 06/17] =?UTF-8?q?Feat:=20JWT=20=EB=B0=9C=EA=B8=89=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=ED=81=B4=EB=9E=98=EC=8A=A4,?= =?UTF-8?q?=20=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/security/SecurityConfig.java | 110 ++++++++++++++++++ .../suppin/global/security/jwt/JWTFilter.java | 74 ++++++++++++ .../suppin/global/security/jwt/JWTUtil.java | 48 ++++++++ .../global/security/jwt/LoginFilter.java | 77 ++++++++++++ .../member/controller/dto/MemberDetails.java | 62 ++++++++++ .../com/cmc/suppin/member/domain/Member.java | 8 +- .../domain/repository/MemberRepository.java | 3 + 7 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/cmc/suppin/global/security/SecurityConfig.java create mode 100644 src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java create mode 100644 src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java create mode 100644 src/main/java/com/cmc/suppin/global/security/jwt/LoginFilter.java create mode 100644 src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java diff --git a/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java b/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java new file mode 100644 index 0000000..c8d1c65 --- /dev/null +++ b/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java @@ -0,0 +1,110 @@ +package com.cmc.suppin.global.security; + +import com.cmc.suppin.global.security.jwt.JWTFilter; +import com.cmc.suppin.global.security.jwt.JWTUtil; +import com.cmc.suppin.global.security.jwt.LoginFilter; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; + +import java.util.Collections; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + //AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입 + private final AuthenticationConfiguration authenticationConfiguration; + + //JWTUtil 주입 + private final JWTUtil jwtUtil; + + public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil) { + + this.authenticationConfiguration = authenticationConfiguration; + this.jwtUtil = jwtUtil; + } + + //AuthenticationManager Bean 등록 + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { + + return configuration.getAuthenticationManager(); + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http + .cors((corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() { + + @Override + public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { + + CorsConfiguration configuration = new CorsConfiguration(); + + configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000")); + configuration.setAllowedMethods(Collections.singletonList("*")); + configuration.setAllowCredentials(true); + configuration.setAllowedHeaders(Collections.singletonList("*")); + configuration.setMaxAge(3600L); + + configuration.setExposedHeaders(Collections.singletonList("Authorization")); + + return configuration; + } + }))); + + //csrf disable + http + .csrf((auth) -> auth.disable()); + + //From 로그인 방식 disable + http + .formLogin((auth) -> auth.disable()); + + //http basic 인증 방식 disable + http + .httpBasic((auth) -> auth.disable()); + + //경로별 인가 작업 + http + .authorizeHttpRequests((auth) -> auth + .requestMatchers("/login", "/", "/join").permitAll() + .requestMatchers("/admin").hasRole("ADMIN") + .anyRequest().authenticated()); + + //JWTFilter 추가 + http + .addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class); + + //LoginFilter 추가 LoginFilter()는 인자를 받음 (AuthenticationManager() 메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요 + http + .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class); + + //세션 설정 + http + .sessionManagement((session) -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + + return http.build(); + } +} + diff --git a/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java b/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java new file mode 100644 index 0000000..8374cf1 --- /dev/null +++ b/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java @@ -0,0 +1,74 @@ +package com.cmc.suppin.global.security.jwt; + +import com.cmc.suppin.member.controller.dto.MemberDetails; +import com.cmc.suppin.member.domain.Member; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +public class JWTFilter extends OncePerRequestFilter { + + private final JWTUtil jwtUtil; + + public JWTFilter(JWTUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + //request에서 Authorization 헤더를 찾음 + String authorization = request.getHeader("Authorization"); + + //Authorization 헤더 검증(토큰이 없는 경우 처리해주는 부분) + if (authorization == null || !authorization.startsWith("Bearer ")) { + + System.out.println("token null"); + filterChain.doFilter(request, response); + + //조건이 해당되면 메소드 종료 (필수) + return; + } + + //Bearer 부분 제거 후 순수 토큰만 획득 + String token = authorization.split(" ")[1]; + + //토큰 소멸 시간 검증(토큰 만료시 처리해주는 부분) + if (jwtUtil.isExpired(token)) { + + System.out.println("token expired"); + filterChain.doFilter(request, response); + + //조건이 해당되면 메소드 종료 (필수) + return; + } + + //토큰에서 username과 role 획득 + String username = jwtUtil.getUsername(token); + String role = jwtUtil.getRole(token); + + // Member Entity를 생성하여 값 set + Member member = new Member(); + member.setName(username); + member.setPassword("tempPassword"); + member.setRole(role); + + //MemberDetails에 회원 정보 객체 담기 + MemberDetails customUserDetails = new MemberDetails(member); + + //스프링 시큐리티 인증 토큰 생성 + Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + //세션에 사용자 등록 + SecurityContextHolder.getContext().setAuthentication(authToken); + + filterChain.doFilter(request, response); + } +} + diff --git a/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java b/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java new file mode 100644 index 0000000..e9f31d1 --- /dev/null +++ b/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java @@ -0,0 +1,48 @@ +package com.cmc.suppin.global.security.jwt; + +import io.jsonwebtoken.Jwts; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Component +public class JWTUtil { + + private SecretKey secretKey; + + public JWTUtil(@Value("${JWT_TOKEN_SECRET}") String secret) { + + this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); + } + + public String getUsername(String token) { + + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class); + } + + public String getRole(String token) { + + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class); + } + + public Boolean isExpired(String token) { + + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); + } + + public String createJwt(String username, String role, Long expiredMs) { + + return Jwts.builder() + .claim("username", username) + .claim("role", role) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) + .compact(); + } +} + diff --git a/src/main/java/com/cmc/suppin/global/security/jwt/LoginFilter.java b/src/main/java/com/cmc/suppin/global/security/jwt/LoginFilter.java new file mode 100644 index 0000000..fd9a419 --- /dev/null +++ b/src/main/java/com/cmc/suppin/global/security/jwt/LoginFilter.java @@ -0,0 +1,77 @@ +package com.cmc.suppin.global.security.jwt; + +import com.cmc.suppin.member.controller.dto.MemberDetails; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import java.util.Collection; +import java.util.Iterator; + +public class LoginFilter extends UsernamePasswordAuthenticationFilter { + + private final AuthenticationManager authenticationManager; + + private final JWTUtil jwtUtil; + + public LoginFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) { + + this.authenticationManager = authenticationManager; + this.jwtUtil = jwtUtil; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + + //클라이언트 요청에서 username, password 추출 + String username = obtainUsername(request); + String password = obtainPassword(request); + + //스프링 시큐리티에서 username과 password를 검증하기 위해서는 token에 담아야 함 + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null); + + //token에 담은 검증을 위한 AuthenticationManager로 전달 + return authenticationManager.authenticate(authToken); + } + + //로그인 성공시 실행하는 메소드 (여기서 JWT를 발급됨) + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) { + + MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); + + String username = memberDetails.getUsername(); + + Collection authorities = authentication.getAuthorities(); + if (authorities.isEmpty()) { + throw new IllegalStateException("권한이 없습니다."); + } + + Iterator iterator = authorities.iterator(); + GrantedAuthority auth = iterator.next(); + + String role = auth.getAuthority(); + + long expirationTime = 1000 * 60 * 60 * 24 * 7; // 7일 + + String token = jwtUtil.createJwt(username, role, expirationTime); + + response.addHeader("Authorization", "Bearer " + token); + } + + + //로그인 실패시 실행하는 메소드 + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { + + //로그인 실패시 401 응답코드 반환 + response.setStatus(401); + } +} + diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java new file mode 100644 index 0000000..053536e --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java @@ -0,0 +1,62 @@ +package com.cmc.suppin.member.controller.dto; + +import com.cmc.suppin.member.domain.Member; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; + +public class MemberDetails implements UserDetails { + + private final Member member; + + public MemberDetails(Member member) { + this.member = member; + } + + @Override + public Collection getAuthorities() { + + Collection collection = new ArrayList<>(); + + collection.add(new GrantedAuthority() { + @Override + public String getAuthority() { + return member.getRole(); + } + }); + + return collection; + } + + @Override + public String getPassword() { + return member.getPassword(); + } + + @Override + public String getUsername() { + return member.getName(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/com/cmc/suppin/member/domain/Member.java b/src/main/java/com/cmc/suppin/member/domain/Member.java index fff34e7..cfb630d 100644 --- a/src/main/java/com/cmc/suppin/member/domain/Member.java +++ b/src/main/java/com/cmc/suppin/member/domain/Member.java @@ -2,12 +2,16 @@ import com.cmc.suppin.event.domain.Event; import com.cmc.suppin.global.domain.BaseDateTimeEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; -import javax.persistence.*; import java.util.ArrayList; import java.util.List; @Entity +@Getter +@Setter public class Member extends BaseDateTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -32,5 +36,7 @@ public class Member extends BaseDateTimeEntity { @Column(columnDefinition = "VARCHAR(13)", nullable = false) private String phoneNumber; + private String role; + } diff --git a/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java index c0c6309..f113cd5 100644 --- a/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java +++ b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java @@ -5,4 +5,7 @@ public interface MemberRepository extends JpaRepository { + Boolean existsByName(String name); + + Member findByName(String name); } From 04ebdb02891f639a05aeeb3c09019b76adc08518 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 21:12:59 +0900 Subject: [PATCH 07/17] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85,=20=EC=95=84=EC=9D=B4=EB=94=94=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../suppin/member/controller/MemberApi.java | 44 ++++++++++++++++ .../controller/dto/MemberRequestDTO.java | 42 ++++++++++++++++ .../controller/dto/MemberResponseDTO.java | 31 ++++++++++++ .../com/cmc/suppin/member/converter/.gitkeep | 0 .../member/converter/MemberConverter.java | 36 +++++++++++++ .../com/cmc/suppin/member/domain/Member.java | 15 +++--- .../domain/repository/MemberRepository.java | 4 +- .../com/cmc/suppin/member/service/.gitkeep | 0 .../service/command/MemberCommandService.java | 11 ++++ .../command/MemberCommandServiceImpl.java | 50 +++++++++++++++++++ src/main/resources/application.yml | 2 + 11 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/cmc/suppin/member/controller/MemberApi.java create mode 100644 src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java create mode 100644 src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java delete mode 100644 src/main/java/com/cmc/suppin/member/converter/.gitkeep create mode 100644 src/main/java/com/cmc/suppin/member/converter/MemberConverter.java delete mode 100644 src/main/java/com/cmc/suppin/member/service/.gitkeep create mode 100644 src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java create mode 100644 src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java diff --git a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java new file mode 100644 index 0000000..3262214 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java @@ -0,0 +1,44 @@ +package com.cmc.suppin.member.controller; + +import com.cmc.suppin.global.presentation.ApiResponse; +import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.controller.dto.MemberResponseDTO; +import com.cmc.suppin.member.converter.MemberConverter; +import com.cmc.suppin.member.domain.Member; +import com.cmc.suppin.member.service.command.MemberCommandService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@Slf4j +@RequiredArgsConstructor +@Validated +@Tag(name = "Member", description = "Member 관련 API") +@RequestMapping("/api/members") +public class MemberApi { + + private final MemberCommandService memberCommandService; + + + @PostMapping("/join") + @Operation(summary = "회원가입 API", description = "request 파라미터 : id, password, name, phone, email") + public ApiResponse join(@RequestBody @Valid MemberRequestDTO.JoinDTO request) { + Member member = memberCommandService.join(request); + + return ApiResponse.onSuccess(MemberConverter.toJoinResultDTO(member)); + + } + + @GetMapping("/checkUserId") + @Operation(summary = "아이디 중복 체크 API", description = "request : userId, response: 중복이면 false, 중복 아니면 true") + public ApiResponse checkUserId(@RequestBody MemberRequestDTO.IdConfirmDTO request) { + boolean checkUserId = memberCommandService.confirmUserId(request); + + return ApiResponse.onSuccess(MemberConverter.toIdConfirmResultDTO(checkUserId)); + } +} diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java new file mode 100644 index 0000000..023208f --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java @@ -0,0 +1,42 @@ +package com.cmc.suppin.member.controller.dto; + +import jakarta.persistence.Id; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class MemberRequestDTO { + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class JoinDTO { + @NotBlank(message = "아이디를 입력해주세요") + @Id + private String userId; + + @NotBlank(message = "이름을 입력해주세요") + private String name; + + @NotBlank(message = "비밀번호를 입력해주세요") + @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", message = "비밀번호는 8~16자 영문, 숫자, 특수문자를 사용하세요.") + private String password; + + @NotBlank(message = "이메일을 입력해주세요") + @Email(message = "이메일 형식이 올바르지 않습니다.") + private String email; + + @NotBlank(message = "휴대폰 번호를 입력해주세요") + private String phone; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class IdConfirmDTO { + private String userId; + } +} diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java new file mode 100644 index 0000000..febf818 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java @@ -0,0 +1,31 @@ +package com.cmc.suppin.member.controller.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class MemberResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class JoinResultDTO { + Long memberId; + String userId; + String name; + String email; + LocalDateTime createdAt; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class IdConfirmResultDTO { + Boolean checkUserId; + } +} diff --git a/src/main/java/com/cmc/suppin/member/converter/.gitkeep b/src/main/java/com/cmc/suppin/member/converter/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java new file mode 100644 index 0000000..65cab45 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java @@ -0,0 +1,36 @@ +package com.cmc.suppin.member.converter; + +import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.controller.dto.MemberResponseDTO; +import com.cmc.suppin.member.domain.Member; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public class MemberConverter { + + public Member toEntity(MemberRequestDTO.JoinDTO request, BCryptPasswordEncoder encoder) { + return Member.builder() + .userId(request.getUserId()) + .name(request.getName()) + .password(encoder.encode(request.getPassword())) + .email(request.getEmail()) + .phoneNumber(request.getPhone()) + .build(); + } + + public static MemberResponseDTO.JoinResultDTO toJoinResultDTO(Member member) { + return MemberResponseDTO.JoinResultDTO.builder() + .memberId(member.getId()) + .userId(member.getUserId()) + .name(member.getName()) + .email(member.getEmail()) + .createdAt(member.getCreatedAt()) + .build(); + } + + public static MemberResponseDTO.IdConfirmResultDTO toIdConfirmResultDTO(boolean checkUserId) { + return MemberResponseDTO.IdConfirmResultDTO.builder() + .checkUserId(checkUserId) + .build(); + } + +} diff --git a/src/main/java/com/cmc/suppin/member/domain/Member.java b/src/main/java/com/cmc/suppin/member/domain/Member.java index cfb630d..32adfdb 100644 --- a/src/main/java/com/cmc/suppin/member/domain/Member.java +++ b/src/main/java/com/cmc/suppin/member/domain/Member.java @@ -3,15 +3,18 @@ import com.cmc.suppin.event.domain.Event; import com.cmc.suppin.global.domain.BaseDateTimeEntity; import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; import java.util.ArrayList; import java.util.List; @Entity @Getter -@Setter +@Builder +@DynamicInsert +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor public class Member extends BaseDateTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -21,12 +24,12 @@ public class Member extends BaseDateTimeEntity { @OneToMany(mappedBy = "member") private List eventList = new ArrayList<>(); + @Column(columnDefinition = "VARCHAR(30)", nullable = false) + private String userId; + @Column(columnDefinition = "VARCHAR(20)", nullable = false) private String name; - @Column(columnDefinition = "VARCHAR(30)", nullable = false) - private String nickname; - @Column(columnDefinition = "VARCHAR(30)", nullable = false) private String email; diff --git a/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java index f113cd5..0b3296e 100644 --- a/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java +++ b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java @@ -5,7 +5,7 @@ public interface MemberRepository extends JpaRepository { - Boolean existsByName(String name); + Boolean existsByUserId(String userId); - Member findByName(String name); + Member findByUserId(String userId); } diff --git a/src/main/java/com/cmc/suppin/member/service/.gitkeep b/src/main/java/com/cmc/suppin/member/service/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java new file mode 100644 index 0000000..54b2855 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java @@ -0,0 +1,11 @@ +package com.cmc.suppin.member.service.command; + +import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.domain.Member; + +public interface MemberCommandService { + + Member join(MemberRequestDTO.JoinDTO request); + + Boolean confirmUserId(MemberRequestDTO.IdConfirmDTO request); +} diff --git a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java new file mode 100644 index 0000000..c962341 --- /dev/null +++ b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java @@ -0,0 +1,50 @@ +package com.cmc.suppin.member.service.command; + +import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.converter.MemberConverter; +import com.cmc.suppin.member.domain.Member; +import com.cmc.suppin.member.domain.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Slf4j +@RequiredArgsConstructor +@Transactional +public class MemberCommandServiceImpl implements MemberCommandService { + + private final MemberRepository memberRepository; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + private final MemberConverter memberConverter; + + /** + * 회원가입 + */ + @Override + public Member join(MemberRequestDTO.JoinDTO request) { + // 중복된 아이디 체크 + if (memberRepository.existsByUserId(request.getUserId())) { + throw new IllegalArgumentException("이미 존재하는 유저입니다."); + } + + // DTO를 Entity로 변환 + Member member = memberConverter.toEntity(request, bCryptPasswordEncoder); + + // 회원 정보 저장 + memberRepository.save(member); + + return member; + } + + /** + * ID 중복 확인 + */ + @Override + public Boolean confirmUserId(MemberRequestDTO.IdConfirmDTO request) { + // 아이디 중복 체크 + return !memberRepository.existsByUserId(request.getUserId()); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 82665d1..9d59683 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,6 @@ spring: + server: + port: 8080 datasource: url: ${DB_URL} username: ${DB_USER} From 82644474d490b1fc48a67208d698f6890e8bbcf4 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 21:36:18 +0900 Subject: [PATCH 08/17] =?UTF-8?q?Fix:=20JWT=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cmc/suppin/global/security/jwt/JWTFilter.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java b/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java index 8374cf1..e57b279 100644 --- a/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java +++ b/src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.java @@ -55,10 +55,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String role = jwtUtil.getRole(token); // Member Entity를 생성하여 값 set - Member member = new Member(); - member.setName(username); - member.setPassword("tempPassword"); - member.setRole(role); + Member member = new Member(username, "tempPassword", "tempEmail", "tempPhoneNumber", role); + //MemberDetails에 회원 정보 객체 담기 MemberDetails customUserDetails = new MemberDetails(member); From d6ace482fbed33e397b32e262380cc5c8e41d1ef Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 21:36:49 +0900 Subject: [PATCH 09/17] =?UTF-8?q?Feat:=20Member=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/cmc/suppin/member/domain/Member.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/cmc/suppin/member/domain/Member.java b/src/main/java/com/cmc/suppin/member/domain/Member.java index 32adfdb..f1cf504 100644 --- a/src/main/java/com/cmc/suppin/member/domain/Member.java +++ b/src/main/java/com/cmc/suppin/member/domain/Member.java @@ -41,5 +41,13 @@ public class Member extends BaseDateTimeEntity { private String role; + // 추가된 생성자 + public Member(String name, String password, String email, String phoneNumber, String role) { + this.name = name; + this.password = password; + this.email = email; + this.phoneNumber = phoneNumber; + this.role = role; + } } From 7cd4a4554bc8b98198dc2d3619c4f26960152572 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 21:37:17 +0900 Subject: [PATCH 10/17] =?UTF-8?q?Feat:=20MemberConverter=20=EB=B9=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cmc/suppin/member/converter/MemberConverter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java index 65cab45..c419881 100644 --- a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java +++ b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java @@ -4,7 +4,9 @@ import com.cmc.suppin.member.controller.dto.MemberResponseDTO; import com.cmc.suppin.member.domain.Member; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Component; +@Component public class MemberConverter { public Member toEntity(MemberRequestDTO.JoinDTO request, BCryptPasswordEncoder encoder) { From dda4409ceece1c5ff6d10cc354bec61985617032 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 21:37:40 +0900 Subject: [PATCH 11/17] =?UTF-8?q?Fix:=20=EA=B2=BD=EB=A1=9C=EB=B3=84=20?= =?UTF-8?q?=EC=9D=B8=EA=B0=80=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cmc/suppin/global/security/SecurityConfig.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java b/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java index c8d1c65..3b52087 100644 --- a/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java +++ b/src/main/java/com/cmc/suppin/global/security/SecurityConfig.java @@ -86,9 +86,7 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { //경로별 인가 작업 http .authorizeHttpRequests((auth) -> auth - .requestMatchers("/login", "/", "/join").permitAll() - .requestMatchers("/admin").hasRole("ADMIN") - .anyRequest().authenticated()); + .anyRequest().permitAll()); //JWTFilter 추가 http From 988fb233bd14d0292d0b902da2ee4f2d228e5004 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 22:34:50 +0900 Subject: [PATCH 12/17] =?UTF-8?q?Fix:=20JoinResultDTO=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cmc/suppin/member/converter/MemberConverter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java index c419881..d807a12 100644 --- a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java +++ b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java @@ -6,6 +6,8 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; + @Component public class MemberConverter { @@ -25,7 +27,7 @@ public static MemberResponseDTO.JoinResultDTO toJoinResultDTO(Member member) { .userId(member.getUserId()) .name(member.getName()) .email(member.getEmail()) - .createdAt(member.getCreatedAt()) + .createdAt(LocalDateTime.now()) .build(); } From c7e3390170637749463582dbb0bf780283abe26e Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Mon, 29 Jul 2024 22:35:44 +0900 Subject: [PATCH 13/17] =?UTF-8?q?Refactor:=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=B0=8F=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../suppin/global/exception/status/ErrorStatus.java | 2 +- .../global/exception/status/SuccessStatus.java | 2 ++ .../cmc/suppin/global/presentation/ApiResponse.java | 8 ++++++-- .../com/cmc/suppin/member/controller/MemberApi.java | 12 ++++++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cmc/suppin/global/exception/status/ErrorStatus.java b/src/main/java/com/cmc/suppin/global/exception/status/ErrorStatus.java index 2188d1e..daa338a 100644 --- a/src/main/java/com/cmc/suppin/global/exception/status/ErrorStatus.java +++ b/src/main/java/com/cmc/suppin/global/exception/status/ErrorStatus.java @@ -21,7 +21,7 @@ public enum ErrorStatus implements BaseErrorCode { TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "테스트"), // Member - MEMBER_NICKNAME_DUPLICATED(HttpStatus.BAD_REQUEST, "MEMBER4001", "중복된 닉네임 입니다."), + MEMBER_USERID_DUPLICATED(HttpStatus.BAD_REQUEST, "MEMBER4001", "중복된 아이디 입니다."), MEMBER_PASSWORD_ERROR(HttpStatus.BAD_REQUEST, "MEMBER4002", "비밀번호가 잘못되었습니다."), //JWT diff --git a/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java b/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java index 9b9f06b..6956bbe 100644 --- a/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java +++ b/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java @@ -12,6 +12,8 @@ public enum SuccessStatus implements BaseCode { _OK(HttpStatus.OK, "COMMON200", "성공입니다."), //Member + MEMBER_JOIN_SUCCESS(HttpStatus.OK, "MEMBER2000", "회원 가입 성공입니다."), + MEMBER_ID_CONFIRM_SUCCESS(HttpStatus.OK, "MEMBER2001", "아이디가 중복되지 않습니다."), MEMBER_DELETE_SUCCESS(HttpStatus.OK, "MEMBER2001", "회원 탈퇴 성공입니다."), ; diff --git a/src/main/java/com/cmc/suppin/global/presentation/ApiResponse.java b/src/main/java/com/cmc/suppin/global/presentation/ApiResponse.java index d239b00..d4827a2 100644 --- a/src/main/java/com/cmc/suppin/global/presentation/ApiResponse.java +++ b/src/main/java/com/cmc/suppin/global/presentation/ApiResponse.java @@ -1,11 +1,11 @@ package com.cmc.suppin.global.presentation; +import com.cmc.suppin.global.exception.BaseCode; +import com.cmc.suppin.global.exception.status.SuccessStatus; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import lombok.AllArgsConstructor; import lombok.Getter; -import com.cmc.suppin.global.exception.BaseCode; -import com.cmc.suppin.global.exception.status.SuccessStatus; @Getter @AllArgsConstructor @@ -23,6 +23,10 @@ public static ApiResponse onSuccess(T result) { return new ApiResponse<>(true, SuccessStatus._OK.getCode(), SuccessStatus._OK.getMessage(), result); } + public static ApiResponse onSuccess(T result, SuccessStatus successStatus) { + return new ApiResponse<>(true, successStatus.getCode(), successStatus.getMessage(), result); + } + public static ApiResponse of(BaseCode code, T result) { return new ApiResponse<>(true, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), result); } diff --git a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java index 3262214..10b65bb 100644 --- a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java +++ b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java @@ -1,5 +1,6 @@ package com.cmc.suppin.member.controller; +import com.cmc.suppin.global.exception.status.SuccessStatus; import com.cmc.suppin.global.presentation.ApiResponse; import com.cmc.suppin.member.controller.dto.MemberRequestDTO; import com.cmc.suppin.member.controller.dto.MemberResponseDTO; @@ -12,7 +13,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; +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; @RestController @Slf4j @@ -30,15 +34,15 @@ public class MemberApi { public ApiResponse join(@RequestBody @Valid MemberRequestDTO.JoinDTO request) { Member member = memberCommandService.join(request); - return ApiResponse.onSuccess(MemberConverter.toJoinResultDTO(member)); + return ApiResponse.onSuccess(MemberConverter.toJoinResultDTO(member), SuccessStatus.MEMBER_JOIN_SUCCESS); } - @GetMapping("/checkUserId") + @PostMapping("/checkUserId") @Operation(summary = "아이디 중복 체크 API", description = "request : userId, response: 중복이면 false, 중복 아니면 true") public ApiResponse checkUserId(@RequestBody MemberRequestDTO.IdConfirmDTO request) { boolean checkUserId = memberCommandService.confirmUserId(request); - return ApiResponse.onSuccess(MemberConverter.toIdConfirmResultDTO(checkUserId)); + return ApiResponse.onSuccess(MemberConverter.toIdConfirmResultDTO(checkUserId), SuccessStatus.MEMBER_ID_CONFIRM_SUCCESS); } } From d97512edd5a1c1824ac476d8a94e8d015a9c2c03 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Tue, 30 Jul 2024 00:45:29 +0900 Subject: [PATCH 14/17] =?UTF-8?q?Fix:=20JWT=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=20claim=20=EA=B0=92=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java b/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java index e9f31d1..358c6a1 100644 --- a/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java +++ b/src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.java @@ -34,10 +34,10 @@ public Boolean isExpired(String token) { return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); } - public String createJwt(String username, String role, Long expiredMs) { + public String createJwt(String userId, String role, Long expiredMs) { return Jwts.builder() - .claim("username", username) + .claim("userId", userId) .claim("role", role) .issuedAt(new Date(System.currentTimeMillis())) .expiration(new Date(System.currentTimeMillis() + expiredMs)) From 1977d41ea3320a7454a1be1bd038d31e171df123 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Tue, 30 Jul 2024 00:46:18 +0900 Subject: [PATCH 15/17] =?UTF-8?q?Feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=ED=83=88=ED=87=B4=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../suppin/member/controller/MemberApi.java | 28 ++++++++++++++---- .../member/controller/dto/MemberDetails.java | 7 ++++- .../controller/dto/MemberRequestDTO.java | 8 +++++ .../controller/dto/MemberResponseDTO.java | 9 ++++++ .../member/converter/MemberConverter.java | 6 ++++ .../domain/repository/MemberRepository.java | 6 +++- .../service/command/MemberCommandService.java | 5 ++++ .../command/MemberCommandServiceImpl.java | 29 +++++++++++++++++++ 8 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java index 10b65bb..fc10ca8 100644 --- a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java +++ b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java @@ -2,6 +2,7 @@ import com.cmc.suppin.global.exception.status.SuccessStatus; import com.cmc.suppin.global.presentation.ApiResponse; +import com.cmc.suppin.member.controller.dto.MemberDetails; import com.cmc.suppin.member.controller.dto.MemberRequestDTO; import com.cmc.suppin.member.controller.dto.MemberResponseDTO; import com.cmc.suppin.member.converter.MemberConverter; @@ -12,11 +13,9 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.annotation.Validated; -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; +import org.springframework.web.bind.annotation.*; @RestController @Slf4j @@ -28,7 +27,7 @@ public class MemberApi { private final MemberCommandService memberCommandService; - + // 회원가입 @PostMapping("/join") @Operation(summary = "회원가입 API", description = "request 파라미터 : id, password, name, phone, email") public ApiResponse join(@RequestBody @Valid MemberRequestDTO.JoinDTO request) { @@ -45,4 +44,23 @@ public ApiResponse checkUserId(@RequestBod return ApiResponse.onSuccess(MemberConverter.toIdConfirmResultDTO(checkUserId), SuccessStatus.MEMBER_ID_CONFIRM_SUCCESS); } + + @DeleteMapping("/delete") + @Operation(summary = "회원탈퇴 API", description = "JWT 토큰을 헤더에 포함시켜 보내주시면 됩니다.") + public ApiResponse deleteMember(@AuthenticationPrincipal MemberDetails memberDetails) { + if (memberDetails == null) { + return ApiResponse.onFailure("403", "인증된 사용자만 삭제할 수 있습니다.", null); + } + memberCommandService.deleteMember(memberDetails.getUserId()); + return ApiResponse.onSuccess(null, SuccessStatus.MEMBER_DELETE_SUCCESS); + } + + // 로그인 + @PostMapping("/login") + @Operation(summary = "로그인 API", description = "request : userId, password") + public ApiResponse login(@RequestBody @Valid MemberRequestDTO.LoginRequestDTO request) { + MemberResponseDTO.LoginResponseDTO response = memberCommandService.login(request); + return ApiResponse.onSuccess(response); + } + } diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java index 053536e..db9b49b 100644 --- a/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.java @@ -37,9 +37,14 @@ public String getPassword() { @Override public String getUsername() { - return member.getName(); + return member.getUserId(); } + public String getUserId() { + return member.getUserId(); + } + + @Override public boolean isAccountNonExpired() { return true; diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java index 023208f..8b21f69 100644 --- a/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java @@ -39,4 +39,12 @@ public static class JoinDTO { public static class IdConfirmDTO { private String userId; } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class LoginRequestDTO { + private String userId; + private String password; + } } diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java index febf818..86bbad1 100644 --- a/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberResponseDTO.java @@ -28,4 +28,13 @@ public static class JoinResultDTO { public static class IdConfirmResultDTO { Boolean checkUserId; } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class LoginResponseDTO { + private String token; + private String userId; + } } diff --git a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java index d807a12..e7f6681 100644 --- a/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java +++ b/src/main/java/com/cmc/suppin/member/converter/MemberConverter.java @@ -37,4 +37,10 @@ public static MemberResponseDTO.IdConfirmResultDTO toIdConfirmResultDTO(boolean .build(); } + public static MemberResponseDTO.LoginResponseDTO toLoginResponseDTO(String token, Member member) { + return MemberResponseDTO.LoginResponseDTO.builder() + .token(token) + .userId(member.getUserId()) + .build(); + } } diff --git a/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java index 0b3296e..fb6eedd 100644 --- a/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java +++ b/src/main/java/com/cmc/suppin/member/domain/repository/MemberRepository.java @@ -3,9 +3,13 @@ import com.cmc.suppin.member.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface MemberRepository extends JpaRepository { Boolean existsByUserId(String userId); - Member findByUserId(String userId); + Optional findByUserId(String userId); + + void deleteByUserId(String userId); } diff --git a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java index 54b2855..051b4c1 100644 --- a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java +++ b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandService.java @@ -1,6 +1,7 @@ package com.cmc.suppin.member.service.command; import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.controller.dto.MemberResponseDTO; import com.cmc.suppin.member.domain.Member; public interface MemberCommandService { @@ -8,4 +9,8 @@ public interface MemberCommandService { Member join(MemberRequestDTO.JoinDTO request); Boolean confirmUserId(MemberRequestDTO.IdConfirmDTO request); + + void deleteMember(String memberId); + + MemberResponseDTO.LoginResponseDTO login(MemberRequestDTO.LoginRequestDTO request); } diff --git a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java index c962341..64caa7c 100644 --- a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java +++ b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java @@ -1,6 +1,8 @@ package com.cmc.suppin.member.service.command; +import com.cmc.suppin.global.security.jwt.JWTUtil; import com.cmc.suppin.member.controller.dto.MemberRequestDTO; +import com.cmc.suppin.member.controller.dto.MemberResponseDTO; import com.cmc.suppin.member.converter.MemberConverter; import com.cmc.suppin.member.domain.Member; import com.cmc.suppin.member.domain.repository.MemberRepository; @@ -19,6 +21,7 @@ public class MemberCommandServiceImpl implements MemberCommandService { private final MemberRepository memberRepository; private final BCryptPasswordEncoder bCryptPasswordEncoder; private final MemberConverter memberConverter; + private final JWTUtil jwtUtil; /** * 회원가입 @@ -47,4 +50,30 @@ public Boolean confirmUserId(MemberRequestDTO.IdConfirmDTO request) { // 아이디 중복 체크 return !memberRepository.existsByUserId(request.getUserId()); } + + /** + * 회원 탈퇴 + */ + @Override + public void deleteMember(String memberId) { + memberRepository.deleteByUserId(memberId); + } + + /** + * 로그인 + */ + @Override + public MemberResponseDTO.LoginResponseDTO login(MemberRequestDTO.LoginRequestDTO request) { + Member member = memberRepository.findByUserId(request.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("Invalid user ID or password")); + + if (!bCryptPasswordEncoder.matches(request.getPassword(), member.getPassword())) { + throw new IllegalArgumentException("Invalid user ID or password"); + } + + String token = jwtUtil.createJwt(member.getUserId(), member.getRole(), 604800000L); // 1주일 유효 토큰 + return MemberConverter.toLoginResponseDTO(token, member); + } + + } From 9e7178af1d992b49f7a17962fa364030a424204a Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Tue, 30 Jul 2024 00:47:47 +0900 Subject: [PATCH 16/17] =?UTF-8?q?Feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20=EA=B3=B5=ED=86=B5=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=B2=98=EB=A6=AC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/cmc/suppin/global/exception/status/SuccessStatus.java | 3 ++- src/main/java/com/cmc/suppin/member/controller/MemberApi.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java b/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java index 6956bbe..4826f9d 100644 --- a/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java +++ b/src/main/java/com/cmc/suppin/global/exception/status/SuccessStatus.java @@ -14,7 +14,8 @@ public enum SuccessStatus implements BaseCode { //Member MEMBER_JOIN_SUCCESS(HttpStatus.OK, "MEMBER2000", "회원 가입 성공입니다."), MEMBER_ID_CONFIRM_SUCCESS(HttpStatus.OK, "MEMBER2001", "아이디가 중복되지 않습니다."), - MEMBER_DELETE_SUCCESS(HttpStatus.OK, "MEMBER2001", "회원 탈퇴 성공입니다."), + MEMBER_DELETE_SUCCESS(HttpStatus.OK, "MEMBER2002", "회원 탈퇴 성공입니다."), + MEMBER_LOGIN_SUCCESS(HttpStatus.OK, "MEMBER2003", "로그인 성공입니다."), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java index fc10ca8..851b686 100644 --- a/src/main/java/com/cmc/suppin/member/controller/MemberApi.java +++ b/src/main/java/com/cmc/suppin/member/controller/MemberApi.java @@ -60,7 +60,7 @@ public ApiResponse deleteMember(@AuthenticationPrincipal MemberDetails mem @Operation(summary = "로그인 API", description = "request : userId, password") public ApiResponse login(@RequestBody @Valid MemberRequestDTO.LoginRequestDTO request) { MemberResponseDTO.LoginResponseDTO response = memberCommandService.login(request); - return ApiResponse.onSuccess(response); + return ApiResponse.onSuccess(response, SuccessStatus.MEMBER_LOGIN_SUCCESS); } } From db18445323b950ba4fa7924daacd4054338b5b26 Mon Sep 17 00:00:00 2001 From: yxhwxn Date: Tue, 30 Jul 2024 00:52:23 +0900 Subject: [PATCH 17/17] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/dto/MemberRequestDTO.java | 2 +- .../service/command/MemberCommandServiceImpl.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java b/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java index 8b21f69..91b4604 100644 --- a/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java +++ b/src/main/java/com/cmc/suppin/member/controller/dto/MemberRequestDTO.java @@ -22,7 +22,7 @@ public static class JoinDTO { private String name; @NotBlank(message = "비밀번호를 입력해주세요") - @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", message = "비밀번호는 8~16자 영문, 숫자, 특수문자를 사용하세요.") + @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", message = "비밀번호는 8~20자 영문, 숫자, 특수문자를 사용하세요.") private String password; @NotBlank(message = "이메일을 입력해주세요") diff --git a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java index 64caa7c..e5223fb 100644 --- a/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java +++ b/src/main/java/com/cmc/suppin/member/service/command/MemberCommandServiceImpl.java @@ -33,6 +33,12 @@ public Member join(MemberRequestDTO.JoinDTO request) { throw new IllegalArgumentException("이미 존재하는 유저입니다."); } + // 비밀번호 조건 검증 + String password = request.getPassword(); + if (!isValidPassword(password)) { + throw new IllegalArgumentException("비밀번호는 8~20자 영문, 숫자, 특수문자를 사용해야 합니다."); + } + // DTO를 Entity로 변환 Member member = memberConverter.toEntity(request, bCryptPasswordEncoder); @@ -42,6 +48,11 @@ public Member join(MemberRequestDTO.JoinDTO request) { return member; } + // 비밀번호 조건 검증 메서드 + private boolean isValidPassword(String password) { + return password.matches("(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}"); + } + /** * ID 중복 확인 */