unknown d15ffe3fce fix(security): P0 — CSRF 방어, h2-console prod 격리, CORS 외부화
- CSRF: CookieCsrfTokenRepository(이중제출 토큰) + SPA용 CsrfTokenRequestAttributeHandler,
  CsrfCookieFilter로 XSRF-TOKEN 쿠키 강제 렌더. /api/auth/login·/api/public/** 는 예외.
  프론트 api.ts가 변경요청에 X-XSRF-TOKEN 헤더 자동 주입.
- 세션쿠키 SameSite=Lax·HttpOnly, prod는 Secure=${ACS_COOKIE_SECURE:false}(HTTPS 시 활성).
- h2-console permitAll·frameOptions.sameOrigin을 spring.h2.console.enabled에 연동 → prod 자동 비노출.
- CORS allowed-origins를 acs.cors.allowed-origins 프로퍼티로 외부화(prod 기본 빈 값, nginx 동일출처).
- .env.example·docker-compose에 ACS_CORS_ALLOWED_ORIGINS·ACS_COOKIE_SECURE 추가.

검증: 빌드/테스트 통과, curl로 CSRF 차단(403)·토큰 통과(404)·로그인/공개 예외·dev h2-console 확인.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 09:54:55 +09:00

IT센터 출입자관리시스템 (Access Control System)

IT센터 방문자/출입자 관리 웹 시스템. 방문 사전신청·승인, 입·출입 체크인, QR/배지 발급, 통계·리포트·블랙리스트를 목표로 하며, 출입통제 하드웨어는 추후 연동 가능하도록 추상화한다.

기술 스택

  • 백엔드: Spring Boot 3.4.5 / Java 21 (JDK 26 빌드) · Spring Security(세션) · JPA · H2(dev)·PostgreSQL(prod) · POI · ZXing · Flyway
  • 프론트엔드: React 19 · Vite 6 · TypeScript · react-router 7
  • 포트: API 8080, 웹 5173

역할

역할 권한
ADMIN 전체 관리, 사용자/블랙리스트/리포트, 모든 승인
SECURITY 입·출입 콘솔(체크인/아웃), 재실현황, 배지 발급
HOST 방문 사전신청 등록, 담당 방문 승인

실행 (로컬 개발, H2)

:: 백엔드 (http://localhost:8080)
run-backend.cmd

:: 프론트엔드 (http://localhost:5173)
run-frontend.cmd

두 스크립트는 C:\ai-dev\scripts\env.cmd(포터블 JDK/Maven/Node)를 먼저 로드한다.

초기 계정 (DataSeeder, dev 전용)

아이디 비밀번호 역할
admin ChangeMe123! ADMIN
security ChangeMe123! SECURITY
host ChangeMe123! HOST

최초 로그인 시 비밀번호 변경이 요구된다.

주요 API

  • POST /api/auth/login · POST /api/auth/logout · GET /api/auth/me · POST /api/auth/change-password
  • GET /api/zones
  • GET/POST /api/visit-requests · GET /api/visit-requests/pending · POST /api/visit-requests/{id}/cancel · POST /api/visit-requests/upload(엑셀)
  • POST /api/approvals/{id}/approve · POST /api/approvals/{id}/reject
  • POST /api/access/check-in · POST /api/access/check-out · GET /api/access/inside · GET /api/access/search?q=
  • GET /api/passes/{id} · GET /api/passes/{id}/qr.png(배지 QR)
  • GET /api/stats/summary · GET /api/reports/visits.xlsx?from=&to=
  • GET/POST /api/blacklist · DELETE /api/blacklist/{id} (ADMIN)

API 스모크 테스트는 backend/test-api.http 참고.

구현 현황

  • 1단계 스캐폴딩 (pom, 설정, 공통 클래스, 전역 예외/응답 래퍼, JPA auditing)
  • 2단계 인증·인가 (세션 로그인, 역할 기반 권한, 시드)
  • 3단계 방문 사전신청 + 승인 (신청/취소/엑셀 업로드, 승인/반려 시 qrToken 발급) + 프론트 화면
  • 4단계 입·출입 체크인/체크아웃 + 재실현황 (AccessEvent, AccessControlGateway+Mock, QR/이름 체크인, 재실 목록, 중복입장·만료 검증) + 출입콘솔 화면
  • 5단계 QR/배지 발급 (ZXing PNG /api/passes/{id}/qr.png, 배지 인쇄 화면)
  • 6단계 블랙리스트(체크인 시 차단) + 대시보드 통계 집계 + 방문 리포트 엑셀(POI) 다운로드
  • 7단계 Flyway V1__init.sql(prod) + Docker Compose(db·app·web nginx) + Python 사용자 시드

Docker 실행 (prod, PostgreSQL)

cd infra
cp .env.example .env        # 값 수정 (아래 참고)
docker compose up -d --build        # db + app(:8080) + web(nginx :80)
docker compose run --rm seed        # 사용자 시드 적재 (admin/security/host)
docker compose logs -f app          # 기동/마이그레이션 로그
  • 백엔드는 prod 프로파일로 기동, Flyway가 스키마를 생성/검증한다.
  • 웹(nginx)이 정적 파일 + /api 리버스 프록시를 담당한다.
  • DB는 named volume(db_data)에 영속 — 재기동해도 데이터 유지(dev H2와 다름).

.env 주요 값

변수 설명
POSTGRES_PASSWORD DB 비밀번호 (실제값으로 변경)
WEB_PORT 웹 공개 포트 (기본 80)
ACS_SMS_PROVIDER dev(로그만) / hanbank(사내 API로 LMS 실발송)
ACS_PUBLIC_BASE_URL 문자 링크가 가리키는 주소 — 방문자 휴대폰에서 접속 가능한 실제 URL (localhost 금지)
ACS_SMS_API_URL 사내 메시지 API 주소 (기본 http://210.104.132.59:8000)

문자 실발송(hanbank) 전제

  1. 서버에서 메시지 API 도달 확인: nc -vz 210.104.132.59 8000
  2. ACS_SMS_PROVIDER=hanbank, ACS_PUBLIC_BASE_URL=외부 접속 URL 로 설정 후 재기동

HTTPS / 카메라 스캔 (선택, 접속 방식에 따라 필요)

  • 출입콘솔의 웹캠 QR 스캔은 secure context(HTTPS 또는 localhost)에서만 동작한다. 서버를 http://내부IP로 접속하면 카메라가 차단된다.
  • 방문자 공개 링크(/pass/{token})도 HTTPS 도메인이면 휴대폰에서 안전하게 열린다.
  • 적용하려면: 사내 도메인/인증서 확보 → frontend/nginx.conf에 443 TLS server 블록 추가, docker-compose.yml web 서비스에 "443:443" 매핑 + 인증서 볼륨 마운트, ACS_PUBLIC_BASE_URLhttps://...로 설정. (도메인/인증서 확정 후 진행)

사내망 빌드 참고

docker compose build는 컨테이너 안에서 npm/maven 의존성을 받는다. dev 서버는 공개망 직접 접근이 되어 대개 그대로 성공하지만, 사내 SSL 인스펙션에 걸려 인증서 오류가 나면 Dockerfile에 사내 CA 주입 또는 내부 미러 설정이 필요하다(그때 별도 반영).

디렉터리

backend/   Spring Boot (com.itcenter.acs)
frontend/  React + Vite
docs/      워크플로우·시퀀스 다이어그램·이슈/유의사항 문서

문서

빌드 노트

  • JDK 26 환경에서 Lombok은 1.18.46 + maven-compiler-plugin annotationProcessorPaths 설정이 필요하다 (Spring Boot가 핀한 1.18.38은 JDK 26에서 애너테이션 처리가 동작하지 않음). backend/pom.xml에 반영됨.
Description
출입관리시스템 : ACS (Access Control System)
Readme 1 MiB
Languages
TypeScript 58.5%
Java 34.7%
CSS 4.2%
JavaScript 1.4%
Python 0.7%
Other 0.4%