Skip to content

feat: [3/3] 임시 Basic 인증 게이트 + EC2 배포 인프라 - #126

Merged
sevineleven merged 8 commits into
devfrom
feat/122-basic-auth-gate
Aug 1, 2026
Merged

feat: [3/3] 임시 Basic 인증 게이트 + EC2 배포 인프라#126
sevineleven merged 8 commits into
devfrom
feat/122-basic-auth-gate

Conversation

@sevineleven

@sevineleven sevineleven commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Situation

인스턴스 8080 을 0.0.0.0 으로 여는데 SecurityConfig전체 permitAll 이었다.

.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());   // MVP 임시

누구나 우리 서버를 경유해 우리 외부 API 키를 태울 수 있는 상태였다. 한도를 전부 실측해보니 여유가 없었다.

외부 API 일일 한도
TMAP 경유지 최적화 50
에어코리아 500
TourAPI · 관광빅데이터 각 1,000
특일정보 · TAGO(정류소·도착·열차) · 기상청 각 10,000

TMAP 50 은 봇 한 마리가 몇 초면 고갈시킨다. #110 에서 테스트가 조용히 80% 를 태운 것이 바로 이 한도다.

보안그룹을 조회해보니 8080 인바운드가 이미 0.0.0.0/0 이었다. 앱을 띄우는 순간 전 세계에 노출된다.

Task

FE 에 로그인 화면이 없고, 소셜 로그인은 provider 클라이언트 ID 가 없어 붙일 수 없다(#93 이 draft 인 이유). 화면 없이 성립하는 최소 인증이 필요했다.

앱이 하는 일 브라우저(Swagger) 왜 이렇게 판단했나
HTTP Basic 헤더 하나 기본 인증 팝업 채택 — 로그인 페이지·토큰 저장·만료 관리가 전부 불필요하고, #93 머지 시 걷어내기 쉽다
formLogin 세션 쿠키 관리 자동 생성 로그인 페이지 앱 클라이언트가 쿠키를 다뤄야 해 번거롭다
JSON 로그인 + JWT 토큰 저장·갱신 토큰을 복사해 붙여야 함 #93 이 이미 제대로 된 것을 갖고 있어, 두 달 뒤 버릴 코드를 두 번 만드는 셈

임시 장치는 제거 비용이 낮은 게 중요하다는 판단이 선택을 갈랐다.

Action

인증 게이트

  • HTTP Basic + stateless. 매 요청이 자격증명을 들고 오므로 세션이 필요 없고, 없어야 앱이 쿠키를 관리하지 않는다.
  • 401 을 공통 응답 래퍼에 맞췄다. Security 기본 401 은 필터 레벨이라 @RestControllerAdvice 를 타지 않아 본문이 빈다 — 그대로 두면 클라이언트가 파싱에 실패한다.
  • 실패 사유(비밀번호 틀림·계정 없음)는 응답에 담지 않는다. 계정 존재 여부를 알려주는 셈이 된다.

계정 값이 새는 자리를 막았다

  • 프로파일 파일에 계정을 하드코딩하지 않고 ${OFFWAY_BASIC_USERNAME:dev} 로 뒀다. 하드코딩하면 프로파일 파일이 환경변수를 이겨, local 로 띄운 인스턴스가 dev/dev 로 고정된다 — 외부에 열면 누구나 아는 계정이 된다. 실제로 부팅해 환경변수가 이기는 것을 확인했다.
  • 값이 비면 부팅을 막는다. 외부 API 키는 없어도 그 호출만 죽지만, 인증 계정이 비면 서버가 통째로 열린 채 뜬다. 조용히 뜨는 쪽이 훨씬 위험하다.
  • 비밀번호는 인코더 접두어를 포함한다({noop}평문 · {bcrypt}해시). local 평문과 운영 해시가 같은 설정 키를 쓴다.

배포 인프라

  • EC2 + Docker + MySQL, dev 머지 자동 배포를 목표로 하되 첫 배포는 수동 실행만 열어뒀다. Flyway 마이그레이션 14개가 빈 MySQL 에 처음 적용되는 순간이고, 그때까지 H2 로만 검증된 상태다.
  • 보안그룹의 22번이 특정 IP 하나만 열려 있어 Actions 러너가 붙을 수 없었다. 상시 개방하는 대신 실행마다 러너 IP 만 열고 끝나면(실패해도) 회수한다.
  • 배포 전용 IAM 사용자를 만들어 보안그룹 규칙 추가·제거 권한만 줬다. 인스턴스 조회조차 UnauthorizedOperation 인 것을 확인했다.
  • 환경변수는 --env-file 로 넘긴다. docker run -e 로 나열하면 서버에서 ps 만 쳐도 DB 비밀번호와 API 키가 보인다.
  • MySQL 은 같은 도커 네트워크에 두고 3306 을 호스트로 노출하지 않는다. 외부에서 DB 에 직접 붙을 수 없다.
  • 컨테이너 TZ 를 Asia/Seoul 로 고정했다. 연차·D-day·"오늘자" 판정이 전부 KST 기준이라 UTC 면 하루가 어긋난다.
  • 기동 확인에서 401 도 성공으로 본다 — 인증 게이트가 살아 있다는 뜻이라 200 과 똑같이 정상이다.

테스트

  • 통합 테스트 18개 클래스에 @WithMockUser. 동시성 테스트 3곳은 별도 스레드라 SecurityContext 가 전파되지 않아 httpBasic 을 명시했다.
  • "계정이 비면 부팅 실패" 는 prod 로 띄워 확인하려 했으나 DB 드라이버에서 먼저 죽어 계정 검증까지 닿지 못했다. 통합으로는 이 불변식만 겨눌 수 없어 단위 테스트로 내렸다 — 공백만 든 환경변수는 "설정했다" 는 착각을 주기 쉬워 빈 값과 같이 막는다.

Result

  • 로컬 기동으로 확인한 것: 무인증 401(래퍼 JSON) · 잘못된 계정 401 · 올바른 계정 200 · 환경변수가 프로파일 기본값을 덮음 · bcrypt 해시 계정 로그인.
  • 아직 배포되지 않았다. 순서는 인프라 → 시큐리티 → 배포 테스트이고, 이 PR 은 두 번째까지다. 배포는 워크플로우를 수동 실행해 검증한 뒤 push 트리거 주석을 푼다.
  • 8080 평문 HTTP 라 Basic 자격증명이 요청마다 네트워크에 그대로 흐른다. 팀 내부용이라 감수하지만, 외부에 더 열게 되면 HTTPS 가 먼저다.
  • 후속: 외부 API 오늘자 잔여 호출량 조회(외부 API 오늘자(KST) 잔여 호출량 조회 #123). 인증으로 외부인은 막지만 팀이 개발하며 태우는 건 막을 수 없어, 막는 대신 보이게 한다.

연관 이슈

Summary by CodeRabbit

  • 새로운 기능

    • 모든 API 요청에 HTTP Basic 인증을 적용했습니다.
    • 인증되지 않은 요청에 일관된 JSON 형식의 401 응답을 제공합니다.
    • 운영 환경에서 인증 정보와 데이터베이스 설정을 환경변수로 지정할 수 있습니다.
    • Docker 기반 애플리케이션 실행 및 EC2 배포를 지원합니다.
  • 문서

    • 인증 방식, 기본 로컬 계정, 운영 환경 설정 및 오류 응답 형식을 README에 추가했습니다.
  • 테스트

    • 인증 성공·실패 및 잘못된 자격증명 처리를 검증하는 테스트를 추가했습니다.
    • 기존 통합 테스트가 인증된 환경에서 실행되도록 보완했습니다.

- 8080 을 0.0.0.0 으로 여는데 SecurityConfig 가 전체 permitAll 이라, 누구나 우리 서버를
  경유해 외부 API 키를 태울 수 있었다. TMAP 경유지 최적화는 하루 50건이라 봇 한 마리로
  고갈된다 — #110 에서 테스트가 조용히 80% 를 태운 그 한도다.
- HTTP Basic 을 골랐다. FE 에 로그인 화면이 없고 provider 클라이언트 ID 가 없어 소셜
  로그인을 붙일 수 없는데(#93 이 draft 인 이유), Basic 은 로그인 페이지·토큰 저장·만료
  관리가 전부 필요 없다. 앱은 헤더 하나, 브라우저는 기본 팝업으로 통과한다. #93 이
  머지되면 통째로 걷어낸다 — 임시 장치는 제거 비용이 낮은 게 중요하다.
- 401 을 AuthenticationEntryPoint 로 감쌌다. Security 기본 401 은 필터 레벨이라
  @RestControllerAdvice 를 타지 않아 본문이 비고, 그대로 두면 클라이언트가 파싱에 실패한다.
- 계정 값을 프로파일 파일에 하드코딩하지 않고 ${OFFWAY_BASIC_USERNAME:dev} 로 뒀다.
  하드코딩하면 프로파일 파일이 환경변수를 이겨, local 로 띄운 인스턴스가 dev/dev 로
  고정된다 — 외부에 열면 누구나 아는 계정이 된다. 실제로 부팅해 환경변수가 이기는 것을 확인했다.
- 값이 비면 부팅을 막는다. 외부 API 키는 없어도 그 호출만 죽지만, 인증 계정이 비면 서버가
  통째로 열린 채 뜬다 — 조용히 뜨는 쪽이 훨씬 위험하다.
- 통합 테스트 18개 클래스에 @WithMockUser. 동시성 테스트 3곳은 별도 스레드라
  SecurityContext 가 전파되지 않아 httpBasic 을 명시했다.
- dev 푸시마다 EC2 에 배포한다. CI 와 workflow_run 으로 엮지 않고 자체 빌드·테스트를 돌린다 —
  엮으면 실패 지점이 두 워크플로우에 흩어져 "왜 배포가 안 됐는지" 추적이 어렵다.
- 이미지 레지스트리를 두지 않고 jar+Dockerfile 을 전송해 EC2 에서 빌드한다. GHCR 을 쓰면
  EC2 에 레지스트리 자격증명을 하나 더 심어야 하는데, 팀 내부 규모에서 그 대가가 이득보다 크다.
- 환경변수는 --env-file 로 넘긴다. docker run -e 로 나열하면 서버에서 ps 만 쳐도 DB 비밀번호와
  API 키가 그대로 보인다. 파일은 600 으로 두고 러너 쪽 사본은 즉시 지운다.
- 배포 워크플로우에 외부 API 키를 넘기되 **테스트 단계와 분리**했다 — 테스트가 실키로 외부를
  호출해 일일 허용량을 태운 적이 있다(#110).
- 기동 확인에서 401 도 성공으로 본다. 인증 게이트가 살아 있다는 뜻이라 200 과 똑같이 정상이다.
  컨테이너가 떴다고 앱이 뜬 게 아니라서(DB 접속·마이그레이션 실패는 기동 중에 죽는다) 이 단계를 둔다.
- 컨테이너 TZ 를 Asia/Seoul 로 고정했다. 연차·D-day·"오늘자" 판정이 전부 KST 기준이라
  컨테이너가 UTC 면 하루가 어긋난다.
- prod 프로파일은 DB 값에 기본값을 두지 않아 미설정이면 부팅이 실패한다. 잘못된 DB 로 조용히
  뜨는 것보다 낫다. H2 콘솔은 끈다 — 운영에 열면 DB 를 브라우저로 그대로 노출한다.
- 첫 배포는 사람이 지켜보는 앞에서 돌아야 해 push 트리거를 주석으로 내렸다. Flyway 마이그레이션
  14개가 빈 MySQL 에 처음 적용되는 순간인데 그때까지 H2 로만 검증된 상태다. 첫 배포가 확인되면
  주석을 풀어 자동 배포로 전환한다.
- 보안그룹을 실제로 조회해보니 22번이 특정 IP 하나(221.151.88.172/32)만 열려 있었다. Actions
  러너는 매번 다른 IP 라 그대로면 SSH 가 timeout 으로 죽는다. 22번을 상시 개방하는 대신 실행마다
  러너 IP 만 열고 끝나면(실패해도) 회수한다 — if: always() 로 걸어 규칙이 남아 영구 개방되는 것을 막는다.
- MySQL 을 같은 도커 네트워크에 두고 3306 을 호스트로 노출하지 않는다. 외부에서 DB 에 직접 붙을 수
  없고, 그래서 DB_URL 의 호스트가 컨테이너명(offway-mysql)이다.
- MySQL 이 떠 있지 않으면 앱을 띄우지 않고 중단한다. 띄워봐야 커넥션 실패로 죽는데, 그때는
  "왜 죽었는지" 가 앱 로그 깊숙이 묻힌다.
- 완료 기준의 "prod 에서 계정 미설정 시 부팅 실패" 를 확인하려 prod 프로파일로 띄워봤으나
  DB 드라이버에서 먼저 죽어 계정 검증까지 도달하지 못했다. 통합으로는 이 불변식만 따로
  겨눌 수 없어 단위 테스트로 내렸다.
- null·빈 문자열·공백을 모두 막는지 본다. 공백만 든 환경변수는 "설정했다" 는 착각을 주기 쉬운
  실수라 빈 값과 같이 취급한다.
@sevineleven sevineleven added the feat 새 기능 (외부에 보이는 변화) label Aug 1, 2026
@sevineleven sevineleven linked an issue Aug 1, 2026 that may be closed by this pull request
4 tasks
@sevineleven sevineleven self-assigned this Aug 1, 2026
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sevineleven, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2bbb1a86-a483-4be1-81ff-f42be01803a5

📥 Commits

Reviewing files that changed from the base of the PR and between fafb8ad and e1d24bd.

📒 Files selected for processing (9)
  • .github/workflows/deploy.yml
  • Dockerfile
  • README.md
  • src/main/java/com/offway/core/user/config/ApiResponseAuthenticationEntryPoint.java
  • src/test/java/com/offway/core/itinerary/controller/CourseLeaveDeductionIntegrationTest.java
  • src/test/java/com/offway/core/itinerary/controller/CoursePlanManagementIntegrationTest.java
  • src/test/java/com/offway/core/itinerary/controller/CourseStorageIntegrationTest.java
  • src/test/java/com/offway/core/leave/controller/MyLeaveIntegrationTest.java
  • src/test/java/com/offway/core/user/controller/BasicAuthIntegrationTest.java
📝 Walkthrough

Walkthrough

전체 API에 HTTP Basic 인증을 적용하고 공통 401 응답을 추가했습니다. 인증 설정과 통합 테스트를 갱신했습니다. Java 25 기반 Docker 이미지를 만들고, GitHub Actions로 EC2 배포와 기동 검증을 자동화했습니다.

Changes

Basic 인증 및 EC2 배포

Layer / File(s) Summary
Basic 인증 계약과 보안 구성
src/main/java/com/offway/core/user/config/*, src/main/java/com/offway/core/common/exception/CommonErrorCode.java, src/main/resources/application*.properties, README.md
설정된 계정을 인메모리 사용자로 등록합니다. 모든 요청에 무상태 HTTP Basic 인증을 요구합니다. 인증 실패는 COMMON-401 공통 응답으로 반환합니다.
인증 계약 검증과 기존 테스트 전환
src/test/java/com/offway/core/user/*, src/test/java/com/offway/core/common/*, src/test/java/com/offway/core/itinerary/*, src/test/java/com/offway/core/leave/*, src/test/java/com/offway/core/policy/*, src/test/java/com/offway/core/trip/*, src/test/java/com/offway/core/weather/*
계정 설정의 필수 조건과 무인증·오답·정답 Basic 인증 응답을 검증합니다. 기존 통합 테스트에는 @WithMockUser 또는 명시적 Basic 인증을 적용합니다.
EC2 컨테이너 배포와 기동 확인
.github/workflows/deploy.yml, Dockerfile, src/main/resources/application-prod.properties
수동 워크플로우가 애플리케이션을 빌드하고 EC2에 배포합니다. Docker 컨테이너를 교체한 뒤 /api/v1/categories의 HTTP 401 또는 200 응답으로 기동을 확인합니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SecurityFilterChain
  participant UserDetailsService
  participant ApiResponseAuthenticationEntryPoint
  participant API
  Client->>SecurityFilterChain: HTTP Basic 요청
  SecurityFilterChain->>UserDetailsService: 자격증명 검증
  UserDetailsService-->>SecurityFilterChain: 인증 결과
  alt 인증 실패
    SecurityFilterChain->>ApiResponseAuthenticationEntryPoint: 401 처리
    ApiResponseAuthenticationEntryPoint-->>Client: ApiResponseBody.fail(UNAUTHORIZED)
  else 인증 성공
    SecurityFilterChain->>API: 요청 전달
    API-->>Client: API 응답
  end
Loading

Possibly related PRs

  • team-offway/core#88: 인증 적용으로 PoiAccessibilityIntegrationTest와 접근성 엔드포인트 테스트가 영향을 받습니다.
  • team-offway/core#124: 두 PR이 CoursePlanManagementIntegrationTest를 수정합니다.

Suggested labels: test

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning [#122]에 직접 정의되지 않은 EC2 배포 워크플로우, Dockerfile, MySQL 운영 설정이 함께 포함되어 있습니다. EC2·Docker·MySQL 배포 변경을 별도 PR로 분리하거나 #122의 범위에 명시하면 좋겠습니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 HTTP Basic 인증 게이트와 EC2 배포 인프라라는 주요 변경 사항을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed [#122] Stateless Basic 인증, 공통 401 응답, local 기본 계정, prod 설정 검증, 기존 테스트 보완 요구사항을 구현했습니다.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/122-basic-auth-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sevineleven

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
Dockerfile (1)

1-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

베이스 이미지 태그를 구체적인 버전으로 고정하면 배포 재현성이 좋아질 것 같습니다.

eclipse-temurin:25-jre는 마이너/패치 버전이 고정되지 않은 태그라서, 이미지가 새로 빌드될 때마다 조금씩 다른 패치 버전을 받아올 수 있습니다. 코드 변경 없이도 배포마다 JRE 버전이 달라질 수 있다는 뜻이라, 나중에 원인 파악이 어려운 문제가 생길 수 있습니다.

25.0.3_9-jre처럼 구체적인 버전으로 고정하고, 업그레이드가 필요할 때 의도적으로 갱신하는 방식을 제안드립니다. 지금 당장 급한 건 아니니 편하실 때 반영해 주시면 됩니다.

-FROM eclipse-temurin:25-jre
+FROM eclipse-temurin:25.0.3_9-jre
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 1 - 6, Update the Dockerfile’s FROM instruction to
use the specific eclipse-temurin JRE version 25.0.3_9-jre instead of the
floating 25-jre tag, keeping future runtime upgrades intentional.
.github/workflows/deploy.yml (2)

52-67: 🔒 Security & Privacy | 🔵 Trivial | 🏗️ Heavy lift

AWS 자격증명을 장기 액세스 키 대신 OIDC로 전환하는 방안도 검토해보면 좋겠습니다.

지금은 AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY를 GitHub Secrets에 장기 보관하는 방식을 쓰고 있습니다. 이 방식은 키가 유출되면 만료 전까지 계속 악용될 수 있다는 리스크가 있습니다. aws-actions/configure-aws-credentialsrole-to-assume와 GitHub OIDC 토큰을 함께 쓰는 방식을 지원하는데, 이 방식을 쓰면 저장소에 장기 키를 두지 않아도 되고 IAM 정책으로 세밀하게 권한을 제한하기도 더 쉽습니다.

지금 당장 급한 문제는 아니라서, 여유 있을 때 한번 살펴보시면 좋을 것 같습니다.

permissions:
  contents: read
  id-token: write

steps:
  - name: AWS 자격증명 설정
    uses: aws-actions/configure-aws-credentials@v5
    with:
      role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
      aws-region: ap-northeast-1

AWS 문서에서 GitHub Actions OIDC 연동에 대한 최신 가이드를 한번 확인해 주시면 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy.yml around lines 52 - 67, Update the AWS credential
setup step to use GitHub Actions OIDC with a restricted IAM role instead of
long-lived access-key secrets: add job-level permissions for contents: read and
id-token: write, replace the access-key fields in configure-aws-credentials with
role-to-assume referencing the deployment role ARN, and retain the existing
aws-region configuration.

107-136: 🩺 Stability & Availability | 🔵 Trivial

latest 태그와 즉시 컨테이너 교체 방식이라 실패 시 롤백 수단이 없다는 점을 참고해 주세요.

docker build -t offway-core:latest .로 매번 같은 태그를 덮어쓰고, 새 컨테이너를 띄우기 전에 기존 컨테이너를 docker rm -f로 먼저 지워버립니다. 이미지 빌드나 컨테이너 기동이 실패하면 이전에 동작하던 이미지도 이미 사라진 뒤라서, 수동으로 이전 버전을 다시 빌드하지 않는 한 되돌릴 방법이 없습니다.

지금은 첫 배포를 수동으로 지켜보는 단계라 큰 문제는 아니지만, 자동 배포로 전환하기 전에는 커밋 SHA 등으로 이미지 태그를 남기고, 기동 확인 실패 시 이전 이미지로 복귀하는 절차를 함께 마련해두면 안전할 것 같습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy.yml around lines 107 - 136, Update the deployment
flow around the docker build and offway-core replacement to tag each image with
an immutable commit SHA instead of relying only on offway-core:latest, retain
the previous image/container reference, and verify the newly started container
becomes healthy before removing the rollback path. If startup verification
fails, stop the failed container and restore the previous image/container; keep
latest only as an optional pointer after successful deployment.
src/main/java/com/offway/core/user/config/ApiResponseAuthenticationEntryPoint.java (1)

33-42: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

하나만 더 제안드릴게요. 지금은 인증 실패 시 로그가 전혀 안 남는데, 임시 게이트를 뚫으려는 시도(브루트포스 등)를 나중에 운영에서 파악하려면 사용자명·자격증명은 빼고 요청 경로·시간 정도만 warn 레벨로 남겨두면 도움이 될 것 같습니다. 지금 급하게 넣을 정도는 아니니 여유 될 때 검토해 보시면 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/offway/core/user/config/ApiResponseAuthenticationEntryPoint.java`
around lines 33 - 42, 인증 실패를 처리하는 commence 메서드에 warn 레벨 로깅을 추가해 요청 경로와 발생 시각을
기록하세요. 사용자명, 비밀번호 등 자격증명이나 계정 존재 여부를 드러내는 정보는 로그에 포함하지 말고, 기존 401 응답 생성 동작은 그대로
유지하세요.
README.md (1)

21-28: 🔒 Security & Privacy | 🔵 Trivial

외부 Basic 인증 경로의 TLS 보호를 확인하면 좋겠습니다.

제공된 PR 목표상 8080 포트가 외부에 노출됩니다. Basic 인증은 자격 증명을 Base64로만 인코딩하므로 평문 HTTP로 직접 노출하면 자격 증명이 탈취되고 재사용될 수 있습니다. EC2 앞단에서 HTTPS/TLS를 종료하거나 8080을 신뢰된 네트워크로 제한하고, 실제 외부 접속 방식을 문서에 명시하면 좋겠습니다. (rfc-editor.org)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 21 - 28, Update the API 인증 section in README.md to
document that external Basic 인증 traffic must be protected by HTTPS/TLS
termination at the EC2 front end, or restrict port 8080 to a trusted network;
clearly state the intended external access path while retaining the local
dev/dev usage example.

Source: MCP tools

src/main/resources/application.properties (1)

19-25: 🔒 Security & Privacy | 🔵 Trivial

배포 전 실행 시 프로필 누락을 실패시키면 좋겠습니다.

기본 프로파일이 local이라 SPRING_PROFILES_ACTIVE가 빠지면 적용은 가능하지만, application-local.properties의 알려진 계정인 dev/dev가 Basic 인증으로 선택됩니다. 운영 배포는 prod + 기본 계정 없이 실행되도록 보간 순서를 조정해 누락 시 기본값이 채우지 않게 하면 좋겠습니다. local은 로컬 실행성을 위해 기본값을 유지하되, 실행 환경에서 의도치 않게 선택되지 않는 경로만 확인하면 됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/application.properties` around lines 19 - 25, Adjust the
Basic authentication property resolution across application.properties and
application-local.properties: keep the local profile’s dev/dev defaults for
local usability, but ensure the production configuration does not inherit those
defaults when SPRING_PROFILES_ACTIVE or deployment credentials are missing.
Update the interpolation/profile precedence so production requires explicitly
supplied OFFWAY_BASIC_USERNAME and OFFWAY_BASIC_PASSWORD values and fails
startup rather than selecting the known local account.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy.yml:
- Around line 89-105: Update the env.prod heredoc in the workflow step “환경변수 파일
생성 (600)” to use a quoted heredoc delimiter, preserving GitHub Actions `${{
secrets.* }}` interpolation while preventing the shell from interpreting special
characters in substituted secret values.

In
`@src/main/java/com/offway/core/user/config/ApiResponseAuthenticationEntryPoint.java`:
- Around line 23-25: Update the class-level comment in
ApiResponseAuthenticationEntryPoint to match the actual SecurityConfig routing:
this entry point never adds WWW-Authenticate, so Swagger access does not trigger
a browser-native authentication popup. Document that users should provide
credentials through the URL or Swagger UI’s Authorize button, without changing
the authentication behavior.

In
`@src/test/java/com/offway/core/itinerary/controller/CourseStorageIntegrationTest.java`:
- Line 4: Update the concurrent deletion requests in
CourseStorageIntegrationTest to use request-scoped user("test") authentication
instead of httpBasic("dev", "dev"), and remove the now-unused httpBasic import.
Keep Basic credential validation covered by BasicAuthIntegrationTest.

---

Nitpick comments:
In @.github/workflows/deploy.yml:
- Around line 52-67: Update the AWS credential setup step to use GitHub Actions
OIDC with a restricted IAM role instead of long-lived access-key secrets: add
job-level permissions for contents: read and id-token: write, replace the
access-key fields in configure-aws-credentials with role-to-assume referencing
the deployment role ARN, and retain the existing aws-region configuration.
- Around line 107-136: Update the deployment flow around the docker build and
offway-core replacement to tag each image with an immutable commit SHA instead
of relying only on offway-core:latest, retain the previous image/container
reference, and verify the newly started container becomes healthy before
removing the rollback path. If startup verification fails, stop the failed
container and restore the previous image/container; keep latest only as an
optional pointer after successful deployment.

In `@Dockerfile`:
- Around line 1-6: Update the Dockerfile’s FROM instruction to use the specific
eclipse-temurin JRE version 25.0.3_9-jre instead of the floating 25-jre tag,
keeping future runtime upgrades intentional.

In `@README.md`:
- Around line 21-28: Update the API 인증 section in README.md to document that
external Basic 인증 traffic must be protected by HTTPS/TLS termination at the EC2
front end, or restrict port 8080 to a trusted network; clearly state the
intended external access path while retaining the local dev/dev usage example.

In
`@src/main/java/com/offway/core/user/config/ApiResponseAuthenticationEntryPoint.java`:
- Around line 33-42: 인증 실패를 처리하는 commence 메서드에 warn 레벨 로깅을 추가해 요청 경로와 발생 시각을
기록하세요. 사용자명, 비밀번호 등 자격증명이나 계정 존재 여부를 드러내는 정보는 로그에 포함하지 말고, 기존 401 응답 생성 동작은 그대로
유지하세요.

In `@src/main/resources/application.properties`:
- Around line 19-25: Adjust the Basic authentication property resolution across
application.properties and application-local.properties: keep the local
profile’s dev/dev defaults for local usability, but ensure the production
configuration does not inherit those defaults when SPRING_PROFILES_ACTIVE or
deployment credentials are missing. Update the interpolation/profile precedence
so production requires explicitly supplied OFFWAY_BASIC_USERNAME and
OFFWAY_BASIC_PASSWORD values and fails startup rather than selecting the known
local account.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c5588b4-9596-4aed-acd7-eaac3c064930

📥 Commits

Reviewing files that changed from the base of the PR and between 9fbed30 and fafb8ad.

📒 Files selected for processing (30)
  • .github/workflows/deploy.yml
  • Dockerfile
  • README.md
  • src/main/java/com/offway/core/common/exception/CommonErrorCode.java
  • src/main/java/com/offway/core/user/config/ApiResponseAuthenticationEntryPoint.java
  • src/main/java/com/offway/core/user/config/BasicAuthProperties.java
  • src/main/java/com/offway/core/user/config/SecurityConfig.java
  • src/main/resources/application-local.properties
  • src/main/resources/application-prod.properties
  • src/main/resources/application.properties
  • src/test/java/com/offway/core/common/OpenApiNullableSchemaIntegrationTest.java
  • src/test/java/com/offway/core/common/exception/GlobalExceptionHandlerIntegrationTest.java
  • src/test/java/com/offway/core/itinerary/controller/CourseGenerateIntegrationTest.java
  • src/test/java/com/offway/core/itinerary/controller/CourseLeaveDeductionIntegrationTest.java
  • src/test/java/com/offway/core/itinerary/controller/CoursePlanManagementIntegrationTest.java
  • src/test/java/com/offway/core/itinerary/controller/CourseRegenerateIntegrationTest.java
  • src/test/java/com/offway/core/itinerary/controller/CourseStorageIntegrationTest.java
  • src/test/java/com/offway/core/itinerary/controller/TripOutcomeIntegrationTest.java
  • src/test/java/com/offway/core/leave/controller/LeaveAvailableTimeIntegrationTest.java
  • src/test/java/com/offway/core/leave/controller/LeaveSandwichIntegrationTest.java
  • src/test/java/com/offway/core/leave/controller/MyLeaveIntegrationTest.java
  • src/test/java/com/offway/core/policy/controller/PolicyDetailIntegrationTest.java
  • src/test/java/com/offway/core/trip/controller/CategoryIntegrationTest.java
  • src/test/java/com/offway/core/trip/controller/HomeIntegrationTest.java
  • src/test/java/com/offway/core/trip/controller/PoiAccessibilityIntegrationTest.java
  • src/test/java/com/offway/core/trip/controller/PoiDetailIntegrationTest.java
  • src/test/java/com/offway/core/trip/controller/RegionRecommendIntegrationTest.java
  • src/test/java/com/offway/core/user/config/BasicAuthPropertiesTest.java
  • src/test/java/com/offway/core/user/controller/BasicAuthIntegrationTest.java
  • src/test/java/com/offway/core/weather/controller/AirIntegrationTest.java

Comment thread .github/workflows/deploy.yml
sevineleven and others added 4 commits August 2, 2026 01:32
- 커스텀 EntryPoint 가 Security 기본 Basic 엔트리 포인트를 **대체**하는데 헤더를 안 붙여,
  WWW-Authenticate 가 아예 나가지 않았다. 실호출로 확인했다 — 401 응답에 헤더가 없다.
  그러면 브라우저 인증 팝업이 뜨지 않아 사람이 Swagger 를 열 수단이 사라진다(로그인 화면도 없다).
  "브라우저는 팝업으로 통과한다" 가 Basic 을 고른 이유 중 하나였는데 그 근거가 무너져 있었다.
- 주석은 정반대로 "붙이지 않는다" 고 적혀 있었다. 문서가 아니라 동작을 의도에 맞췄다.
- 동시성 테스트의 httpBasic("dev","dev") 를 요청 단위 mock 인증(user)으로 바꿨다. 실제
  자격증명에 기대면 운영 계정이 바뀔 때 삭제 경합이 아니라 401 경로를 검증하게 된다.
  실제 자격증명 검증은 BasicAuthIntegrationTest 한 곳이 맡는다.
- 베이스 이미지를 25.0.3_9-jre 로 고정했다. 이동 태그(25-jre)는 코드 변경 없이도 배포마다 다른
  JRE 를 받아와 "어제는 됐는데" 를 추적 불가능하게 만든다.
- 이미지를 커밋 SHA 로도 태그한다. latest 만 쓰면 이전 이미지가 덮여 롤백 대상이 사라진다.
  오래된 태그는 최근 3개만 남긴다 — 태그가 붙어 있어 prune 이 지우지 않아 무한히 쌓인다.
- Dockerfile 에 SPRING_PROFILES_ACTIVE=prod 를 기본값으로 넣었다. 애플리케이션 기본값이
  local 이라 프로파일이 빠지면 H2 와 **알려진 계정 dev/dev** 로 조용히 뜬다 — 외부에 열린
  8080 에서는 그게 곧 무방비다. env.prod 가 이미 넘기지만 안전망을 하나 더 둔다.
- 인증 실패에 흔적을 남긴다. 사용자명은 남기지 않는다: 비밀번호가 username 자리에 들어오는
  오타가 흔해 그대로 로그에 박힌다. 레벨은 규약대로 info 다(401 은 클라이언트 계약 위반).
- README 에 현재 평문 HTTP 라는 사실과 HTTPS 종단이 선행돼야 하는 조건을 명시했다.
@sevineleven

Copy link
Copy Markdown
Contributor Author

@coderabbitai nitpick 6건 검토 결과입니다.

반영 (e1d24bd)

nitpick 처리
Dockerfile 베이스 태그 고정 25.0.3_9-jre 로 고정 (Docker Hub 에 실존 확인)
latest 태그·롤백 수단 없음 커밋 SHA 로도 태그, 최근 3개 유지. docker run ... offway-core:<이전SHA> 로 롤백
프로필 누락 시 dev/dev 선택 위험 Dockerfile 에 SPRING_PROFILES_ACTIVE=prod 기본값 추가
인증 실패 로그 부재 method·path 만 남김. 사용자명은 제외 — 비밀번호가 username 자리에 들어오는 오타가 흔해 그대로 박힌다
README TLS 명시 평문 HTTP 라는 사실과 HTTPS 종단이 선행돼야 하는 조건을 명시

로그 레벨은 제안하신 warn 대신 info 로 뒀습니다. 이 레포 로깅 규약이 "클라이언트 계약 위반은 서버 입장에서 정상 흐름이라 info" 로 정하고 있어 401 이 거기 해당합니다. 브루트포스 탐지가 실제로 필요해지면 로그 레벨이 아니라 실패 카운팅 같은 별도 장치를 두는 편이 맞다고 봤습니다.

보류 — AWS OIDC 전환

방향은 동의합니다. 다만 지금 배포 인프라는 소셜 로그인(#93)이 붙기 전까지만 쓰는 임시 구성이고, 액세스 키의 권한을 이미 최소로 좁혀뒀습니다 — 전용 IAM 사용자에 보안그룹 규칙 추가·제거 권한만 주고 인스턴스 조회조차 UnauthorizedOperation 인 것을 실호출로 확인했습니다. 유출 시 피해가 "22번 포트를 열고 닫을 수 있음" 으로 제한돼, OIDC 도입 비용 대비 지금 얻을 이득이 크지 않다고 판단했습니다. 배포 구성이 상설화되면 그때 전환하겠습니다.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@sevineleven 반영 내용 확인했습니다. Docker 이미지 태그 고정, SHA 기반 롤백, prod 프로필 기본값, 인증 실패 로그의 민감정보 제외, TLS 전제 문서화가 모두 적절합니다.

401 로그를 info로 둔 결정도 레포지토리 규약과 일치하므로 괜찮습니다. 사용자명을 제외한 것은 Basic 인증 입력 실수로 비밀번호가 로그에 남을 가능성을 막습니다.

AWS OIDC 전환 보류 사유도 현재 범위에서는 타당합니다. 전용 IAM 사용자의 권한이 보안 그룹 규칙 추가·제거로 제한되고, 이 구성이 임시라는 전제가 유지되어야 합니다. 배포 구성이 상설화될 때 OIDC 전환을 추적할 후속 이슈를 만들어 두면 좋겠습니다. 원하시면 생성하겠습니다.

🐇 ✅


✏️ Learnings added
Learnt from: sevineleven
URL: https://github.com/team-offway/core/pull/126

Timestamp: 2026-08-01T16:44:29.462Z
Learning: 이 저장소에서는 HTTP 401 같은 클라이언트 계약 위반을 서버의 정상 흐름으로 보고 `warn`이 아닌 `info` 레벨로 기록한다. Basic 인증 실패 로그에는 사용자명을 기록하지 않는다. 사용자 입력 실수로 비밀번호가 username 필드에 들어갈 수 있기 때문이다.

Learnt from: sevineleven
URL: https://github.com/team-offway/core/pull/126

Timestamp: 2026-08-01T16:44:29.463Z
Learning: PR `#126의` AWS 액세스 키 기반 배포는 소셜 로그인 이슈 `#93` 이전까지만 사용하는 임시 구성이다. 전용 IAM 사용자는 SSH 보안 그룹 규칙 추가·제거 권한만 갖는다. 배포 구성이 상설화되면 GitHub Actions AWS OIDC로 전환해야 한다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@sevineleven
sevineleven merged commit 18fff28 into dev Aug 1, 2026
2 checks passed
@sevineleven
sevineleven deleted the feat/122-basic-auth-gate branch August 1, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새 기능 (외부에 보이는 변화)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

외부 노출 대비 임시 Basic 인증 게이트

1 participant