gc-guide-api/CLAUDE.md
htlee 7aa50dc6a2 docs: CLAUDE.md 상세화 — 전체 구현 가이드 추가
- 7단계 구현 로드맵 (엔티티→인증→관리자→활동→이슈→RBAC→시드)
- DB 스키마 DDL (PostgreSQL + 인덱스)
- 패키지 구조 목표 (entity/auth/service/controller/dto)
- 인증/인가 흐름 상세 (Google OAuth→JWT→RBAC)
- application.yml 설정 참조
2026-02-14 13:25:41 +09:00

11 KiB

gc-guide-api — 가이드 사이트 백엔드 API

프로젝트 개요

gc-guide 프론트엔드의 백엔드 API. Google OAuth2 인증, 사용자 관리(RBAC), 활동 기록, 이슈 트래커 제공.

기술 스택

  • Spring Boot 3.5.2 + JDK 17
  • Spring Security (JWT 기반 Stateless 인증)
  • Spring Data JPA + PostgreSQL (운영) / H2 (로컬)
  • Google API Client 2.7.2 (ID Token 검증)
  • JJWT 0.12.6 (JWT 생성/검증)
  • Lombok

빌드 & 실행

source ~/.sdkman/bin/sdkman-init.sh && sdk use java 17.0.18-amzn  # JDK 전환
./mvnw -s .mvn/settings.xml clean compile          # 컴파일
./mvnw -s .mvn/settings.xml spring-boot:run         # 로컬 실행 (H2, port 8080)
./mvnw -s .mvn/settings.xml package -DskipTests     # JAR 패키징

프로필

  • local (기본): H2 인메모리 DB (create-drop), 디버그 로깅, H2 콘솔 /h2-console
  • prod: PostgreSQL (gc_guide DB), validate 모드

배포

  • Docker (eclipse-temurin:17-jre + JAR)
  • guide.gc-si.dev/api/* → Nginx 프록시 → 컨테이너
  • CI/CD: Gitea Actions (main 머지 시 자동 빌드/배포)
  • Gitea: https://gitea.gc-si.dev/gc/gc-guide-api

의존성 레포지토리

관련 프로젝트

  • gc-guide: 프론트엔드 (React + TypeScript + Vite)

현재 구현 상태

완료 (scaffold)

  • Spring Boot 프로젝트 초기화 (pom.xml + Maven Wrapper)
  • application.yml + local/prod 프로필 분리
  • SecurityConfig.java — CSRF 비활성화, Stateless 세션, 공개 엔드포인트 설정
  • HealthController.java — GET /api/health{status: "UP"}
  • Nexus 프록시 인증 설정 (.mvn/settings.xml)
  • 빌드 검증: mvn clean compile 성공

미구현 (별도 세션에서 작업)

아래 순서대로 구현 필요:

1단계: DB 엔티티 + JPA

패키지: com.gcsc.guide.entity

@Entity User        id, email, name, avatarUrl, status(PENDING/ACTIVE/REJECTED/DISABLED), isAdmin, createdAt, updatedAt, lastLoginAt
@Entity Role        id, name, description, createdAt
@Entity UserRole    userId, roleId (다대다, @IdClass 또는 @JoinTable)
@Entity RoleUrlPattern  id, roleId, urlPattern, createdAt
@Entity LoginHistory    id, userId, loginAt, ipAddress, userAgent
@Entity PageView        id, userId, pagePath, viewedAt
@Entity Issue           id, title, body, status, priority, authorId, assigneeId, createdAt, updatedAt
@Entity IssueComment    id, issueId, authorId, body, createdAt

패키지: com.gcsc.guide.repository — 각 엔티티별 JpaRepository

2단계: 인증 API

패키지: com.gcsc.guide.auth

  • JwtTokenProvider.java — JWT 생성/검증 (JJWT 0.12.6)
    • secret: app.jwt.secret (256bit 이상)
    • expiration: app.jwt.expiration-ms (기본 24시간)
  • JwtAuthenticationFilter.java — OncePerRequestFilter, Authorization Bearer 토큰 파싱
  • GoogleTokenVerifier.java — Google API Client로 ID Token 검증
    • Client ID: app.google.client-id
    • 이메일 도메인 검증: app.allowed-email-domain (gcsc.co.kr)
  • AuthController.java:
    • POST /api/auth/google — { idToken } → GoogleTokenVerifier → User 조회/생성 → JWT 발급
      • 신규 사용자: status=PENDING, DB 저장
      • htlee@gcsc.co.kr: 자동 ACTIVE + isAdmin=true
      • 응답: { token, user }
    • GET /api/auth/me — JWT에서 userId 추출 → User + roles 반환
    • POST /api/auth/logout — (Stateless이므로 프론트에서 토큰 삭제, 서버는 204)

SecurityConfig 수정:

  • JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 앞에 추가
  • 공개 엔드포인트: /api/auth/**, /api/health, /actuator/health, /h2-console/**

3단계: 관리자 API

패키지: com.gcsc.guide.controller

  • AdminUserController.java:

    • GET /api/admin/users — 전체 목록 (status 필터 쿼리파라미터)
    • PUT /api/admin/users/{id}/approve — PENDING→ACTIVE
    • PUT /api/admin/users/{id}/reject — PENDING→REJECTED
    • PUT /api/admin/users/{id}/disable — ACTIVE→DISABLED
    • PUT /api/admin/users/{id}/roles — { roleIds: [1,2] } → user_roles 업데이트
    • POST /api/admin/users/{id}/admin — isAdmin=true
    • DELETE /api/admin/users/{id}/admin — isAdmin=false
  • AdminRoleController.java:

    • GET /api/admin/roles — 전체 롤 목록
    • POST /api/admin/roles — { name, description } → Role 생성
    • PUT /api/admin/roles/{id} — 수정
    • DELETE /api/admin/roles/{id} — 삭제
    • GET /api/admin/roles/{id}/permissions — URL 패턴 목록
    • POST /api/admin/roles/{id}/permissions — { urlPattern } 추가
    • DELETE /api/admin/permissions/{id} — 삭제
  • AdminStatsController.java:

    • GET /api/admin/stats — { totalUsers, activeUsers, pendingUsers, todayLogins, ... }

관리자 권한 체크: @PreAuthorize("@authChecker.isAdmin()") 또는 SecurityConfig에서 /api/admin/** 패턴에 커스텀 필터 적용

4단계: 활동 기록 API

  • ActivityController.java:
    • POST /api/activity/track — { pagePath } → PageView 저장
    • GET /api/activity/login-history — 현재 사용자의 로그인 이력

5단계: 이슈 관리 API

  • IssueController.java:
    • GET /api/issues — 이슈 목록 (status 필터, 페이징)
    • POST /api/issues — { title, body, priority } → Issue 생성
    • GET /api/issues/{id} — 이슈 상세 (코멘트 포함)
    • PUT /api/issues/{id} — 수정
    • POST /api/issues/{id}/comments — { body } → 코멘트 추가

6단계: 롤 기반 URL 접근 제어 (선택)

  • DynamicAuthorizationManager.java — 커스텀 AuthorizationManager
    • 매 요청마다 DB에서 사용자 롤 → role_url_patterns 조회 → ant-style 매칭
    • SecurityConfig에서 .anyRequest().access(dynamicAuthorizationManager) 적용
    • 캐싱: 사용자별 URL 패턴을 JWT 클레임에 포함하거나 Redis/로컬캐시 활용

7단계: 초기 데이터 시딩

data.sql 또는 ApplicationRunner로:

INSERT INTO roles (name, description) VALUES
    ('ADMIN', '전체 접근 권한 (관리자 페이지 포함)'),
    ('DEVELOPER', '전체 개발 가이드 접근'),
    ('FRONT_DEV', '프론트엔드 개발 가이드만');

INSERT INTO role_url_patterns (role_id, url_pattern) VALUES
    (1, '/**'),
    (2, '/dev/**'),
    (3, '/dev/front/**');

DB 스키마

로컬 (H2, create-drop)

JPA가 엔티티 기반으로 자동 생성. data.sql로 초기 시드.

운영 (PostgreSQL)

항목
Host 211.208.115.83 (Docker: 172.17.0.1)
Port 5432
Database gc_guide
Username gcguide
Password GcGuide!2026

DB/사용자 아직 미생성 — 서버에서 아래 SQL 실행 필요:

CREATE USER gcguide WITH PASSWORD 'GcGuide!2026';
CREATE DATABASE gc_guide OWNER gcguide ENCODING 'UTF8';
GRANT ALL PRIVILEGES ON DATABASE gc_guide TO gcguide;

스키마 DDL (JPA가 로컬에서 자동 생성하되, 운영은 validate 모드이므로 수동 생성 필요):

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(100) NOT NULL,
    avatar_url VARCHAR(500),
    status VARCHAR(20) DEFAULT 'PENDING',
    is_admin BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    last_login_at TIMESTAMP
);

CREATE TABLE roles (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(255),
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE user_roles (
    user_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
    role_id BIGINT REFERENCES roles(id) ON DELETE CASCADE,
    PRIMARY KEY (user_id, role_id)
);

CREATE TABLE role_url_patterns (
    id BIGSERIAL PRIMARY KEY,
    role_id BIGINT REFERENCES roles(id) ON DELETE CASCADE,
    url_pattern VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE login_history (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT REFERENCES users(id),
    login_at TIMESTAMP DEFAULT NOW(),
    ip_address VARCHAR(45),
    user_agent VARCHAR(500)
);

CREATE TABLE page_views (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT REFERENCES users(id),
    page_path VARCHAR(255) NOT NULL,
    viewed_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE issues (
    id BIGSERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    body TEXT,
    status VARCHAR(20) DEFAULT 'OPEN',
    priority VARCHAR(20) DEFAULT 'NORMAL',
    author_id BIGINT REFERENCES users(id),
    assignee_id BIGINT REFERENCES users(id),
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE issue_comments (
    id BIGSERIAL PRIMARY KEY,
    issue_id BIGINT REFERENCES issues(id) ON DELETE CASCADE,
    author_id BIGINT REFERENCES users(id),
    body TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_login_history_user ON login_history(user_id);
CREATE INDEX idx_page_views_user ON page_views(user_id);
CREATE INDEX idx_issues_status ON issues(status);
CREATE INDEX idx_issue_comments_issue ON issue_comments(issue_id);

패키지 구조 (목표)

com.gcsc.guide/
├── GcGuideApiApplication.java      ✅
├── config/
│   └── SecurityConfig.java         ✅ (JWT 필터 추가 필요)
├── auth/
│   ├── JwtTokenProvider.java       ⬜
│   ├── JwtAuthenticationFilter.java ⬜
│   ├── GoogleTokenVerifier.java    ⬜
│   └── AuthController.java        ⬜
├── entity/
│   ├── User.java                   ⬜
│   ├── Role.java                   ⬜
│   ├── RoleUrlPattern.java         ⬜
│   ├── LoginHistory.java           ⬜
│   ├── PageView.java               ⬜
│   ├── Issue.java                  ⬜
│   └── IssueComment.java          ⬜
├── repository/                     ⬜ (각 엔티티별 JpaRepository)
├── service/
│   ├── UserService.java            ⬜
│   ├── RoleService.java            ⬜
│   ├── ActivityService.java        ⬜
│   └── IssueService.java          ⬜
├── controller/
│   ├── HealthController.java       ✅
│   ├── AdminUserController.java    ⬜
│   ├── AdminRoleController.java    ⬜
│   ├── AdminStatsController.java   ⬜
│   ├── ActivityController.java     ⬜
│   └── IssueController.java       ⬜
├── dto/                            ⬜ (요청/응답 DTO)
└── exception/                      ⬜ (GlobalExceptionHandler)

application.yml 설정 참조

app:
  jwt:
    secret: ${JWT_SECRET:gc-guide-dev-jwt-secret-key-must-be-at-least-256-bits-long}
    expiration-ms: ${JWT_EXPIRATION:86400000}
  google:
    client-id: ${GOOGLE_CLIENT_ID:295080817934-1uqaqrkup9jnslajkl1ngpee7gm249fv.apps.googleusercontent.com}
  allowed-email-domain: gcsc.co.kr

구현 시 주의사항

  • DTO와 Entity 분리 필수 (API 응답에 Entity 직접 노출 금지)
  • 비즈니스 로직은 Service 계층에 집중
  • @Transactional 범위 최소화
  • H2 로컬 모드에서 전체 기능 테스트 가능하도록 프로필 분리 유지
  • Google OAuth2 Client ID는 gc-guide (프론트엔드)와 동일한 것 사용