diff --git a/.github/workflows/ec2-deploy.yml b/.github/workflows/ec2-deploy.yml index 3e3c8fb..40623df 100644 --- a/.github/workflows/ec2-deploy.yml +++ b/.github/workflows/ec2-deploy.yml @@ -72,13 +72,12 @@ jobs: ORG_LOWER="team-senifit" REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') SPRING_BOOT_IMAGE="ghcr.io/${REPO_LOWER}:${{ github.sha }}" - ADMIN_BOOT_IMAGE="ghcr.io/${ORG_LOWER}/senifit-admin:${{ github.sha }}" ENV_B64=$(printf '%s' "${{ secrets.ENV_FILE }}" | base64 -w 0) 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}\",\"export ADMIN_BOOT_IMAGE=${ADMIN_BOOT_IMAGE}\",\"docker compose pull\",\"docker compose up -d\"]}" \ - --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 /apps/senifit/restart_if_infra_down.sh\"]}" \ + --comment "Deploy was image ${SPRING_BOOT_IMAGE}" \ --output text \ --query "Command.CommandId") echo "command_id=$COMMAND_ID" >> $GITHUB_OUTPUT 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()); + } +}