From 0e565ce778200668681194947e19d8650c088756 Mon Sep 17 00:00:00 2001 From: tmin002 Date: Tue, 3 Feb 2026 02:02:41 +0900 Subject: [PATCH 1/5] Limit SSM deploy to spring-boot service Stop pulling/updating other services during SSM deploy so admin and infra containers are not touched. --- .github/workflows/ec2-deploy.yml | 2 +- deploy/README.md | 57 +++++++++++++++++++++ deploy/docker-compose.yml | 88 ++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 deploy/README.md create mode 100644 deploy/docker-compose.yml diff --git a/.github/workflows/ec2-deploy.yml b/.github/workflows/ec2-deploy.yml index a143a7b..a3506cc 100644 --- a/.github/workflows/ec2-deploy.yml +++ b/.github/workflows/ec2-deploy.yml @@ -76,7 +76,7 @@ jobs: COMMAND_ID=$(aws ssm send-command \ --instance-ids "${{ secrets.SSM_INSTANCE_ID }}" \ --document-name "AWS-RunShellScript" \ - --parameters "{\"commands\":[\"cd /apps/senifit && echo ${ENV_B64} | base64 -d > /apps/senifit/.env && export SPRING_BOOT_IMAGE=${SPRING_BOOT_IMAGE} && sudo docker compose pull && sudo docker compose up -d\"]}" \ + --parameters "{\"commands\":[\"cd /apps/senifit && echo ${ENV_B64} | base64 -d > /apps/senifit/.env && export SPRING_BOOT_IMAGE=${SPRING_BOOT_IMAGE} && sudo docker compose pull spring-boot && sudo docker compose up -d spring-boot\"]}" \ --comment "Deploy spring-boot image ${SPRING_BOOT_IMAGE}" \ --output text \ --query "Command.CommandId") diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..f3272b8 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,57 @@ +# Deploy (GHCR + AWS SSM) + +`main` 브랜치 push 시 GitHub Actions가 다음을 수행합니다. + +1. **Docker 빌드** → **ghcr.io 푸시** (태그: `commit SHA` + `latest`) +2. **AWS SSM**으로 대상 EC2에 명령 전송 → `/apps/senifit`에서 `SPRING_BOOT_IMAGE=ghcr.io/...:` export 후 `docker compose pull` & `up -d` + +실제 사용 중인 compose: [docker-compose.yml](./docker-compose.yml) +- **spring-boot** 서비스: `image: ${SPRING_BOOT_IMAGE}` → CI가 배포 시 `SPRING_BOOT_IMAGE=ghcr.io/:` 로 설정 +- **admin** 서비스: `image: ${ADMIN_BOOT_IMAGE}` → CI에서는 건드리지 않음 (서버 .env 값 유지) + +## GitHub Secrets + +| Secret | 설명 | +|--------|------| +| `AWS_ACCESS_KEY_ID` | SSM SendCommand 권한이 있는 IAM 키 | +| `AWS_SECRET_ACCESS_KEY` | 위 IAM 시크릿 | +| `AWS_REGION` | (선택) 리전. 없으면 `ap-northeast-2` | +| `SSM_INSTANCE_ID` | 배포 대상 EC2 인스턴스 ID (예: `i-0abc123...`) | + +## EC2(/apps/senifit) 준비 + +1. **SSM Agent** + EC2에 SSM Agent 설치·실행, 해당 인스턴스에 대한 IAM 역할(또는 사용 중인 키)에 `ssm:SendCommand` 등 필요 권한 부여. + +2. **Docker & Docker Compose** + 서버에 Docker, Docker Compose 설치. + +3. **docker-compose.yml** + `/apps/senifit`에는 이 디렉터리의 [docker-compose.yml](./docker-compose.yml)과 동일한 구성을 두고, 서버용 `.env`에 `SPRING_BOOT_IMAGE`, `ADMIN_BOOT_IMAGE` 등 필요한 변수를 설정. + CI 배포 시 **SPRING_BOOT_IMAGE**만 현재 커밋 SHA 이미지로 덮어서 `docker compose up -d` 합니다. + +4. **SSL keystore (spring-boot)** + `spring-boot` 컨테이너는 `/app/cert`에 keystore를 마운트하도록 구성되어 있습니다. + 서버에 `/apps/senifit/cert/keystore.p12`를 두고, `.env`에 아래 값을 설정하세요. + + ``` + SSL_ENABLED=true + SSL_KEYSTORE_PATH=/app/cert/keystore.p12 + SSL_KEYSTORE_PASSWORD=... + ``` + +5. **ghcr.io 비공개 이미지인 경우** + EC2에서 한 번만 로그인: + + ```bash + echo $GITHUB_PAT | docker login ghcr.io -u YOUR_GITHUB_USER --password-stdin + ``` + + (패키지 읽기 권한이 있는 PAT를 변수/시크릿으로 관리) + +## 이미지 주소 (spring-boot) + +- `ghcr.io//:` +- `ghcr.io//:latest` + +`/`는 해당 GitHub 저장소 이름(소문자)과 동일합니다. diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..04883c8 --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,88 @@ +services: + mysql: + image: mysql:8.0 + container_name: senifit-mysql + restart: unless-stopped + env_file: + - .env + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + TZ: Asia/Seoul + volumes: + - ./mysql/data:/var/lib/mysql + - ./mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro + - ./mysql/init:/docker-entrypoint-initdb.d:ro + ports: + - "3306:3306" + command: --default-authentication-plugin=mysql_native_password + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - senifit-network + + redis: + image: redis:7-alpine + container_name: senifit-redis + restart: unless-stopped + env_file: + - .env + volumes: + - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro + command: redis-server /usr/local/etc/redis/redis.conf + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "-a", "${REDIS_PASS}", "ping"] + interval: 10s + timeout: 3s + retries: 5 + networks: + - senifit-network + + spring-boot: + image: ${SPRING_BOOT_IMAGE} + container_name: senifit-backend + restart: unless-stopped + env_file: + - .env + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - ./cert:/app/cert:ro + - ./logs:/app/logs + ports: + - "8443:8443" + networks: + - senifit-network + admin: + image: ${ADMIN_BOOT_IMAGE} + container_name: senifit-admin + restart: unless-stopped + env_file: + - .env + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + environment: + SERVER_PORT: 8082 + volumes: + - ./admin/logs:/app/logs + ports: + - "8082:8082" + networks: + - senifit-network + +networks: + senifit-network: + driver: bridge From aad43bc8f183d6c083e3f087ab7e606f3d029a38 Mon Sep 17 00:00:00 2001 From: tmin002 Date: Tue, 3 Feb 2026 02:14:54 +0900 Subject: [PATCH 2/5] Update docker-compose log and keystore mounts Mount logs to /senifit paths and bind the keystore path into the container to match SSL configuration. --- deploy/docker-compose.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 04883c8..24de78c 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -58,7 +58,8 @@ services: condition: service_healthy volumes: - ./cert:/app/cert:ro - - ./logs:/app/logs + - ${SSL_KEYSTORE_PATH}:${SSL_KEYSTORE_PATH}:ro + - /senifit/logs/was:/app/logs ports: - "8443:8443" networks: @@ -77,7 +78,7 @@ services: environment: SERVER_PORT: 8082 volumes: - - ./admin/logs:/app/logs + - /senifit/logs/admin:/app/logs ports: - "8082:8082" networks: From e58f6cb8af09955cc230168c9d8fb80f53c6f316 Mon Sep 17 00:00:00 2001 From: tmin002 Date: Tue, 3 Feb 2026 03:03:24 +0900 Subject: [PATCH 3/5] Restart spring-boot on deploy Bring spring-boot down before pulling and starting to ensure the running container is replaced during deployment. --- .github/workflows/ec2-deploy.yml | 4 ++-- deploy/docker-compose.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ec2-deploy.yml b/.github/workflows/ec2-deploy.yml index a3506cc..58bcc8f 100644 --- a/.github/workflows/ec2-deploy.yml +++ b/.github/workflows/ec2-deploy.yml @@ -76,8 +76,8 @@ jobs: COMMAND_ID=$(aws ssm send-command \ --instance-ids "${{ secrets.SSM_INSTANCE_ID }}" \ --document-name "AWS-RunShellScript" \ - --parameters "{\"commands\":[\"cd /apps/senifit && echo ${ENV_B64} | base64 -d > /apps/senifit/.env && export SPRING_BOOT_IMAGE=${SPRING_BOOT_IMAGE} && sudo docker compose pull spring-boot && sudo docker compose up -d spring-boot\"]}" \ - --comment "Deploy spring-boot image ${SPRING_BOOT_IMAGE}" \ + --parameters "{\"commands\":[\"cd /apps/senifit && echo ${ENV_B64} | base64 -d > /apps/senifit/.env && export SPRING_BOOT_IMAGE=${SPRING_BOOT_IMAGE} && sudo docker compose down was && sudo docker compose pull was && sudo docker compose up -d was\"]}" \ + --comment "Deploy was image ${SPRING_BOOT_IMAGE}" \ --output text \ --query "Command.CommandId") echo "command_id=$COMMAND_ID" >> $GITHUB_OUTPUT diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 24de78c..e9c30fa 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -59,7 +59,7 @@ services: volumes: - ./cert:/app/cert:ro - ${SSL_KEYSTORE_PATH}:${SSL_KEYSTORE_PATH}:ro - - /senifit/logs/was:/app/logs + - /apps/senifit/logs/was:/app/logs ports: - "8443:8443" networks: @@ -78,7 +78,7 @@ services: environment: SERVER_PORT: 8082 volumes: - - /senifit/logs/admin:/app/logs + - /apps/senifit/logs/admin:/app/logs ports: - "8082:8082" networks: From 7603813022b27f2673c143e4fdab0c8d2c1d27ad Mon Sep 17 00:00:00 2001 From: tmin002 Date: Tue, 3 Feb 2026 03:26:30 +0900 Subject: [PATCH 4/5] Run infra-aware restart script on deploy Ship and execute a script that restarts all services when mysql/redis are down, otherwise only refresh spring-boot. --- .github/workflows/ec2-deploy.yml | 3 ++- deploy/restart_if_infra_down.sh | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 deploy/restart_if_infra_down.sh diff --git a/.github/workflows/ec2-deploy.yml b/.github/workflows/ec2-deploy.yml index 58bcc8f..5829f6f 100644 --- a/.github/workflows/ec2-deploy.yml +++ b/.github/workflows/ec2-deploy.yml @@ -73,10 +73,11 @@ jobs: REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') SPRING_BOOT_IMAGE="ghcr.io/${REPO_LOWER}:${{ github.sha }}" ENV_B64=$(printf '%s' "${{ secrets.ENV_FILE }}" | base64 -w 0) + SCRIPT_B64=$(base64 -w 0 deploy/restart_if_infra_down.sh) COMMAND_ID=$(aws ssm send-command \ --instance-ids "${{ secrets.SSM_INSTANCE_ID }}" \ --document-name "AWS-RunShellScript" \ - --parameters "{\"commands\":[\"cd /apps/senifit && echo ${ENV_B64} | base64 -d > /apps/senifit/.env && export SPRING_BOOT_IMAGE=${SPRING_BOOT_IMAGE} && sudo docker compose down was && sudo docker compose pull was && sudo docker compose up -d was\"]}" \ + --parameters "{\"commands\":[\"cd /apps/senifit && echo ${ENV_B64} | base64 -d > /apps/senifit/.env && echo ${SCRIPT_B64} | base64 -d > /apps/senifit/restart_if_infra_down.sh && chmod +x /apps/senifit/restart_if_infra_down.sh && export SPRING_BOOT_IMAGE=${SPRING_BOOT_IMAGE} && sudo /apps/senifit/restart_if_infra_down.sh\"]}" \ --comment "Deploy was image ${SPRING_BOOT_IMAGE}" \ --output text \ --query "Command.CommandId") diff --git a/deploy/restart_if_infra_down.sh b/deploy/restart_if_infra_down.sh new file mode 100644 index 0000000..249d308 --- /dev/null +++ b/deploy/restart_if_infra_down.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Usage: run inside /apps/senifit (docker-compose.yml 위치) + +infra_down=0 + +check_service_running() { + local service="$1" + local cid + cid="$(docker compose ps -q "$service" || true)" + if [[ -z "$cid" ]]; then + return 1 + fi + local status + status="$(docker inspect -f '{{.State.Status}}' "$cid" 2>/dev/null || true)" + [[ "$status" == "running" ]] +} + +if ! check_service_running mysql; then + infra_down=1 +fi + +if ! check_service_running redis; then + infra_down=1 +fi + +if [[ "$infra_down" -eq 1 ]]; then + echo "[INFO] mysql/redis 중 하나 이상 down -> 전체 재시작" + docker compose down + docker compose up -d --build +else + echo "[INFO] mysql/redis 정상 -> 기존 방식 (spring-boot만 갱신)" + docker compose down spring-boot + docker compose pull spring-boot + docker compose up -d spring-boot +fi From e187dc5f5afa799a8bc178d766d3853ee886359c Mon Sep 17 00:00:00 2001 From: tmin002 Date: Tue, 3 Feb 2026 03:33:06 +0900 Subject: [PATCH 5/5] Use server-managed restart script Remove deployment-time script upload and run the existing restart script on the server instead. --- .github/workflows/ec2-deploy.yml | 3 +- deploy/README.md | 57 ------------ deploy/docker-compose.yml | 89 ------------------- deploy/restart_if_infra_down.sh | 37 -------- .../resources/application-dev-local-only.yml | 62 +++++++++++++ .../security/SessionAuthenticationTest.java | 78 ++++++++++++++++ 6 files changed, 141 insertions(+), 185 deletions(-) delete mode 100644 deploy/README.md delete mode 100644 deploy/docker-compose.yml delete mode 100644 deploy/restart_if_infra_down.sh create mode 100644 was/src/main/resources/application-dev-local-only.yml create mode 100644 was/src/test/java/com/senifit/was/security/SessionAuthenticationTest.java diff --git a/.github/workflows/ec2-deploy.yml b/.github/workflows/ec2-deploy.yml index 5829f6f..40623df 100644 --- a/.github/workflows/ec2-deploy.yml +++ b/.github/workflows/ec2-deploy.yml @@ -73,11 +73,10 @@ jobs: REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') SPRING_BOOT_IMAGE="ghcr.io/${REPO_LOWER}:${{ github.sha }}" ENV_B64=$(printf '%s' "${{ secrets.ENV_FILE }}" | base64 -w 0) - SCRIPT_B64=$(base64 -w 0 deploy/restart_if_infra_down.sh) COMMAND_ID=$(aws ssm send-command \ --instance-ids "${{ secrets.SSM_INSTANCE_ID }}" \ --document-name "AWS-RunShellScript" \ - --parameters "{\"commands\":[\"cd /apps/senifit && echo ${ENV_B64} | base64 -d > /apps/senifit/.env && echo ${SCRIPT_B64} | base64 -d > /apps/senifit/restart_if_infra_down.sh && chmod +x /apps/senifit/restart_if_infra_down.sh && export SPRING_BOOT_IMAGE=${SPRING_BOOT_IMAGE} && sudo /apps/senifit/restart_if_infra_down.sh\"]}" \ + --parameters "{\"commands\":[\"cd /apps/senifit && echo ${ENV_B64} | base64 -d > /apps/senifit/.env && export SPRING_BOOT_IMAGE=${SPRING_BOOT_IMAGE} && sudo /apps/senifit/restart_if_infra_down.sh\"]}" \ --comment "Deploy was image ${SPRING_BOOT_IMAGE}" \ --output text \ --query "Command.CommandId") diff --git a/deploy/README.md b/deploy/README.md deleted file mode 100644 index f3272b8..0000000 --- a/deploy/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# Deploy (GHCR + AWS SSM) - -`main` 브랜치 push 시 GitHub Actions가 다음을 수행합니다. - -1. **Docker 빌드** → **ghcr.io 푸시** (태그: `commit SHA` + `latest`) -2. **AWS SSM**으로 대상 EC2에 명령 전송 → `/apps/senifit`에서 `SPRING_BOOT_IMAGE=ghcr.io/...:` export 후 `docker compose pull` & `up -d` - -실제 사용 중인 compose: [docker-compose.yml](./docker-compose.yml) -- **spring-boot** 서비스: `image: ${SPRING_BOOT_IMAGE}` → CI가 배포 시 `SPRING_BOOT_IMAGE=ghcr.io/:` 로 설정 -- **admin** 서비스: `image: ${ADMIN_BOOT_IMAGE}` → CI에서는 건드리지 않음 (서버 .env 값 유지) - -## GitHub Secrets - -| Secret | 설명 | -|--------|------| -| `AWS_ACCESS_KEY_ID` | SSM SendCommand 권한이 있는 IAM 키 | -| `AWS_SECRET_ACCESS_KEY` | 위 IAM 시크릿 | -| `AWS_REGION` | (선택) 리전. 없으면 `ap-northeast-2` | -| `SSM_INSTANCE_ID` | 배포 대상 EC2 인스턴스 ID (예: `i-0abc123...`) | - -## EC2(/apps/senifit) 준비 - -1. **SSM Agent** - EC2에 SSM Agent 설치·실행, 해당 인스턴스에 대한 IAM 역할(또는 사용 중인 키)에 `ssm:SendCommand` 등 필요 권한 부여. - -2. **Docker & Docker Compose** - 서버에 Docker, Docker Compose 설치. - -3. **docker-compose.yml** - `/apps/senifit`에는 이 디렉터리의 [docker-compose.yml](./docker-compose.yml)과 동일한 구성을 두고, 서버용 `.env`에 `SPRING_BOOT_IMAGE`, `ADMIN_BOOT_IMAGE` 등 필요한 변수를 설정. - CI 배포 시 **SPRING_BOOT_IMAGE**만 현재 커밋 SHA 이미지로 덮어서 `docker compose up -d` 합니다. - -4. **SSL keystore (spring-boot)** - `spring-boot` 컨테이너는 `/app/cert`에 keystore를 마운트하도록 구성되어 있습니다. - 서버에 `/apps/senifit/cert/keystore.p12`를 두고, `.env`에 아래 값을 설정하세요. - - ``` - SSL_ENABLED=true - SSL_KEYSTORE_PATH=/app/cert/keystore.p12 - SSL_KEYSTORE_PASSWORD=... - ``` - -5. **ghcr.io 비공개 이미지인 경우** - EC2에서 한 번만 로그인: - - ```bash - echo $GITHUB_PAT | docker login ghcr.io -u YOUR_GITHUB_USER --password-stdin - ``` - - (패키지 읽기 권한이 있는 PAT를 변수/시크릿으로 관리) - -## 이미지 주소 (spring-boot) - -- `ghcr.io//:` -- `ghcr.io//:latest` - -`/`는 해당 GitHub 저장소 이름(소문자)과 동일합니다. diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml deleted file mode 100644 index e9c30fa..0000000 --- a/deploy/docker-compose.yml +++ /dev/null @@ -1,89 +0,0 @@ -services: - mysql: - image: mysql:8.0 - container_name: senifit-mysql - restart: unless-stopped - env_file: - - .env - environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE: ${MYSQL_DATABASE} - MYSQL_USER: ${MYSQL_USER} - MYSQL_PASSWORD: ${MYSQL_PASSWORD} - TZ: Asia/Seoul - volumes: - - ./mysql/data:/var/lib/mysql - - ./mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro - - ./mysql/init:/docker-entrypoint-initdb.d:ro - ports: - - "3306:3306" - command: --default-authentication-plugin=mysql_native_password - healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] - interval: 10s - timeout: 5s - retries: 5 - networks: - - senifit-network - - redis: - image: redis:7-alpine - container_name: senifit-redis - restart: unless-stopped - env_file: - - .env - volumes: - - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro - command: redis-server /usr/local/etc/redis/redis.conf - ports: - - "6379:6379" - healthcheck: - test: ["CMD", "redis-cli", "-a", "${REDIS_PASS}", "ping"] - interval: 10s - timeout: 3s - retries: 5 - networks: - - senifit-network - - spring-boot: - image: ${SPRING_BOOT_IMAGE} - container_name: senifit-backend - restart: unless-stopped - env_file: - - .env - depends_on: - mysql: - condition: service_healthy - redis: - condition: service_healthy - volumes: - - ./cert:/app/cert:ro - - ${SSL_KEYSTORE_PATH}:${SSL_KEYSTORE_PATH}:ro - - /apps/senifit/logs/was:/app/logs - ports: - - "8443:8443" - networks: - - senifit-network - admin: - image: ${ADMIN_BOOT_IMAGE} - container_name: senifit-admin - restart: unless-stopped - env_file: - - .env - depends_on: - mysql: - condition: service_healthy - redis: - condition: service_healthy - environment: - SERVER_PORT: 8082 - volumes: - - /apps/senifit/logs/admin:/app/logs - ports: - - "8082:8082" - networks: - - senifit-network - -networks: - senifit-network: - driver: bridge diff --git a/deploy/restart_if_infra_down.sh b/deploy/restart_if_infra_down.sh deleted file mode 100644 index 249d308..0000000 --- a/deploy/restart_if_infra_down.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Usage: run inside /apps/senifit (docker-compose.yml 위치) - -infra_down=0 - -check_service_running() { - local service="$1" - local cid - cid="$(docker compose ps -q "$service" || true)" - if [[ -z "$cid" ]]; then - return 1 - fi - local status - status="$(docker inspect -f '{{.State.Status}}' "$cid" 2>/dev/null || true)" - [[ "$status" == "running" ]] -} - -if ! check_service_running mysql; then - infra_down=1 -fi - -if ! check_service_running redis; then - infra_down=1 -fi - -if [[ "$infra_down" -eq 1 ]]; then - echo "[INFO] mysql/redis 중 하나 이상 down -> 전체 재시작" - docker compose down - docker compose up -d --build -else - echo "[INFO] mysql/redis 정상 -> 기존 방식 (spring-boot만 갱신)" - docker compose down spring-boot - docker compose pull spring-boot - docker compose up -d spring-boot -fi diff --git a/was/src/main/resources/application-dev-local-only.yml b/was/src/main/resources/application-dev-local-only.yml new file mode 100644 index 0000000..284af46 --- /dev/null +++ b/was/src/main/resources/application-dev-local-only.yml @@ -0,0 +1,62 @@ +# 로컬 Docker 개발 환경 설정 +# 사용법: ./gradlew bootRun --args='--spring.profiles.active=dev-local' +# 사전 조건: cd src/dev && docker compose up -d + +server: + port: 8443 + ssl: + enabled: true + key-store: classpath:cert/localhost.p12 + key-store-type: PKCS12 + key-store-password: '!senifit0527@' + key-alias: localhost + +spring: + datasource: + url: jdbc:mysql://localhost:3306/dev?rewriteBatchedStatements=true + driver-class-name: com.mysql.cj.jdbc.Driver + username: senifit + password: '!senifiT2025' + pbkdf2_pepper: 8ZvnK2f7CYVU/UEXT55uE1VeVdbzqGm7okaUwmOZaUIc6Sb/Cv310TI3GFRz+CqB7GbgCzM/EPskIqoP+OwK7A== + + jpa: + database: mysql + hibernate: + ddl-auto: none + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.MySQL8Dialect + jdbc: + batch_size: 20 + order_inserts: true + order_updates: true + + data: + redis: + host: localhost + port: 6379 + password: '!senifiT2025' + + session: + store-type: redis + timeout: 5400s + +app: + s3: + region: ap-northeast-2 + bucket: senifit-program-bk + video-prefix: video/ + thumb-prefix: thumb/ + presign: + expiry-seconds: 7200 + content-disposition: inline + force-content-type: false + +logging: + level: + root: info + com.senifit: debug + org.hibernate.SQL: debug + org.hibernate.type.descriptor.sql.BasicBinder: trace diff --git a/was/src/test/java/com/senifit/was/security/SessionAuthenticationTest.java b/was/src/test/java/com/senifit/was/security/SessionAuthenticationTest.java new file mode 100644 index 0000000..9c26f36 --- /dev/null +++ b/was/src/test/java/com/senifit/was/security/SessionAuthenticationTest.java @@ -0,0 +1,78 @@ +package com.senifit.was.security; + +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.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * 세션 기반 인증 테스트 + * + * 이 테스트는 백엔드의 세션 관리가 정상 동작하는지 검증합니다. + * - 로그인 후 세션 쿠키가 유지되는지 + * - 세션을 통한 인증된 요청이 정상 처리되는지 + * - 세션 없이 요청하면 401/403이 반환되는지 + */ +@SpringBootTest +@AutoConfigureMockMvc +class SessionAuthenticationTest { + + @Autowired + private MockMvc mockMvc; + + @Test + @DisplayName("인증 없이 보호된 API 호출 시 403 반환") + void unauthenticatedRequest_shouldReturn403() throws Exception { + mockMvc.perform(get("/center")) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("로그인 후 세션으로 인증된 API 호출 성공") + void authenticatedRequest_withSession_shouldSucceed() throws Exception { + // 1. 로그인 수행 (테스트용 계정 필요) + // 실제 테스트 시에는 테스트용 계정 정보로 변경 필요 + MvcResult loginResult = mockMvc.perform( + formLogin("/auth/signin") + .user("id", "test_account") // 실제 테스트 계정으로 변경 + .password("password", "test_password") // 실제 비밀번호로 변경 + ).andReturn(); + + // 2. 로그인 응답에서 세션 추출 + MockHttpSession session = (MockHttpSession) loginResult.getRequest().getSession(); + + // 3. 세션을 포함하여 보호된 API 호출 + if (loginResult.getResponse().getStatus() == 200) { + mockMvc.perform(get("/center").session(session)) + .andExpect(status().isOk()); + } + } + + @Test + @DisplayName("세션 없이 요청 → 새 세션으로 요청: 403 반환") + void requestWithoutSessionCookie_shouldReturn403() throws Exception { + // 첫 번째 요청 (세션 없음) + MvcResult result1 = mockMvc.perform(get("/center")) + .andExpect(status().isForbidden()) + .andReturn(); + + // 두 번째 요청 (다른 세션) + mockMvc.perform(get("/center")) + .andExpect(status().isForbidden()); + } + + @Test + @DisplayName("로그인 엔드포인트는 인증 없이 접근 가능") + void loginEndpoint_shouldBeAccessible() throws Exception { + mockMvc.perform(get("/health")) + .andExpect(status().isOk()); + } +}