From ac879f8e3854daa2a87cbe85a83ba0a4faa1e8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EC=A4=80?= <105282117+sengjun0624@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:10:17 +0900 Subject: [PATCH 1/2] =?UTF-8?q?:bug:=20Fix:=20Cookie=EC=97=90=20=20CrossDo?= =?UTF-8?q?main=EC=9D=84=20=ED=97=88=EC=9A=A9=20(#63)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminAuthController.java | 140 +++++++++--------- 1 file changed, 71 insertions(+), 69 deletions(-) diff --git a/src/main/java/dev/admin/admin/controller/AdminAuthController.java b/src/main/java/dev/admin/admin/controller/AdminAuthController.java index 35f9c4e..1569860 100644 --- a/src/main/java/dev/admin/admin/controller/AdminAuthController.java +++ b/src/main/java/dev/admin/admin/controller/AdminAuthController.java @@ -9,6 +9,7 @@ import dev.admin.global.apiPayload.ApiResponse; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; + import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -18,73 +19,74 @@ @RequiredArgsConstructor public class AdminAuthController { - private final AdminAuthCommandService authCommandService; - private final JwtUtil jwtUtil; - - @PostMapping("/login") - public ApiResponse login(@RequestBody LoginRequestDto requestDto, HttpServletResponse response) { - JwtDto tokenDto = authCommandService.login(requestDto); - - ResponseCookie accessTokenCookie = ResponseCookie.from("accessToken", tokenDto.getAccessToken()) - .httpOnly(true) - .secure(false) - .path("/") - .sameSite("Lax") - .maxAge(60 * 60) - .build(); - - ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", tokenDto.getRefreshToken()) - .httpOnly(true) - .secure(false) - .path("/") - .sameSite("Lax") - .maxAge(7 * 24 * 60 * 60) - .build(); - - response.addHeader("Set-Cookie", accessTokenCookie.toString()); - response.addHeader("Set-Cookie", refreshTokenCookie.toString()); - - return ApiResponse.onSuccess(tokenDto); - } - - @PostMapping("/logout") - public ApiResponse logout(@CookieValue("refreshToken") String refreshToken, - HttpServletResponse response) { - authCommandService.logout(refreshToken); - - ResponseCookie clearAccessToken = ResponseCookie.from("accessToken", "") - .httpOnly(true) - .secure(false) - .path("/") - .maxAge(0) - .sameSite("Lax") - .build(); - - ResponseCookie clearRefreshToken = ResponseCookie.from("refreshToken", "") - .httpOnly(true) - .secure(false) - .path("/") - .maxAge(0) - .sameSite("Lax") - .build(); - - response.addHeader("Set-Cookie", clearAccessToken.toString()); - response.addHeader("Set-Cookie", clearRefreshToken.toString()); - - return ApiResponse.onSuccess(null); - } - - @GetMapping("/me") - public ApiResponse me(@CookieValue("accessToken") String accessToken) { - JwtPayload payload = jwtUtil.parseToken(accessToken); - - AdminInfoResponse info = new AdminInfoResponse( - payload.getSub(), - payload.getEmail(), - payload.getName(), - payload.getRole() - ); - - return ApiResponse.onSuccess(info); - } + private final AdminAuthCommandService authCommandService; + private final JwtUtil jwtUtil; + + @PostMapping("/login") + public ApiResponse login(@RequestBody LoginRequestDto requestDto, HttpServletResponse response) { + JwtDto tokenDto = authCommandService.login(requestDto); + + ResponseCookie accessTokenCookie = ResponseCookie.from("accessToken", tokenDto.getAccessToken()) + .httpOnly(true) + .secure(true) + .path("/") + .sameSite("None") + .maxAge(60 * 60) + .build(); + + ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", tokenDto.getRefreshToken()) + .httpOnly(true) + .secure(true) + .path("/") + .sameSite("None") + .maxAge(7 * 24 * 60 * 60) + .build(); + + response.addHeader("Set-Cookie", accessTokenCookie.toString()); + response.addHeader("Set-Cookie", refreshTokenCookie.toString()); + + return ApiResponse.onSuccess(tokenDto); + } + + @PostMapping("/logout") + public ApiResponse logout(@CookieValue("refreshToken") String refreshToken, + HttpServletResponse response) { + authCommandService.logout(refreshToken); + + ResponseCookie clearAccessToken = ResponseCookie.from("accessToken", "") + .httpOnly(true) + .secure(true) + .path("/") + .sameSite("None") + .maxAge(0) + .build(); + + ResponseCookie clearRefreshToken = ResponseCookie.from("refreshToken", "") + .httpOnly(true) + .secure(true) + .path("/") + .sameSite("None") + .maxAge(0) + .build(); + + + response.addHeader("Set-Cookie", clearAccessToken.toString()); + response.addHeader("Set-Cookie", clearRefreshToken.toString()); + + return ApiResponse.onSuccess(null); + } + + @GetMapping("/me") + public ApiResponse me(@CookieValue("accessToken") String accessToken) { + JwtPayload payload = jwtUtil.parseToken(accessToken); + + AdminInfoResponse info = new AdminInfoResponse( + payload.getSub(), + payload.getEmail(), + payload.getName(), + payload.getRole() + ); + + return ApiResponse.onSuccess(info); + } } From 4ffb7535015159d7670eda97db1317b36b681f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=8A=B9=EC=A4=80?= <105282117+sengjun0624@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:56:55 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20jwt=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=EC=8B=9C=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=A7=80=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#66)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :bug: fix: Cookie 발급시 Domain 지정하도록 변경 * :sparkles: feat: ReleaseNote workflow 추가, Release PR Template 추가 * :sparkles: feat: ReleaseNote workflow 추가, Release PR Template 추가 --- .../feature_template.md} | 0 .../PULL_REQUEST_TEMPLATE/release_template.md | 17 ++++++ .github/workflows/deploy.yml | 59 ------------------- .github/workflows/release_note.yml | 26 ++++++++ .../admin/controller/AdminAuthController.java | 8 +++ 5 files changed, 51 insertions(+), 59 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE.md => PULL_REQUEST_TEMPLATE/feature_template.md} (100%) create mode 100644 .github/PULL_REQUEST_TEMPLATE/release_template.md delete mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/release_note.yml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE/feature_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE/feature_template.md diff --git a/.github/PULL_REQUEST_TEMPLATE/release_template.md b/.github/PULL_REQUEST_TEMPLATE/release_template.md new file mode 100644 index 0000000..d5dc635 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/release_template.md @@ -0,0 +1,17 @@ +# {{version_number}} 릴리스 요약 + +## 변경 사항 + +- 새로운 기능: [간단한 설명] +- 개선 사항: [간단한 설명] +- 버그 수정: [수정된 버그에 대한 간단한 설명] + +## 상세 변경 로그 + +[이 릴리스에 포함된 커밋 목록 또는 더 상세한 변경 로그 링크] + +--- + +**버전 올림 기준:** `주요 변경 (Major)`: 하위 호환되지 않는 API 변경, `기능 (Minor)`: 하위 호환되는 새로운 기능 추가, `패치 (Patch)`: 하위 호환되는 버그 수정 시 올립니다. + + diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index b216d3b..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Docker CI/CD to GCP - -on: - push: - branches: - - develop - - workflow_dispatch: - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Generate application.yml from secret - run: | - mkdir -p src/main/resources - echo "${{ secrets.APP_YML }}" > src/main/resources/application.yml - - - name: Grant execute permission to Gradle wrapper - run: chmod +x ./gradlew - - - name: Build Spring Boot app (JAR) - run: ./gradlew clean build -x test - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push Docker image - run: | - docker build -t ${{ secrets.DOCKER_USERNAME }}/tokkit-server-admin:latest . - docker push ${{ secrets.DOCKER_USERNAME }}/tokkit-server-admin:latest - - - name: SSH to GCP and deploy container - uses: appleboy/ssh-action@v0.1.10 - with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USER }} - key: ${{ secrets.SSH_PRIVATE_KEY }} - script: | - docker stop tokkit-server-admin || true - docker rm tokkit-server-admin || true - docker pull ${{ secrets.DOCKER_USERNAME }}/tokkit-server-admin:latest - docker run -d \ - --name tokkit-server-admin \ - -p 8080:8080 \ - --memory=512m \ - --restart always \ - ${{ secrets.DOCKER_USERNAME }}/tokkit-server-admin:latest - diff --git a/.github/workflows/release_note.yml b/.github/workflows/release_note.yml new file mode 100644 index 0000000..8f3e931 --- /dev/null +++ b/.github/workflows/release_note.yml @@ -0,0 +1,26 @@ +name: Tag Release +on: + pull_request: + branches: + - "main" + types: + - closed +jobs: + generate_release: + name: Release + if: contains(github.event.pull_request.head.ref, 'release/v') && github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + - name: Fetch version + run: | + VERSION=$(echo "${GITHUB_HEAD_REF}" | sed "s/release\\\\/v//") + echo "version=${VERSION}" >> $GITHUB_OUTPUT + id: fetch-version + - name: Generate release notes + uses: softprops/action-gh-release@v1 + with: + token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + tag_name: v${{ steps.fetch-version.outputs.version }} + generate_release_notes: true diff --git a/src/main/java/dev/admin/admin/controller/AdminAuthController.java b/src/main/java/dev/admin/admin/controller/AdminAuthController.java index 1569860..1d20a2c 100644 --- a/src/main/java/dev/admin/admin/controller/AdminAuthController.java +++ b/src/main/java/dev/admin/admin/controller/AdminAuthController.java @@ -10,6 +10,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; // 이 import가 필요합니다. import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -22,6 +23,9 @@ public class AdminAuthController { private final AdminAuthCommandService authCommandService; private final JwtUtil jwtUtil; + @Value("${app.cookie.domain}") // 설정 파일에서 app.cookie.domain 값을 읽어옵니다. + private String cookieDomain; + @PostMapping("/login") public ApiResponse login(@RequestBody LoginRequestDto requestDto, HttpServletResponse response) { JwtDto tokenDto = authCommandService.login(requestDto); @@ -30,6 +34,7 @@ public ApiResponse login(@RequestBody LoginRequestDto requestDto, HttpSe .httpOnly(true) .secure(true) .path("/") + .domain(cookieDomain) // 설정 파일에서 읽어온 도메인 값을 설정합니다. .sameSite("None") .maxAge(60 * 60) .build(); @@ -38,6 +43,7 @@ public ApiResponse login(@RequestBody LoginRequestDto requestDto, HttpSe .httpOnly(true) .secure(true) .path("/") + .domain(cookieDomain) // refreshToken도 동일하게 설정 .sameSite("None") .maxAge(7 * 24 * 60 * 60) .build(); @@ -57,6 +63,7 @@ public ApiResponse logout(@CookieValue("refreshToken") String refreshToken .httpOnly(true) .secure(true) .path("/") + .domain(cookieDomain) // 로그아웃 시에도 도메인을 지정해야 삭제가 정확히 됩니다. .sameSite("None") .maxAge(0) .build(); @@ -65,6 +72,7 @@ public ApiResponse logout(@CookieValue("refreshToken") String refreshToken .httpOnly(true) .secure(true) .path("/") + .domain(cookieDomain) // 로그아웃 시에도 도메인을 지정해야 삭제가 정확히 됩니다. .sameSite("None") .maxAge(0) .build();