From f12a0b4b0695ad30177f669d4e6bfc261edc68ce Mon Sep 17 00:00:00 2001 From: wrathlion Date: Mon, 9 Sep 2024 02:42:16 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=99=84=EB=A3=8C=20=EC=8B=9C=20=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EB=A6=AC=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: Member DB 수정 - 한글이름, 영어이름, 기수, 과정 추가 - 과정 표현하는 Enumerate 추가 - 수정에 따른 오류 수정 - getNickname -> getKoreaName of getEnglishName Related to: #2 * Rename: Member DB Field 이름 변경 Related to: #2 * Feat: /api/auth/member 관련 DTO 작성 Related to: #2 * Feat: /api/auth/member API 관련 DTO 내용 추가 Related to: #2 * Feat: /api/auth/member 관련 MemberService 추가 Related to: #2 * Feat: /api/auth/member 관련 MemberContoller 추가 Related to: #2 * Feat: CORS Error 수정 Related to: #2 * Fix: RefreshToken 매번 생성 오류 수정 Related to: #2 * Refactor: 코드 리뷰 내용 반영 - 불필요 코드 삭제 - 반환값 오류 수정 Related to: #2 * Test: MemberRepository Test 코드 추가 Related to: #2 * Chore: Test용 application yml 추가 Related to: #2 * 카카오 회원가입/로그인, /api/auth/member API 기능 추가 (#6) * ✨ 카카오 로그인/회원가입 구현 (#3) * Style: codeStyle 추가 * Feat: Member, Profile Entity, MemberRepository 추가 + MemberRepositoryTest 추가 Related to: #1 * Feat: 카카오 로그인, 회원가입 기능 추가 - 권한 필요한 API 접속 시 카카오 로그인 페이지 반환 - 로그인 성공 시 가입이 안되어있으면 가입, 되어있으면 권한 부여 - 권한은 JWT 발급 후 쿠키에 추가, 매 요청마다 JwtFilter를 거치며 쿠키의 Jwt 확 Related to: #1 * Style: 코드 포맷, 비밀번호 노출 등 수정 Related to: #1 * ✨ /api/auth/member API 기능 추가 (#4) * Feat: Member DB 수정 - 한글이름, 영어이름, 기수, 과정 추가 - 과정 표현하는 Enumerate 추가 - 수정에 따른 오류 수정 - getNickname -> getKoreaName of getEnglishName Related to: #2 * Rename: Member DB Field 이름 변경 Related to: #2 * Feat: /api/auth/member 관련 DTO 작성 Related to: #2 * Feat: /api/auth/member API 관련 DTO 내용 추가 Related to: #2 * Feat: /api/auth/member 관련 MemberService 추가 Related to: #2 * Feat: /api/auth/member 관련 MemberContoller 추가 Related to: #2 * Feat: CORS Error 수정 Related to: #2 * Fix: RefreshToken 매번 생성 오류 수정 Related to: #2 * Refactor: 코드 리뷰 내용 반영 - 불필요 코드 삭제 - 반환값 오류 수정 Related to: #2 * Chore: DB를 MariaDB -> H2로 변경 Ralted to: #5 * Test: Test용 application 추가 Related to: #5 * Feat: 깃허브 액션 작성 Related to: #5 * Feat: 도커 파일 작성 Related to: #5 * Feat: 테스트용 도커 컴포즈 파일 작성 Related to: #5 * Refactor: import 추가 * Fix: 깃허브 액션 오타 수정 Related to: #5 * Feat: SSL 적용 * Fix: SSL 수정 * Fix: SSL 수정 스크립트 수정 * Fix: SSL 스크립트 수정 * Fix: SSL 스크립트 수정 * Feat: 카카오 로그인 완료 시 메인 페이지로 redirect Related to: #1 --------- Co-authored-by: 박지혁 --- .github/workflows/deploy.yml | 83 +++++++++++++++++++ .gitignore | 7 +- Dockerfile | 14 ++++ docker-compose-auth-test-db.yml | 15 ++++ .../kaboo_auth/config/SecurityConfig.java | 2 +- .../domain/handler/LoginSuccessHandler.java | 6 +- .../kaboo_auth/KabooAuthApplicationTests.java | 1 + .../repository/MemberRepositoryTest.java | 67 +++++++++++++++ 8 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 Dockerfile create mode 100644 docker-compose-auth-test-db.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..34ff6d0 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,83 @@ +name: Build and Push Docker Image and Deploy + +# main, dev 브랜치에 push or PR 이 오면 실행 +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +jobs: + # 도커 이미지 빌드, 푸시 + build_and_push: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + # DB 세팅 정보 입력 + - name: Set up application.yml + run: | + echo "${{ secrets.APPLICATION }}" > ./src/main/resources/application.yml + echo "${{ secrets.APPLICATION_TEST }}" > ./src/main/resources/application-test.yml + + # SSL 적용 + - name: Create SSH Key File + run: echo "${{ secrets.PRIVATE_KEY }}" > /tmp/private_key.pem + + - name: Set Permissions for SSH Key + run: chmod 600 /tmp/private_key.pem + + - name: Copy keystore.p12 from EC2 + run: scp -o StrictHostKeyChecking=no -i /tmp/private_key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:/home/${{ secrets.EC2_USER }}/keystore.p12 ./src/main/resources/keystore.p12 + + # 도커 이미지 빌드용 환경 세팅 및 도커 이미지 빌드 + - name: set up test DB and docker build + run: | + docker compose -f docker-compose-auth-test-db.yml up -d # 도커 컴포즈파일로 테스트 환경 세팅 + DOCKER_BUILDKIT=0 docker build --network testNet -t ${{ secrets.DOCKER_IMAGE_NAME }}:latest . # 도커 빌드 (빌드 과정에서 네트워크 사용을 위해 빌드킷 0) + docker compose -f docker-compose-auth-test-db.yml down # 테스트 환경 제거 (네트워크까지 삭제됨) + + # 도커 로그인 + - name: docker Login + uses: docker/login-action@v3.3.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + # 도커 이미지 push + - name: push docker images + run: | + docker push ${{ secrets.DOCKER_IMAGE_NAME }}:latest + + # 도커 이미지 EC2 인스턴스에 배포 + deploy_to_ec2: + needs: build_and_push + runs-on: ubuntu-24.04 + + steps: + - name: Deploy to EC2 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_HOST }} # EC2 IP 주소 + username: ${{ secrets.EC2_USER }} # EC2 사용자 + key: ${{ secrets.PRIVATE_KEY }} # pem 키 + + # 기존 컨테이너 중지 + script: | + CONTAINER_ID=$(sudo docker ps -aq --filter "name=kaboo-auth") + + if [ ! -z "$CONTAINER_ID" ]; then + sudo docker stop $CONTAINER_ID || true + sudo docker rm -f $CONTAINER_ID || true + fi + + # 최신 도커 이미지로 컨테이너 실행 + sudo docker pull ${{ secrets.DOCKER_IMAGE_NAME }}:latest # 도커 최신 이미지 다운로드 + # 도커 이미지 실행 (host.docker.internal 사용 가능하도록) + docker run --name kaboo-auth -d --add-host host.docker.internal:host-gateway -p 8081:8081 ${{ secrets.DOCKER_IMAGE_NAME }}:latest + sudo docker image prune -f # 구버전의 도커 이미지 제거 diff --git a/.gitignore b/.gitignore index df991c9..f9cc683 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ fabric.properties .idea/* +!.idea/codeStyles !.idea/runConfigurations ### Java ### @@ -174,7 +175,9 @@ gradle-app.setting # Java heap dump *.hprof -# End of https://www.toptal.com/developers/gitignore/api/java,gradle,macos,intellij+allauth.yml -oauth.yml +# End of https://www.toptal.com/developers/gitignore/api/java,gradle,macos,intellij+all +application.yml +application-test.yml database.yml jwt.yml +oauth.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..eb6b700 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# Build +FROM eclipse-temurin:17-jdk AS build +LABEL authors="pjh5365" + +WORKDIR /src +COPY . /src +RUN ./gradlew build + +# Run +FROM eclipse-temurin:17-jre +EXPOSE 8081 +COPY --from=build /src/build/libs/*SNAPSHOT.jar kaboo-auth.jar + +ENTRYPOINT ["java", "-jar", "kaboo-auth.jar"] diff --git a/docker-compose-auth-test-db.yml b/docker-compose-auth-test-db.yml new file mode 100644 index 0000000..9d42339 --- /dev/null +++ b/docker-compose-auth-test-db.yml @@ -0,0 +1,15 @@ +# 테스트 DB 환경 세팅 +services: + redis: + image: redis:alpine + container_name: redis + ports: + - "6379:6379" + networks: + - testNet + +networks: + testNet: + name: testNet # 네트워크 이름 지정 + driver: bridge # 브릿지 모드 + attachable: true # 외부접속 허용 diff --git a/src/main/java/kaboo/kaboo_auth/config/SecurityConfig.java b/src/main/java/kaboo/kaboo_auth/config/SecurityConfig.java index 6202af5..fe2cedc 100644 --- a/src/main/java/kaboo/kaboo_auth/config/SecurityConfig.java +++ b/src/main/java/kaboo/kaboo_auth/config/SecurityConfig.java @@ -45,7 +45,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.oauth2Login(auth -> auth .userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig.userService(customOAuth2Service)) .successHandler(loginSuccessHandler)); - + return http.build(); } diff --git a/src/main/java/kaboo/kaboo_auth/domain/handler/LoginSuccessHandler.java b/src/main/java/kaboo/kaboo_auth/domain/handler/LoginSuccessHandler.java index 4bfe757..46f798b 100644 --- a/src/main/java/kaboo/kaboo_auth/domain/handler/LoginSuccessHandler.java +++ b/src/main/java/kaboo/kaboo_auth/domain/handler/LoginSuccessHandler.java @@ -2,6 +2,7 @@ import java.io.IOException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; @@ -26,6 +27,9 @@ public class LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final int accessTokenValidTime = 10 * 60; // 유효기간 : 10분 private final int refreshTokenValidTime = 10 * 24 * 60 * 60; // 유효기간 : 10일 + @Value("${AUTH.REDIRECT_URL}") + String redirectURL; + private Cookie createCookie(String key, String value, int maxAge) { Cookie cookie = new Cookie(key, value); cookie.setMaxAge(maxAge); @@ -49,6 +53,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.addCookie(createCookie("Username", username, refreshTokenValidTime)); response.addCookie(createCookie("Authorization", accessToken, accessTokenValidTime)); response.addCookie(createCookie("RefreshToken", refreshToken, refreshTokenValidTime)); - response.sendRedirect("/"); + response.sendRedirect(redirectURL); } } diff --git a/src/test/java/kaboo/kaboo_auth/KabooAuthApplicationTests.java b/src/test/java/kaboo/kaboo_auth/KabooAuthApplicationTests.java index af9cdf9..3da9996 100644 --- a/src/test/java/kaboo/kaboo_auth/KabooAuthApplicationTests.java +++ b/src/test/java/kaboo/kaboo_auth/KabooAuthApplicationTests.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest @ActiveProfiles("test") diff --git a/src/test/java/kaboo/kaboo_auth/repository/MemberRepositoryTest.java b/src/test/java/kaboo/kaboo_auth/repository/MemberRepositoryTest.java index b1c8d0f..f436968 100644 --- a/src/test/java/kaboo/kaboo_auth/repository/MemberRepositoryTest.java +++ b/src/test/java/kaboo/kaboo_auth/repository/MemberRepositoryTest.java @@ -2,19 +2,24 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; +import kaboo.kaboo_auth.domain.Course; import kaboo.kaboo_auth.domain.entity.Member; @DataJpaTest @Transactional @DisplayName("Member Repository Test") +@ActiveProfiles("test") class MemberRepositoryTest { @Autowired MemberRepository memberRepository; @@ -58,4 +63,66 @@ void findByUsername_Failure() { // Then assertEquals(result, Optional.empty()); } + + @Test + @DisplayName("DB에 존재하는 classNum 찾을 때 findByClassNum method 테스트") + void findByClassNum_Success() { + // Given + Member member1 = Member.builder().englishName("Alice").classNum(1).build(); + Member member2 = Member.builder().englishName("Bob").classNum(1).build(); + + memberRepository.save(member1); + memberRepository.save(member2); + + List expectedMembers = Arrays.asList(member1, member2); + + // When + List byClassNum = memberRepository.findByClassNum(1); + + // Then + assertEquals(byClassNum.size(), 2, "리스트의 크기가 다릅니다."); + assertTrue(byClassNum.containsAll(expectedMembers), "리스트에 모든 멤버가 포함되어 있지 않습니다."); + } + + @Test + @DisplayName("DB에 존재하지 않는 classNum 찾을 때 findByClassNum method 테스트") + void findByClassNum_Failure() { + // Given + + // When + List byClassNum = memberRepository.findByClassNum(1); + + // Then + assertEquals(byClassNum.size(), 0); + } + + @Test + @DisplayName("findByKoreaName 성공 테스트") + void findByKoreaName_Success() { + // Given + Member member = Member.builder() + .koreaName("홍길동") + .classNum(1) + .course(Course.AI) + .build(); + memberRepository.save(member); + + // When + Optional byKoreaName = memberRepository.findByKoreaName("홍길동"); + + // Then + assertEquals(byKoreaName.get(), member); + } + + @Test + @DisplayName("findByKoreaName 실패 테스트") + void findByKoreaName_Failure() { + // Given + + // When + Optional byKoreaName = memberRepository.findByKoreaName("홍길동"); + + // Then + assertTrue(byKoreaName.isEmpty()); + } }