← 개발 로그 목록

website: e2e 전용 고정 비밀번호 SUPER_ADMIN/ADMIN 시드 추가

/ 4분 분량 / 개발 로그

Playwright e2e 테스트에서 로그인 상태(storageState)를 만들어야 하는데, 기존 어드민 시드 방식으로는 로그인 자체가 불가능해서 별도의 e2e 전용 시드 러너를 추가한 PR이다.

Playwright는 로그인 세션을 재사용하려고 storageState를 저장해두고 쓰는데, 이게 실질적으로는 로그인 이후의 쿠키를 담아두는 거다. 문제는 세션 쿠키가 HttpOnly라서 JS로 직접 주입할 방법이 없다는 것 — 결국 storageState를 만들려면 한 번은 실제로 로그인 폼을 타고 들어가야 한다.

그런데 여기서 막힌 게, 기존 AdminSeedRunner는 애초에 비밀번호를 아무도 모르게 만드는 구조였다. 랜덤 비밀번호를 생성해서 저장한 뒤 그 값 자체는 버리고, 실제 비밀번호는 재설정 메일을 통해서만 설정하게 되어 있다. 운영 계정의 초기 비밀번호가 어딘가에 평문으로 남거나 전달되는 경로 자체를 없애자는 의도로 짜인 설계다. 이 설계는 보안 관점에서 맞는 방향이라 그대로 두는 게 맞았고, e2e만을 위해 이 구조를 건드리는 건 배보다 배꼽이 컸다.

그래서 선택한 방향은 기존 러너를 수정하는 대신 완전히 별도의 러너를 하나 더 만드는 것이었다. E2eAdminSeedRunner를 추가하고 @Profile("e2e")로 게이팅해서, SPRING_PROFILES_ACTIVE=e2e를 명시적으로 켠 경우에만 빈 자체가 생성되도록 했다. 이 프로필은 .env.stage나 .env.prod에는 존재하지 않으니, 운영 환경에 알려진 비밀번호를 가진 SUPER_ADMIN이 새어 들어갈 걱정은 구조적으로 차단된다.

다만 프로필 게이팅만 믿고 넘어가기엔 찜찜한 부분이 있었다. 프로필 설정은 결국 배포 스크립트나 환경변수 관리에 달려 있는 건데, 사람이 실수로 .env 파일에 e2e 프로필을 잘못 넣거나 하는 일이 아예 불가능하다고 장담할 수 없다. 그래서 FlywayConfig가 @Profile("stage")로 게이팅되어 있는 걸 코드 레벨에서 테스트로 고정해둔 패턴을 그대로 가져와서, E2eAdminSeedRunnerProfileGateTest를 만들었다. ApplicationContextRunner로 e2e/stage/prod/무프로필 네 가지 조합을 각각 띄워보고, e2e에서만 빈이 생기고 나머지 세 경우엔 절대 생기지 않는다는 걸 테스트로 못박았다. 이렇게 해두면 나중에 누군가 실수로 프로필 애노테이션을 건드리거나 지워도 테스트가 바로 잡아준다.

시드되는 계정 정보는 이메일/비밀번호를 하드코딩하지 않고 @Value로 프로퍼티를 주입받게 했다. application.yml에 admin.e2e-seed.* 하위로 기본값을 박아두고(e2e-super-admin@likelion-khu.com / E2eSuperAdmin!2026 등), 필요하면 ADMIN_E2E_* 환경변수로 오버라이드할 수 있게 .env.example에도 항목을 추가했다. 이 값들은 로컬/CI e2e 환경에서만 쓰이는 고정값이라 기본값을 리포지토리에 그대로 커밋해도 문제가 없다고 판단했는데, 대신 .env.example 주석에 "stage/prod .env엔 이 프로필도, 이 값들도 절대 넣지 말 것"이라고 명시적으로 적어뒀다. 코드 레벨 안전장치와 별개로, 사람이 실수할 수 있는 지점에는 문서로도 경고를 남겨두는 게 낫다고 봤다.

동작 검증은 E2eAdminSeedRunnerTest로 했다. 러너를 실행하면 고정 비밀번호로 SUPER_ADMIN/ADMIN 두 계정이 만들어지고, 비밀번호 인코더로 매칭이 되는지 확인했다. 그리고 러너가 두 번 실행돼도 계정이 중복 생성되지 않는지도 같이 확인했는데, 이건 existsByEmail 체크로 이미 존재하면 건너뛰게 만들어둔 부분을 검증한 거다. 애플리케이션이 재시작되거나 e2e를 여러 번 돌릴 때마다 계정이 쌓이면 곤란하니 처음부터 멱등하게 짜뒀다.

전체 백엔드 테스트 스위트를 돌려서 이 변경과 무관한 Mailpit 통합테스트 3건(Docker 타이밍 이슈)을 제외하고 전부 통과하는 걸 확인한 뒤 병합했다. 커밋은 한 번에 끝났는데, 처음부터 별도 러너 + 프로필 게이팅 + 게이트 테스트라는 구조를 잡고 들어간 덕에 시행착오랄 게 크게 없었다. 오히려 신경 쓴 부분은 코드보다 "이 계정이 절대 운영에 새어나가면 안 된다"는 전제를 프로필 애노테이션 하나에만 의존하지 않고 테스트로 이중으로 박아두는 쪽이었다.