- Spring prod: server.forward-headers-strategy=framework — nginx의 X-Forwarded-Proto를 신뢰해 프록시 뒤에서도 request.isSecure()=true → Secure 쿠키·https 리다이렉트 정상화. - frontend/nginx-tls.conf: 443 TLS 종료 + 80→443 리다이렉트(+ /api 프록시). - infra/docker-compose.tls.yml: base와 함께 쓰는 HTTPS 오버레이(443 매핑·인증서 볼륨· ACS_PUBLIC_BASE_URL/ACS_COOKIE_SECURE). infra/certs/.gitignore로 인증서·키 커밋 제외. - README: HTTPS 적용 절차(인증서 준비→.env→compose 오버레이) 갱신. 검증: forward-headers=framework + cookie.secure=true로 기동 후 X-Forwarded-Proto=https 유무 대조 — 헤더 있으면 XSRF-TOKEN·JSESSIONID 모두 Secure, 없으면 XSRF Secure 미부여(프록시 프로토콜 연동 확인). 자체서명 인증서 생성 확인. 전체 컨테이너 TLS e2e는 Docker 데몬 기동 시 별도 검증 필요. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
124 lines
6.8 KiB
Markdown
124 lines
6.8 KiB
Markdown
# 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)
|
|
```cmd
|
|
:: 백엔드 (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](backend/test-api.http) 참고.
|
|
|
|
## 구현 현황
|
|
- [x] **1단계** 스캐폴딩 (pom, 설정, 공통 클래스, 전역 예외/응답 래퍼, JPA auditing)
|
|
- [x] **2단계** 인증·인가 (세션 로그인, 역할 기반 권한, 시드)
|
|
- [x] **3단계** 방문 사전신청 + 승인 (신청/취소/엑셀 업로드, 승인/반려 시 qrToken 발급) + 프론트 화면
|
|
- [x] **4단계** 입·출입 체크인/체크아웃 + 재실현황 (`AccessEvent`, `AccessControlGateway`+Mock, QR/이름 체크인, 재실 목록, 중복입장·만료 검증) + 출입콘솔 화면
|
|
- [x] **5단계** QR/배지 발급 (ZXing PNG `/api/passes/{id}/qr.png`, 배지 인쇄 화면)
|
|
- [x] **6단계** 블랙리스트(체크인 시 차단) + 대시보드 통계 집계 + 방문 리포트 엑셀(POI) 다운로드
|
|
- [x] **7단계** Flyway `V1__init.sql`(prod) + Docker Compose(db·app·web nginx) + Python 사용자 시드
|
|
|
|
## Docker 실행 (prod, PostgreSQL)
|
|
```bash
|
|
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 적용 (TLS 종료는 nginx가 담당)
|
|
브라우저 → **nginx(web, TLS 종료)** → 내부 http → Spring(app) 구조. 앱은 `server.forward-headers-strategy=framework`
|
|
(prod에 반영됨)로 nginx의 `X-Forwarded-Proto`를 신뢰해 Secure 쿠키·https 리다이렉트를 처리한다.
|
|
|
|
TLS 오버레이가 준비돼 있다 — 인증서만 넣으면 된다:
|
|
1. **인증서 준비** — `infra/certs/{fullchain.pem,privkey.pem}` (사내 CA 발급 / 공인 CA / 테스트용 자체서명).
|
|
자체서명 예: `openssl req -x509 -newkey rsa:2048 -nodes -days 825 -keyout infra/certs/privkey.pem -out infra/certs/fullchain.pem -subj "/CN=<host>"`
|
|
(`infra/certs/*.pem`은 `.gitignore`로 커밋 제외.)
|
|
2. **`.env`**: `ACS_PUBLIC_BASE_URL=https://<도메인>`, `ACS_COOKIE_SECURE=true`.
|
|
3. **기동** (base + TLS 오버레이):
|
|
```bash
|
|
cd infra
|
|
docker compose -f docker-compose.yml -f docker-compose.tls.yml up -d --build
|
|
```
|
|
→ nginx가 `443` TLS 종료 + `80→443` 리다이렉트. `frontend/nginx-tls.conf` 사용.
|
|
|
|
`ACS_COOKIE_SECURE=true`는 위처럼 **실제 HTTPS로 서비스될 때만** 켠다(평문 http에서 켜면 쿠키 미전송 → 로그인 불가).
|
|
|
|
- 출입콘솔의 **웹캠 QR 스캔**은 secure context(HTTPS 또는 localhost)에서만 동작 → HTTPS면 `http://내부IP`에서도 카메라 사용 가능.
|
|
- 방문자 공개 링크(`/pass/{token}`)도 HTTPS 도메인이면 휴대폰에서 안전하게 열린다.
|
|
|
|
### 사내망 빌드 참고
|
|
`docker compose build`는 컨테이너 안에서 npm/maven 의존성을 받는다. dev 서버는 공개망 직접
|
|
접근이 되어 대개 그대로 성공하지만, 사내 SSL 인스펙션에 걸려 인증서 오류가 나면 Dockerfile에
|
|
사내 CA 주입 또는 내부 미러 설정이 필요하다(그때 별도 반영).
|
|
|
|
## 디렉터리
|
|
```
|
|
backend/ Spring Boot (com.itcenter.acs)
|
|
frontend/ React + Vite
|
|
docs/ 워크플로우·시퀀스 다이어그램·이슈/유의사항 문서
|
|
```
|
|
|
|
## 문서
|
|
- [docs/workflow.md](docs/workflow.md) — 전체 업무 워크플로우(서술형)
|
|
- [docs/workflow-sequence.md](docs/workflow-sequence.md) — 시퀀스/상태 다이어그램(Mermaid)
|
|
- [docs/issues-and-guidelines.md](docs/issues-and-guidelines.md) — 이슈 정리 및 유의사항(규칙)
|
|
|
|
## 빌드 노트
|
|
- JDK 26 환경에서 Lombok은 **1.18.46** + maven-compiler-plugin `annotationProcessorPaths` 설정이 필요하다
|
|
(Spring Boot가 핀한 1.18.38은 JDK 26에서 애너테이션 처리가 동작하지 않음). `backend/pom.xml`에 반영됨.
|