-
Notifications
You must be signed in to change notification settings - Fork 0
[DEPLOY] 2차 배포 #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DEPLOY] 2차 배포 #21
Changes from all commits
0311aec
25d0106
e16b5e2
71758e3
e87c5bd
a73bf3b
3e67534
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| # .github/workflows/cd.yml | ||
| name: CD - Deploy to EC2 (Blue-Green) | ||
|
|
||
| on: | ||
| push: # Push 이벤트 발생 시 | ||
| branches: [ main ] # main 브랜치에 Push될 때만 실행 | ||
| workflow_dispatch: # 수동 실행 가능 | ||
|
|
||
| env: # 워크플로우 전체에서 사용할 환경 변수 | ||
| AWS_REGION: ap-northeast-2 # 본인이 사용하는 AWS 리전으로 변경 | ||
| ECR_REPOSITORY: focussu-backend # 생성한 ECR 리포지토리 이름 | ||
|
|
||
| jobs: | ||
| # --- Job 1: Docker 이미지 빌드 및 ECR 푸시 --- | ||
| build-and-push: | ||
| name: Build and Push Docker Image | ||
| runs-on: ubuntu-latest | ||
| outputs: # 다음 job('deploy-blue-green')에서 사용할 값 정의 | ||
| image_tag: ${{ steps.determine_tag.outputs.tag }} # 생성된 이미지 태그 전달 | ||
|
|
||
| steps: | ||
| # 1. 코드 체크아웃 | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| # 2. AWS 자격 증명 설정: GitHub Secrets에 저장된 AWS 키를 사용하여 AWS 서비스에 접근할 권한 설정 | ||
| - name: Configure AWS credentials | ||
| uses: aws-actions/configure-aws-credentials@v4 | ||
| with: | ||
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} # GitHub Secret | ||
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # GitHub Secret | ||
| aws-region: ${{ env.AWS_REGION }} | ||
|
|
||
| # 3. Amazon ECR 로그인: Docker 클라이언트가 ECR에 이미지를 푸시할 수 있도록 로그인 | ||
| - name: Login to Amazon ECR | ||
| id: login-ecr # 이 스텝의 출력을 다른 스텝에서 사용할 수 있도록 ID 부여 | ||
| uses: aws-actions/amazon-ecr-login@v2 | ||
|
|
||
| # 4. JDK 설정 | ||
| - name: Set up JDK 17 | ||
| uses: actions/setup-java@v4 | ||
| with: { java-version: '17', distribution: 'temurin', cache: 'gradle' } | ||
|
|
||
| # 5. Gradle 설정 | ||
| - name: Setup Gradle | ||
| uses: gradle/actions/setup-gradle@v3 | ||
|
|
||
| # 6. gradlew 실행 권한 부여 | ||
| - name: Grant execute permission for gradlew | ||
| run: chmod +x ./gradlew | ||
| working-directory: ./backend | ||
|
|
||
| # 7. Spring Boot JAR 빌드 (테스트 제외): CI 단계에서 이미 테스트를 통과했으므로 '-x test' 옵션으로 테스트 생략 | ||
| - name: Build Spring Boot JAR (without tests) | ||
| run: ./gradlew bootJar -x test | ||
| working-directory: ./backend | ||
|
|
||
| # 8. 이미지 태그 결정: GitHub Commit SHA의 앞 7자리를 이미지 태그로 사용 (고유성 확보) | ||
| - name: Determine Image Tag (use commit SHA) | ||
| id: determine_tag | ||
| run: echo "tag=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT # GitHub Actions 출력 변수로 설정 | ||
|
|
||
| # 9. Docker 이미지 빌드 및 태그 지정: Dockerfile을 사용하여 이미지 빌드하고 ECR 주소와 태그 지정 | ||
| - name: Build and tag Docker image | ||
| env: | ||
| ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} # ECR 로그인 스텝에서 얻은 레지스트리 주소 | ||
| IMAGE_TAG: ${{ steps.determine_tag.outputs.tag }} # 위에서 결정한 이미지 태그 | ||
| run: | | ||
| # docker build 명령어 실행 | ||
| # -t 옵션: 이미지 이름 및 태그 지정 (예: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/focussu-backend:abcdefg) | ||
| # ./backend: Dockerfile이 있는 디렉토리 경로 | ||
| # --build-arg: Dockerfile 내에서 사용할 변수 전달 (JAR 파일 경로) | ||
| docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG ./backend \ | ||
| --build-arg JAR_FILE=build/libs/*.jar | ||
| # 10. Docker 이미지를 ECR로 푸시 | ||
| - name: Push Docker image to ECR | ||
| env: | ||
| ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} | ||
| IMAGE_TAG: ${{ steps.determine_tag.outputs.tag }} | ||
| run: docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG | ||
|
|
||
| # --- Job 2: EC2에 Blue-Green 배포 --- | ||
| deploy-blue-green: | ||
| name: Deploy to EC2 (Blue-Green) | ||
| needs: build-and-push # 'build-and-push' job이 성공해야만 실행됨 | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| # 1. AWS 자격 증명 설정 (EC2에서 ECR 접근 등에 필요할 수 있음) | ||
| - name: Configure AWS credentials | ||
| uses: aws-actions/configure-aws-credentials@v4 | ||
| with: { aws-access-key-id: ${ { secrets.AWS_ACCESS_KEY_ID } }, aws-secret-access-key: ${ { secrets.AWS_SECRET_ACCESS_KEY } }, aws-region: ${ { env.AWS_REGION } } } | ||
|
|
||
| # 2. Amazon ECR 로그인 (EC2 인스턴스에서 이미지를 pull 할 때 필요할 수 있음) | ||
| - name: Login to Amazon ECR | ||
| id: login-ecr | ||
| uses: aws-actions/amazon-ecr-login@v2 | ||
|
|
||
| # 3. SSH를 통해 EC2 서버에 접속하여 배포 스크립트 실행 | ||
| - name: Deploy via SSH | ||
| uses: appleboy/ssh-action@v1.0.3 # 검증된 버전 사용 권장 | ||
| with: | ||
| host: ${{ secrets.EC2_HOST }} # EC2 Public IP 또는 DNS (GitHub Secret) | ||
| username: ubuntu # EC2 사용자 이름 (AMI에 따라 다름) | ||
| key: ${{ secrets.EC2_SSH_KEY }} # EC2 접속용 Private Key (GitHub Secret) | ||
| port: 22 # SSH 포트 | ||
| script_stop: true # 스크립트 실행 중 오류 발생 시 즉시 중지 | ||
| script: | | ||
| # --- EC2 서버 내부에서 실행될 스크립트 시작 --- | ||
| # 0. 환경 변수 설정 (스크립트 내에서 사용) | ||
| export ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }} # 이전 스텝에서 얻은 ECR 주소 | ||
| export ECR_REPOSITORY=${{ env.ECR_REPOSITORY }} # 워크플로우 환경 변수 | ||
| export IMAGE_TAG=${{ needs.build-and-push.outputs.image_tag }} # 이전 job에서 전달된 이미지 태그 | ||
| export APP_DIR="/home/ubuntu/app" # EC2 내 작업 디렉토리 경로 | ||
| # 1. 배포 대상 결정 (Blue 또는 Green) | ||
| cd $APP_DIR # 작업 디렉토리로 이동 | ||
| # 현재 Nginx가 가리키는 서비스 확인 (service-env.inc 파일 읽기) | ||
| CURRENT_UPSTREAM_URL=$(cat $APP_DIR/service-env.inc | grep -o 'http://[^;]*') | ||
| echo "Current Nginx upstream URL: $CURRENT_UPSTREAM_URL" | ||
| # 현재 서비스가 blue이면 타겟은 green, 아니면 blue | ||
| if [[ "$CURRENT_UPSTREAM_URL" == *"backend-blue"* ]]; then | ||
| CURRENT_SERVICE="backend-blue" | ||
| TARGET_SERVICE="backend-green" | ||
| TARGET_PORT="8081" | ||
| TARGET_IMAGE_TAG="green" # docker-compose.yml에서 사용할 이미지 태그 | ||
| CURRENT_IMAGE_TAG="blue" | ||
| else | ||
| CURRENT_SERVICE="backend-green" | ||
| TARGET_SERVICE="backend-blue" | ||
| TARGET_PORT="8080" | ||
| TARGET_IMAGE_TAG="blue" | ||
| CURRENT_IMAGE_TAG="green" | ||
| fi | ||
| echo "Deployment Target Service: $TARGET_SERVICE (Port: $TARGET_PORT), Target Image Tag: $TARGET_IMAGE_TAG" | ||
| echo "Current Live Service: $CURRENT_SERVICE, Current Image Tag: $CURRENT_IMAGE_TAG" | ||
| # 2. 환경변수 파일 생성 (.env) - 애플리케이션 및 Compose에서 사용 | ||
| # GitHub Secrets 값을 EC2 서버의 .env 파일로 안전하게 전달 | ||
| # 주의: 이 파일은 보안상 중요하므로 접근 권한 관리가 필요하며, gitignore 처리 필수 | ||
| echo "Creating .env file in $APP_DIR..." | ||
| echo "RDS_ENDPOINT=${{ secrets.RDS_ENDPOINT }}" > $APP_DIR/.env | ||
| echo "RDS_PORT=${{ secrets.RDS_PORT }}" >> $APP_DIR/.env | ||
| echo "RDS_DATABASE=${{ secrets.RDS_DATABASE }}" >> $APP_DIR/.env | ||
| echo "RDS_USERNAME=${{ secrets.RDS_USERNAME }}" >> $APP_DIR/.env | ||
| echo "RDS_PASSWORD=${{ secrets.RDS_PASSWORD }}" >> $APP_DIR/.env | ||
| echo "ELASTICACHE_ENDPOINT=${{ secrets.ELASTICACHE_ENDPOINT }}" >> $APP_DIR/.env | ||
| echo "ELASTICACHE_PORT=${{ secrets.ELASTICACHE_PORT }}" >> $APP_DIR/.env | ||
| echo "JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }}" >> $APP_DIR/.env | ||
| echo "JWT_EXPIRATION_TIME=${{ secrets.JWT_EXPIRATION_TIME }}" >> $APP_DIR/.env | ||
| # docker-compose.yml에서 이미지 주소를 동적으로 사용하기 위한 변수 추가 | ||
| echo "ECR_REGISTRY=$ECR_REGISTRY" >> $APP_DIR/.env | ||
| echo "ECR_REPOSITORY=$ECR_REPOSITORY" >> $APP_DIR/.env | ||
| echo ".env file created successfully." | ||
| # 3. 최신 Docker 이미지 Pull 및 리태깅 | ||
| echo "Pulling new image: $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" | ||
| # ECR 로그인 (권한 만료 대비, aws-cli가 EC2에 설치되어 있어야 함) - 선택적 | ||
| # aws ecr get-login-password --region ${{ env.AWS_REGION }} | docker login --username AWS --password-stdin $ECR_REGISTRY | ||
| docker pull $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG | ||
| # docker-compose.yml에서 사용할 태그(blue 또는 green)로 다시 태그 지정 | ||
| echo "Tagging image as $ECR_REGISTRY/$ECR_REPOSITORY:$TARGET_IMAGE_TAG..." | ||
| docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:$TARGET_IMAGE_TAG | ||
| # 4. 새 버전 컨테이너 실행 (Target Service) | ||
| # docker-compose는 자동으로 .env 파일을 로드하여 환경 변수 설정 | ||
| # --env-file 옵션으로 명시적으로 지정하여 안정성 확보 | ||
| echo "Starting $TARGET_SERVICE container with image $ECR_REGISTRY/$ECR_REPOSITORY:$TARGET_IMAGE_TAG..." | ||
| # --no-deps: 의존성 관계에 있는 다른 서비스는 건드리지 않음 | ||
| # --remove-orphans: docker-compose 파일에서 제거된 서비스의 컨테이너 삭제 | ||
| docker-compose -f $APP_DIR/docker-compose-prod.yml --env-file $APP_DIR/.env up -d --no-deps --remove-orphans $TARGET_SERVICE | ||
| echo "$TARGET_SERVICE starting process initiated." | ||
| # 5. Health Check 대기 (매우 중요!) | ||
| echo "Waiting for $TARGET_SERVICE health check (Target Port: $TARGET_PORT)... Max wait 300 seconds." | ||
| # timeout 명령어로 전체 대기 시간 제한 (예: 5분) | ||
| # bash -c '...' : 내부 스크립트를 별도의 쉘에서 실행 | ||
| timeout 300s bash -c ' \ | ||
| HEALTH_CHECK_PASSED=false; \ | ||
| # 최대 30번 시도 (약 5분간 시도) | ||
| for i in {1..30}; do \ | ||
| # EC2 내부에서 localhost와 타겟 포트로 헬스체크 URL 호출 | ||
| # -s: silent 모드, -o /dev/null: 출력 버리기, -w "%{http_code}": HTTP 상태 코드만 출력 | ||
| response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$TARGET_PORT/actuator/health); \ | ||
| # 상태 코드가 200이면 성공 | ||
| if [ "$response" = "200" ]; then \ | ||
| echo "$TARGET_SERVICE is healthy! (Attempt $i)"; \ | ||
| HEALTH_CHECK_PASSED=true; \ | ||
| break; # 성공 시 루프 종료 | ||
| fi; \ | ||
| # 실패 시 로그 출력 후 10초 대기 | ||
| echo "Health check attempt $i failed (HTTP Code: $response). Retrying in 10 seconds..."; \ | ||
| sleep 10; \ | ||
| done; \ | ||
| # 루프 종료 후 성공 여부 확인 | ||
| if [ "$HEALTH_CHECK_PASSED" = false ]; then \ | ||
| echo "Health check failed for $TARGET_SERVICE after multiple attempts."; \ | ||
| exit 1; # 실패 시 스크립트 비정상 종료 (exit code 1) | ||
| fi \ | ||
| ' | ||
| HEALTH_CHECK_EXIT_CODE=$? # timeout 또는 내부 스크립트의 종료 코드 확인 | ||
| # 6. Health Check 실패 또는 타임아웃 시 롤백 | ||
| if [ "$HEALTH_CHECK_EXIT_CODE" -ne 0 ]; then | ||
| echo "Health check failed or timed out (Exit Code: $HEALTH_CHECK_EXIT_CODE). Rolling back deployment..." | ||
| # 실패한 타겟 서비스 컨테이너 중지 | ||
| docker-compose -f $APP_DIR/docker-compose-prod.yml stop $TARGET_SERVICE | ||
| # docker-compose -f $APP_DIR/docker-compose-prod.yml rm -f $TARGET_SERVICE # 컨테이너 삭제는 선택적 | ||
| # Optional: 실패 알림 전송 (예: Slack 웹훅 호출) | ||
| exit 1 # GitHub Actions 워크플로우를 실패 상태로 만듦 | ||
| fi | ||
| # --- Health Check 성공 시 계속 진행 --- | ||
| # 7. Nginx 트래픽 전환 | ||
| echo "Switching Nginx traffic to $TARGET_SERVICE..." | ||
| # service-env.inc 파일 내용을 타겟 서비스 URL로 변경 (sudo 필요) | ||
| echo "set \$service_url http://$TARGET_SERVICE:$TARGET_PORT;" | sudo tee $APP_DIR/service-env.inc | ||
| # Nginx 컨테이너 내부에서 nginx reload 명령어 실행 (설정 다시 로드) | ||
| docker exec nginx-proxy nginx -s reload | ||
| echo "Nginx reloaded. Traffic is now directed to $TARGET_SERVICE." | ||
| sleep 5 # 트래픽 전환 후 안정화 대기 시간 | ||
| # 8. 이전 버전 컨테이너 중지 (Current Service) | ||
| echo "Stopping old service: $CURRENT_SERVICE..." | ||
| docker-compose -f $APP_DIR/docker-compose-prod.yml stop $CURRENT_SERVICE | ||
| echo "$CURRENT_SERVICE stopped." | ||
| # 9. (선택 사항) 사용하지 않는 이전 버전 Docker 이미지 태그 삭제 | ||
| # echo "Removing old image tag if different: $ECR_REGISTRY/$ECR_REPOSITORY:$CURRENT_IMAGE_TAG" | ||
| # # 현재 태그와 새 태그가 다를 경우에만 삭제 시도 | ||
| # if [ "$CURRENT_IMAGE_TAG" != "$TARGET_IMAGE_TAG" ]; then | ||
| # docker rmi $ECR_REGISTRY/$ECR_REPOSITORY:$CURRENT_IMAGE_TAG || echo "Old image tag removal failed (maybe already removed or in use)." | ||
| # fi | ||
| # 10. (선택 사항, 주의!) 사용하지 않는 Docker 리소스 정리 | ||
| echo "Cleaning up unused Docker resources (dangling images)..." | ||
| docker image prune -af # 태그가 없는(dangling) 이미지 모두 삭제 | ||
| echo "Blue-Green Deployment to $TARGET_SERVICE Completed Successfully!" | ||
| # --- EC2 서버 내부에서 실행될 스크립트 종료 --- | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| # .github/workflows/ci.yml | ||
| name: CI - Build and Test | ||
|
|
||
| on: | ||
| pull_request: # Pull Request 이벤트 발생 시 | ||
| branches: [ main ] # main 브랜치를 대상으로 하는 PR | ||
| workflow_dispatch: # 수동 실행도 가능하게 | ||
|
|
||
| jobs: | ||
| build-test: | ||
| name: Build and Run Tests | ||
| runs-on: ubuntu-latest # 실행 환경 지정 | ||
|
|
||
| steps: | ||
| # 1. 코드 체크아웃: 리포지토리의 코드를 워크플로우 실행 환경으로 가져옵니다. | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| # 2. JDK 설정: 빌드 및 테스트에 사용할 Java 버전(17)을 설정합니다. | ||
| - name: Set up JDK 17 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: '17' | ||
| distribution: 'temurin' # Temurin JDK 사용 | ||
| cache: 'gradle' # Gradle 의존성 캐싱으로 빌드 시간 단축 | ||
|
|
||
| # 3. Gradle 설정: Gradle 빌드 환경을 설정합니다. | ||
| - name: Setup Gradle | ||
| uses: gradle/actions/setup-gradle@v3 | ||
| # with: | ||
| # gradle-version: wrapper # 프로젝트의 Gradle Wrapper 사용을 명시 (선택 사항) | ||
|
|
||
| # 4. gradlew 실행 권한 부여: Gradle Wrapper 스크립트에 실행 권한을 줍니다. | ||
| - name: Grant execute permission for gradlew | ||
| run: chmod +x ./gradlew | ||
| working-directory: ./backend # backend 디렉토리에서 실행 | ||
|
Comment on lines
+28
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion
- - name: Setup Gradle
- uses: gradle/actions/setup-gradle@v3
+ - name: Grant execute permission for gradlew
+ run: chmod +x ./gradlew
+ working-directory: ./backend
+
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v3
|
||
|
|
||
| # 5. Gradle 빌드 및 테스트: Gradle의 build 태스크를 실행합니다. 이 태스크는 컴파일, 테스트 실행 등을 포함합니다. | ||
| - name: Build and Test with Gradle | ||
| run: ./gradlew build # 'build' 태스크는 기본적으로 'test' 태스크를 포함하여 실행합니다. | ||
| working-directory: ./backend | ||
|
|
||
| # (선택 사항) Dockerfile 빌드 가능성 테스트 (실제 푸시는 하지 않음) | ||
| # - name: Build Docker image (test only) | ||
| # run: | | ||
| # # Dockerfile이 정상적으로 빌드되는지만 확인 | ||
| # docker build -t ci-build-test ./backend \ | ||
| # --build-arg JAR_FILE=build/libs/*.jar | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.ServletException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.http.HttpServletRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.http.HttpServletResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.http.MediaType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.authentication.AuthenticationServiceException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.authentication.BadCredentialsException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -25,6 +26,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class LoginFilter extends UsernamePasswordAuthenticationFilter { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final ObjectMapper mapper = new ObjectMapper(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -63,15 +65,26 @@ protected void successfulAuthentication(HttpServletRequest req, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FilterChain chain, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Authentication auth) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throws IOException, ServletException { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1. 인증 성공 후 사용자 정보 가져오기 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String username = auth.getName(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 반드시 UserDetails 로드해서 토큰 생성 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UserDetails user = userDetailsService.loadUserByUsername(username); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String jwt = jwtTokenUtil.generateToken(user); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenService.saveToken(jwt, user.getUsername()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 2. 새로운 JWT 토큰 생성 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String newJwt = jwtTokenUtil.generateToken(user); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 3. (추가) Redis에서 해당 사용자의 기존 토큰 삭제 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenService.removeTokenByUsername(user.getUsername()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 4. 새로운 토큰을 Redis에 저장 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenService.saveToken(newJwt, user.getUsername()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info("[LOGIN FILTER] Save {} Token..", user.getUsername()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 5. 응답 설정 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.setCharacterEncoding("UTF-8"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.addHeader("Authorization", "Bearer " + jwt); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.addHeader("Authorization", "Bearer " + newJwt); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| res.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mapper.writeValue(res.getWriter(), new AuthenticationResponse(jwt)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mapper.writeValue(res.getWriter(), new AuthenticationResponse(newJwt)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+68
to
88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SecurityContext 미설정으로 인증 정보 유실 가능
@@
- // 5. 응답 설정
+ // 5. SecurityContext 저장
+ SecurityContextHolder.getContext().setAuthentication(auth);
+
+ // 6. 응답 설정📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
YAML 구문 오류 수정 필요
with: { aws-access-key-id: ${ { secrets.AWS_ACCESS_KEY_ID } }, ... }부분에서${ { ... } }공백으로 인해 YAML 파싱 오류가 발생합니다. 아래처럼 블록 매핑으로 변경하거나${{ ... }}문법을 올바르게 사용하세요.🧰 Tools
🪛 actionlint (1.7.4)
92-92: could not parse as YAML: yaml: line 92: did not find expected ',' or '}'
(syntax-check)
🪛 YAMLlint (1.35.1)
[error] 93-93: syntax error: expected ',' or '}', but got '{'
(syntax)