diff --git a/README.ko.md b/README.ko.md index 25e11ba..82d5d2d 100644 --- a/README.ko.md +++ b/README.ko.md @@ -1,94 +1,226 @@ # devslab-kit -[English README](README.md) - -DevsLab 제품군에서 재사용하는 플랫폼 Spring Boot Starter. - -`devslab-kit`은 인증, 계정, 권한, 역할, 멀티/싱글 테넌트, 메뉴, 감사 로그, Admin API, -Spring Boot AutoConfiguration 같은 **여러 제품이 공통으로 쓰는 플랫폼 빌딩 블록**을 제공한다. -각 제품은 자기 도메인에만 집중할 수 있게 된다. - -이 kit은 **특정 제품 도메인을 절대 알지 않는다.** 첫 번째 실사용 제품은 `bookrecord`지만, -`devslab-kit` 자체는 제품에 독립적이어야 한다. +[![Maven Central](https://img.shields.io/maven-central/v/kr.devslab/devslab-kit-spring-boot-starter?logo=apachemaven)](https://central.sonatype.com/artifact/kr.devslab/devslab-kit-spring-boot-starter) +[![Build](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml/badge.svg)](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) +![Java](https://img.shields.io/badge/Java-21%2B-orange) +![Spring Boot](https://img.shields.io/badge/Spring%20Boot-4.0-6DB33F?logo=springboot) + +[English README](README.md) · [변경 이력](CHANGELOG.ko.md) · [ADR](docs/adr) + +재사용 가능한 **Spring Boot 4 플랫폼 스타터**. 애플리케이션에 끼워 넣으면 인증, 인가, +멀티테넌시, 동적 메뉴, 감사 로깅이 자동 구성으로 제공되고, 관리자 REST API와 바로 쓰는 +관리자 콘솔까지 딸려옵니다. 매번 플랫폼 계층을 다시 만드는 대신 각 제품은 자기 도메인에만 +집중할 수 있습니다. + +`devslab-kit`은 의도적으로 **제품에 독립적**입니다. `UserId`, `TenantId`, `Permission`, +`Role`, `Menu`, `Audit` 같은 플랫폼 개념만 알 뿐, 특정 제품의 도메인은 절대 알지 않습니다. + +> **상태 — 1.0 이전.** 첫 공개 릴리스 `0.1.0`에 필요한 기능은 모두 완성되었습니다. +> `0.1.0`부터 Maven Central에 배포하며, 그 전에는 소스 빌드나 `publishToMavenLocal`을 +> 사용하세요. + +## 목차 + +- [기능](#기능) · [요구-사항](#요구-사항) · [설치](#설치) +- [빠른-시작](#빠른-시작) · [설정](#설정) +- [모듈](#모듈) · [관리자-rest-api](#관리자-rest-api) · [관리자-콘솔](#관리자-콘솔) +- [설계-원칙](#설계-원칙) · [문서](#문서) · [소스에서-빌드](#소스에서-빌드) +- [버전-정책](#버전-정책) · [라이선스](#라이선스) + +## 기능 + +| 영역 | 제공 내용 | +| --- | --- | +| **Identity** | 사용자 계정, BCrypt 자격 증명, JWT 발급/파싱, 설정 가능한 로그인 잠금, 비밀번호 강제 변경. | +| **Access** | 역할, 권한, 주체 **그룹**, 그리고 RBAC 위에 얹은 **ABAC** 정책 SPI(`PolicyEvaluator`). | +| **멀티테넌시** | *항상 존재하는* 테넌트 컨텍스트(싱글 테넌트라도 추상화를 건너뛰지 않고 default를 resolve), 플러그형 리졸버: `fixed` · `header` · `jwt` · `subdomain`; `single`/`multi` 모드. | +| **메뉴** | 사용자별로 계산되는 권한 필터링 동적 메뉴 트리. | +| **감사(Audit)** | `ApplicationEventPublisher` 기반 비동기 감사 로깅, PostgreSQL(JSONB 메타데이터)에 영속화. | +| **캐시** | 플러그형 캐시 — `in-memory` · `redis` · `none`. Redis 백엔드가 JSON 직렬화를 직접 책임지므로 `Serializable` 구현이나 직렬화기 배선이 필요 없습니다(ADR 0002). 사용자별 메뉴 캐시도 이 공유 매니저를 사용합니다. | +| **최초 관리자 부트스트랩** | 첫 부팅 시 테넌트, `PLATFORM_ADMIN` 역할, `admin.*` 권한, 관리자 사용자를 멱등하게 생성 — opt-in, 프로퍼티 기반(ADR 0001). | +| **관리자 REST API** | 위 모든 엔티티 + 진단 + 실시간 설정 뷰를 위한 `/admin/api/v1/**`. | +| **Override 친화적** | 모든 기본 빈이 `@ConditionalOnMissingBean` — 직접 선언하면 어느 조각이든 교체 가능. | +| **GraalVM Native** | 리플렉션 중심 설계를 피하고, 샘플 앱이 `nativeCompile`을 검증. | + +## 요구 사항 + +| | | +| --- | --- | +| Java | 21+ | +| Spring Boot | 4.0+ | +| 데이터 저장소 | PostgreSQL (주 저장소; Flyway 마이그레이션) | +| 캐시 | Redis (선택 — `cache.type = redis`일 때만) | +| 웹 스택 | Spring Web MVC (Servlet) + Spring Security | + +## 설치 + +> `0.1.0`부터 Maven Central에서 받을 수 있습니다. 스타터가 플랫폼 전체를 끌어옵니다. + +**Gradle (Kotlin DSL)** + +```kotlin +implementation("kr.devslab:devslab-kit-spring-boot-starter:0.1.0") +``` -## 상태 +**Maven** -> **`0.1.0` 이전 부트스트랩 단계.** 현재 리포지토리에는 IntelliJ New Project 마법사로 생성한 -> Spring Boot 4 초기 프로젝트와, 조직 컨벤션(라이선스, 변경 이력, 양 언어 README)만 들어 있다. -> 멀티모듈 분리, 공개 계약, AutoConfiguration, 샘플 앱은 [`CHANGELOG.md`](CHANGELOG.md)에 따라 -> 후속 PR에서 들어온다. +```xml + + kr.devslab + devslab-kit-spring-boot-starter + 0.1.0 + +``` -## 기술 스택 +원하는 모듈만? 개별 모듈(예: `devslab-kit-access-core`)에만 의존하거나, `-api` 계약에만 +의존해 직접 구현을 제공할 수도 있습니다. + +## 빠른 시작 + +**1. 스타터 추가** (위 참조). + +**2. 설정** — datasource와 플랫폼: + +```yaml +spring: + datasource: + url: jdbc:postgresql://localhost:5432/app + username: app + password: app + data: + redis: + host: localhost # cache.type = redis 일 때만 필요 + +devslab: + kit: + tenant: + mode: single # single | multi + resolver: fixed # fixed | header | jwt | subdomain + default-tenant-id: default + identity: + jwt: + secret: ${DEVSLAB_JWT_SECRET} # HS256용 32바이트 이상 — 운영에서 설정 + ttl: PT8H + max-failed-attempts: 5 # N회 실패 시 계정 잠금 + lockout-duration: PT15M + cache: + type: in-memory # in-memory | redis | none + bootstrap: + enabled: true # 첫 부팅 시 최초 관리자 생성 +``` -| 계층 | 선택 | -| ---------------- | ---------------------------------------------------------- | -| 언어 | Java 25 | -| 프레임워크 | Spring Boot 4.x | -| 빌드 | Gradle (Kotlin DSL) | -| Group / Package | `kr.devslab` / `kr.devslab.kit` | -| 웹 스택 | Spring Web MVC (Servlet) — WebFlux는 core에 **없음** | -| 보안 | Spring Security (Servlet) | -| 영속성 | Spring Data JPA + Flyway + PostgreSQL | -| 캐시 / 세션 | Spring Data Redis (Spring Session은 아직 core에 **없음**) | -| 관측 | Spring Boot Actuator | -| 네이티브 | GraalVM Native Build Tools | -| 로컬 개발 / 테스트 | Docker Compose + Testcontainers (PostgreSQL, Redis) | +**3. 앱 부팅.** 부트스트랩이 `PLATFORM_ADMIN`을 시드하고, 관리자 REST API가 +`/admin/api/v1/**`에서 동작하며, Flyway가 `platform_*` 테이블을 생성합니다. +[관리자 콘솔](#관리자-콘솔)을 연결해 로그인하세요. + +Docker Compose(Postgres + Redis)와 Testcontainers 기반 테스트까지 갖춘 완전히 +동작하는 설정은 [`devslab-kit-sample-app`](devslab-kit-sample-app) +([README](devslab-kit-sample-app/README.ko.md))에 있습니다. + +## 설정 + +모든 키는 `devslab.kit.*` 접두사 아래에 있습니다. 기본값 표기. + +| 키 | 기본값 | 설명 | +| --- | --- | --- | +| `tenant.enabled` | `true` | 테넌트 계층 마스터 스위치. | +| `tenant.mode` | `single` | `single` 또는 `multi`. | +| `tenant.default-tenant-id` | `default` | single 모드/폴백에 쓰는 테넌트. | +| `tenant.resolver` | `fixed` | `fixed` · `header` · `jwt` · `subdomain`. | +| `identity.jwt.secret` | — | HS256용 32바이트 이상 키. **운영 필수.** | +| `identity.jwt.issuer` | `devslab-kit` | JWT `iss` 클레임. | +| `identity.jwt.ttl` | `PT8H` | 토큰 수명(ISO-8601 duration). | +| `identity.max-failed-attempts` | `5` | 이 횟수만큼 실패하면 계정 잠금. | +| `identity.lockout-duration` | `PT15M` | 계정 잠금 유지 시간. | +| `audit.enabled` | `true` | 감사 로깅 토글. | +| `audit.async-queue-capacity` | `1024` | 비동기 발행기의 bounded 큐. | +| `menu.enabled` | `true` | 동적 메뉴 토글. | +| `cache.type` | `in-memory` | `in-memory` · `redis` · `none`. | +| `cache.ttl` | `PT10M` | 엔트리 TTL(Redis 백엔드에서 사용). | +| `cache.key-prefix` | `devslab:` | Redis 키 네임스페이스. | +| `cache.allowed-package` | `kr.devslab` | 안전한 다형 JSON 타이핑 허용 목록. | +| `bootstrap.enabled` | `false` | 첫 부팅 시 최초 관리자 생성. | +| `bootstrap.admin-login-id` | `admin` | 최초 관리자 로그인 id. | +| `bootstrap.admin-password` | — | 비우면 강력한 랜덤 비밀번호를 한 번 로깅. | +| `bootstrap.must-change-password` | `true` | 첫 로그인 시 변경 강제. | + +런타임의 실제 적용값은 `GET /admin/api/v1/settings`에서도 볼 수 있습니다(시크릿 마스킹). + +## 모듈 + +| 모듈 | 역할 | +| --- | --- | +| `devslab-kit-core` | 공유 값 객체 (`TenantId`, `UserId`, `PublicId`, …) | +| `devslab-kit-tenant-{api,core}` | 테넌트 컨텍스트 + 리졸버 | +| `devslab-kit-identity-{api,core}` | 사용자, 자격 증명, JWT, 로그인 잠금 | +| `devslab-kit-access-{api,core}` | 역할, 권한, 그룹, ABAC 정책 엔진 | +| `devslab-kit-menu-{api,core}` | 권한 필터링 동적 메뉴 | +| `devslab-kit-audit-{api,core}` | 비동기 감사 로깅 | +| `devslab-kit-cache-{api,core}` | 플러그형 캐시 (in-memory / Redis) | +| `devslab-kit-admin-api` | 관리자 REST 엔드포인트 | +| `devslab-kit-autoconfigure` | Spring Boot 자동 구성 | +| `devslab-kit-spring-boot-starter` | 스타터 — 플랫폼 전체를 끌어옴 | +| `devslab-kit-sample-app` | 실행 가능한 참조 앱 + 통합 테스트 하니스 (배포 안 함) | + +**`-api` vs `-core`.** 각 기능은 얇은 계약 모듈(`-api`)과 기본 구현(`-core`)으로 +나뉩니다. 배터리 포함 기본값을 쓰려면 `-core`에, 직접 구현을 끼우려면 `-api`에만 +의존하세요 — 그러면 자동 구성이 물러납니다(`@ConditionalOnMissingBean`). + +## 관리자 REST API + +모두 `/admin/api/v1` 아래: + +| 리소스 | 엔드포인트 | +| --- | --- | +| `auth` | 로그인, 비밀번호 변경 | +| `users` · `roles` · `permissions` · `groups` | 전체 CRUD + 할당 | +| `menus` · `tenants` | 메뉴 트리/테넌트 관리 | +| `policies` | ABAC 정책 목록 + `(subject, action, resource)` 드라이런 | +| `audit-logs` | 감사 추적 검색/필터 | +| `diagnostics` | 읽기 전용 로그인/권한/메뉴 가시성 프로브 | +| `settings` | 실시간 `devslab.kit.*` 뷰(시크릿 마스킹) | +| `bootstrap/status` | 최초 실행 흐름용 비인증 `{ initialized: boolean }` | + +## 관리자 콘솔 + +[**devslab-kit-admin-ui**](https://github.com/devslab-kr/devslab-kit-admin-ui)는 +이 REST API 위에 바로 올린 Vue 3 + PrimeVue 콘솔입니다 — 로그인, 모든 엔티티 화면, +ABAC 정책 테스트, 감사 로그 검색, 진단, 실시간 설정 뷰까지 모두 한/영 양 언어. 그대로 +쓰거나 직접 UI를 만들 때 참고하세요. ## 설계 원칙 -1. **제품에 독립적.** `bookrecord`(또는 다른 어떤 제품)의 도메인 타입도 `devslab-kit`에 - 들어오지 않는다. `UserId`, `TenantId`, `Permission`, `Role`, `Menu`, `Audit` 같은 - 플랫폼 개념만 둔다. -2. **계약은 Java API다.** GraphQL, WebFlux, RabbitMQ, OAuth2, Spring Session은 모두 - **선택형 starter**이며 절대 core에 넣지 않는다. -3. **AutoConfiguration은 override 친화적.** 모든 기본 Bean은 `@ConditionalOnMissingBean`이라 - 소비 앱이 kit을 fork하지 않고도 교체할 수 있다. -4. **TenantContext는 항상 존재한다** — 싱글 테넌트라도 default tenant를 resolve하지, - 추상화를 건너뛰지 않는다. -5. **권한은 메뉴를 모른다.** 메뉴는 권한을 참조할 수 있지만, 그 반대 방향 의존은 없다. -6. **인증 계정 ≠ 서비스 프로필.** `platform_user_account`는 로그인 / 상태 / 테넌시만 갖는다. - 닉네임, 아바타, 취향 같은 제품별 프로필 데이터는 제품 테이블에 둔다. -7. **GraalVM Native 친화적.** 리플렉션 중심 설계를 피하고, 샘플 앱이 `nativeCompile`을 - end-to-end로 검증한다. - -## 예정 모듈 구조 - -```text -devslab-kit/ -├─ devslab-kit-bom -├─ devslab-kit-core -├─ devslab-kit-identity-{api,core} -├─ devslab-kit-access-{api,core} -├─ devslab-kit-tenant-{api,core} -├─ devslab-kit-menu-{api,core} -├─ devslab-kit-audit-{api,core} -├─ devslab-kit-autoconfigure -├─ devslab-kit-spring-boot-starter -├─ devslab-kit-admin-{api,ui,starter} -├─ devslab-kit-test-support -└─ devslab-kit-sample-app -``` +1. **제품에 독립적.** 제품 도메인 타입은 들어오지 않고, 플랫폼 개념만 둡니다. +2. **계약은 Java API.** GraphQL, WebFlux, RabbitMQ, OAuth2, Spring Session은 선택형 추가 기능이며 절대 core가 아닙니다. +3. **Override 친화적 자동 구성.** 모든 기본 빈은 `@ConditionalOnMissingBean`. +4. **TenantContext는 항상 존재** — 싱글 테넌트라도 default를 resolve합니다. +5. **권한은 메뉴를 모름.** 메뉴는 권한을 참조할 수 있지만 그 반대 의존은 없습니다. +6. **인증 계정 ≠ 서비스 프로필.** 플랫폼 계정은 로그인 / 상태 / 테넌시만 갖고, 제품별 프로필 데이터는 제품 테이블에 둡니다. +7. **GraalVM Native 친화적.** 리플렉션 중심 설계를 피합니다. -선택형 adapter starter (필요할 때 추가, 기본 starter에는 들어가지 않음): +## 문서 -```text -devslab-kit-graphql-dgs-starter -devslab-kit-webflux-starter -devslab-kit-rabbitmq-starter -devslab-kit-spring-session-starter -devslab-kit-oauth2-{client,resource-server}-starter -``` +- **아키텍처 결정 기록** — [`docs/adr`](docs/adr): ADR 0001(최초 관리자 부트스트랩), + ADR 0002(플러그형 캐시). 한/영 양 언어. +- **변경 이력** — [`CHANGELOG.ko.md`](CHANGELOG.ko.md) ([English](CHANGELOG.md)). -## 로컬 실행 +## 소스에서 빌드 ```bash -./gradlew test -./gradlew bootRun +./gradlew build # 컴파일 + 테스트 (Testcontainers: Postgres + Redis; Docker 필요) +./gradlew publishToMavenLocal # 모든 모듈을 ~/.m2 에 설치 +./gradlew :devslab-kit-sample-app:bootRun # 참조 앱 실행 ``` -Docker Compose(`compose.yaml`)는 Spring Boot의 `spring-boot-docker-compose` 통합으로 -`bootRun` 시에 PostgreSQL과 Redis를 자동 기동한다. 테스트는 `@ServiceConnection` 기반 -Testcontainers로 동작한다. +Java 21(빌드는 GraalVM 21 toolchain 사용)과 통합 테스트용으로 실행 중인 Docker가 +필요합니다. + +## 버전 정책 + +라이브러리 메이저는 Spring Boot 메이저와 정렬됩니다: **`4.x.y`는 Spring Boot 4.x를 +대상으로** 합니다. 릴리스는 [유의적 버전](https://semver.org/lang/ko/)을 따릅니다. +마이그레이션 노트는 [변경 이력](CHANGELOG.ko.md)을 참고하세요. ## 라이선스 diff --git a/README.md b/README.md index ab5f074..dedd035 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,231 @@ # devslab-kit -[한국어 README](README.ko.md) - -A reusable platform Spring Boot Starter for DevsLab products. - -`devslab-kit` provides cross-product platform building blocks — authentication, accounts, -permissions, roles, multi/single tenancy, menus, audit logs, an admin API, and Spring Boot -AutoConfiguration — so that each product can focus on its own domain. - -The kit deliberately does **not** depend on any specific product domain. Its first real consumer -is the `bookrecord` service, but `devslab-kit` itself must remain product-agnostic. +[![Maven Central](https://img.shields.io/maven-central/v/kr.devslab/devslab-kit-spring-boot-starter?logo=apachemaven)](https://central.sonatype.com/artifact/kr.devslab/devslab-kit-spring-boot-starter) +[![Build](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml/badge.svg)](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) +![Java](https://img.shields.io/badge/Java-21%2B-orange) +![Spring Boot](https://img.shields.io/badge/Spring%20Boot-4.0-6DB33F?logo=springboot) + +[한국어 README](README.ko.md) · [Changelog](CHANGELOG.md) · [ADRs](docs/adr) + +A reusable **Spring Boot 4 platform starter**. Drop it into an application and get +authentication, authorization, multi-tenancy, dynamic menus and audit logging +from auto-configuration — plus an admin REST API and a ready-made admin console — +so each product can focus on its own domain instead of rebuilding the platform +layer every time. + +`devslab-kit` is deliberately **product-agnostic**: it knows only platform +concepts (`UserId`, `TenantId`, `Permission`, `Role`, `Menu`, `Audit`), never a +specific product's domain. + +> **Status — pre-1.0.** The platform is feature-complete for `0.1.0`, the first +> public release. Artifacts publish to Maven Central from `0.1.0` onward; until +> then, build from source or use `publishToMavenLocal`. + +## Contents + +- [Features](#features) · [Requirements](#requirements) · [Install](#install) +- [Quick start](#quick-start) · [Configuration](#configuration) +- [Modules](#modules) · [Admin REST API](#admin-rest-api) · [Admin console](#admin-console) +- [Design principles](#design-principles) · [Documentation](#documentation) +- [Building from source](#building-from-source) · [Versioning](#versioning) · [License](#license) + +## Features + +| Area | What you get | +| --- | --- | +| **Identity** | User accounts, BCrypt credentials, JWT issue/parse, configurable login lockout, forced password change. | +| **Access** | Roles, permissions, subject **groups**, and an **ABAC** policy SPI (`PolicyEvaluator`) layered on top of RBAC. | +| **Multi-tenancy** | A tenant context that is *always present* (single-tenant resolves a default rather than skipping the abstraction), with pluggable resolvers: `fixed`, `header`, `jwt`, `subdomain`; `single` and `multi` modes. | +| **Menus** | Permission-filtered dynamic menu trees, computed per user. | +| **Audit** | Asynchronous audit logging through `ApplicationEventPublisher`, persisted to PostgreSQL (JSONB metadata). | +| **Cache** | A pluggable cache — `in-memory`, `redis`, or `none`. The Redis backend owns JSON serialization, so you never implement `Serializable` or wire a serializer (ADR 0002). The per-user menu cache rides this shared manager. | +| **First-admin bootstrap** | Idempotently provisions a tenant, a `PLATFORM_ADMIN` role, the `admin.*` permissions, and an admin user on first boot — opt-in and property-driven (ADR 0001). | +| **Admin REST API** | `/admin/api/v1/**` for every entity above, plus diagnostics and a live settings view. | +| **Override-friendly** | Every default bean is `@ConditionalOnMissingBean` — replace any piece by declaring your own. | +| **GraalVM Native** | Reflection-heavy patterns are avoided; the sample app verifies `nativeCompile`. | + +## Requirements + +| | | +| --- | --- | +| Java | 21+ | +| Spring Boot | 4.0+ | +| Datastore | PostgreSQL (primary; Flyway-migrated) | +| Cache | Redis (optional — only when `cache.type = redis`) | +| Web stack | Spring Web MVC (Servlet) + Spring Security | + +## Install + +> Available on Maven Central from `0.1.0`. The starter pulls in the whole platform. + +**Gradle (Kotlin DSL)** + +```kotlin +implementation("kr.devslab:devslab-kit-spring-boot-starter:0.1.0") +``` -## Status +**Maven** -> **Pre-`0.1.0` scaffold.** The repository currently holds the initial Spring Boot 4 project -> generated from IntelliJ's New Project wizard, plus org-wide conventions (license, changelog, -> bilingual README). Multi-module split, public contracts, AutoConfiguration, and the sample app -> are tracked in [`CHANGELOG.md`](CHANGELOG.md) and land in subsequent PRs. +```xml + + kr.devslab + devslab-kit-spring-boot-starter + 0.1.0 + +``` -## Tech stack +Prefer à la carte? Depend on an individual module (e.g. `devslab-kit-access-core`, +or just the `devslab-kit-access-api` contract to supply your own implementation). + +## Quick start + +**1. Add the starter** (above). + +**2. Configure** a datasource and the platform: + +```yaml +spring: + datasource: + url: jdbc:postgresql://localhost:5432/app + username: app + password: app + data: + redis: + host: localhost # only needed when cache.type = redis + +devslab: + kit: + tenant: + mode: single # single | multi + resolver: fixed # fixed | header | jwt | subdomain + default-tenant-id: default + identity: + jwt: + secret: ${DEVSLAB_JWT_SECRET} # 32+ bytes for HS256 — set in prod + ttl: PT8H + max-failed-attempts: 5 # lock the account after N failures + lockout-duration: PT15M + cache: + type: in-memory # in-memory | redis | none + bootstrap: + enabled: true # provision the first admin on first boot +``` -| Layer | Choice | -| ---------------- | ---------------------------------------------------------- | -| Language | Java 25 | -| Framework | Spring Boot 4.x | -| Build | Gradle (Kotlin DSL) | -| Group / Package | `kr.devslab` / `kr.devslab.kit` | -| Web stack | Spring Web MVC (Servlet) — WebFlux is **not** in core | -| Security | Spring Security (Servlet) | -| Persistence | Spring Data JPA + Flyway + PostgreSQL | -| Cache / session | Spring Data Redis (Spring Session is **not** in core yet) | -| Observability | Spring Boot Actuator | -| Native | GraalVM Native Build Tools | -| Local dev / test | Docker Compose + Testcontainers (PostgreSQL, Redis) | +**3. Boot the app.** The bootstrap seeds a `PLATFORM_ADMIN`, the admin REST API +goes live at `/admin/api/v1/**`, and Flyway creates the `platform_*` tables. Point +the [admin console](#admin-console) at it to log in. + +A complete, runnable configuration (with Docker Compose for Postgres + Redis and +Testcontainers-backed tests) lives in +[`devslab-kit-sample-app`](devslab-kit-sample-app) — see +[its README](devslab-kit-sample-app/README.md). + +## Configuration + +All keys are under the `devslab.kit.*` prefix. Defaults shown. + +| Key | Default | Notes | +| --- | --- | --- | +| `tenant.enabled` | `true` | Master switch for the tenant layer. | +| `tenant.mode` | `single` | `single` or `multi`. | +| `tenant.default-tenant-id` | `default` | Tenant used in single mode / as fallback. | +| `tenant.resolver` | `fixed` | `fixed` · `header` · `jwt` · `subdomain`. | +| `identity.jwt.secret` | — | 32+ byte HS256 key. **Required in production.** | +| `identity.jwt.issuer` | `devslab-kit` | JWT `iss` claim. | +| `identity.jwt.ttl` | `PT8H` | Token lifetime (ISO-8601 duration). | +| `identity.max-failed-attempts` | `5` | Lock the account after this many failures. | +| `identity.lockout-duration` | `PT15M` | How long an account stays locked. | +| `audit.enabled` | `true` | Toggle audit logging. | +| `audit.async-queue-capacity` | `1024` | Bounded queue for the async publisher. | +| `menu.enabled` | `true` | Toggle dynamic menus. | +| `cache.type` | `in-memory` | `in-memory` · `redis` · `none`. | +| `cache.ttl` | `PT10M` | Entry TTL (used by the Redis backend). | +| `cache.key-prefix` | `devslab:` | Redis key namespace. | +| `cache.allowed-package` | `kr.devslab` | Allow-list for safe polymorphic JSON typing. | +| `bootstrap.enabled` | `false` | Provision the first admin on first boot. | +| `bootstrap.admin-login-id` | `admin` | First admin login id. | +| `bootstrap.admin-password` | — | Blank → a strong random one is logged once. | +| `bootstrap.must-change-password` | `true` | Force a rotation on first login. | + +The live, effective values are also viewable at runtime via +`GET /admin/api/v1/settings` (secrets masked). + +## Modules + +| Module | Purpose | +| --- | --- | +| `devslab-kit-core` | Shared value objects (`TenantId`, `UserId`, `PublicId`, …) | +| `devslab-kit-tenant-{api,core}` | Tenant context + resolvers | +| `devslab-kit-identity-{api,core}` | Users, credentials, JWT, login lockout | +| `devslab-kit-access-{api,core}` | Roles, permissions, groups, ABAC policy engine | +| `devslab-kit-menu-{api,core}` | Permission-filtered dynamic menus | +| `devslab-kit-audit-{api,core}` | Async audit logging | +| `devslab-kit-cache-{api,core}` | Pluggable cache (in-memory / Redis) | +| `devslab-kit-admin-api` | Admin REST endpoints | +| `devslab-kit-autoconfigure` | Spring Boot auto-configuration | +| `devslab-kit-spring-boot-starter` | The starter — pulls in the whole platform | +| `devslab-kit-sample-app` | Runnable reference app + integration harness (not published) | + +**`-api` vs `-core`.** Each capability is split into a thin contract module +(`-api`) and a default implementation (`-core`). Depend on a `-core` for the +batteries-included default, or on an `-api` alone to plug in your own — the +auto-configuration backs off (`@ConditionalOnMissingBean`) when you do. + +## Admin REST API + +All under `/admin/api/v1`: + +| Resource | Endpoints | +| --- | --- | +| `auth` | login, change password | +| `users` · `roles` · `permissions` · `groups` | full CRUD + assignments | +| `menus` · `tenants` | manage menu trees and tenants | +| `policies` | list ABAC policies + dry-run a `(subject, action, resource)` tuple | +| `audit-logs` | search/filter the audit trail | +| `diagnostics` | read-only login / permission / menu-visibility probes | +| `settings` | live `devslab.kit.*` view (secrets masked) | +| `bootstrap/status` | unauthenticated `{ initialized: boolean }` for first-run flows | + +## Admin console + +[**devslab-kit-admin-ui**](https://github.com/devslab-kr/devslab-kit-admin-ui) is +a Vue 3 + PrimeVue console built directly on this REST API — login, the full set +of entity screens, ABAC policy testing, audit-log search, diagnostics and a live +settings view, all bilingual (en/ko). Use it as-is or as a reference for your own. ## Design principles -1. **Product-agnostic.** No `bookrecord` (or any other product) domain type may live in - `devslab-kit`. Only platform concepts: `UserId`, `TenantId`, `Permission`, `Role`, `Menu`, - `Audit`, etc. -2. **Java APIs are the contract.** GraphQL, WebFlux, RabbitMQ, OAuth2, Spring Session are - **optional starters**, never core. -3. **Override-friendly AutoConfiguration.** Every default bean is `@ConditionalOnMissingBean` - so consumer apps can replace them without forking the kit. -4. **TenantContext is always present** — even single-tenant deployments resolve a default tenant - rather than skipping the abstraction. -5. **Permission knows nothing about Menu.** Menu may reference permissions; the dependency does - not reverse. -6. **Auth account ≠ service profile.** `platform_user_account` holds login / status / tenancy - only. Per-product profile data (nickname, avatar, preferences) lives in product tables. -7. **GraalVM Native friendly.** Reflection-heavy patterns are avoided; the sample app verifies - `nativeCompile` end-to-end. - -## Planned module layout - -```text -devslab-kit/ -├─ devslab-kit-bom -├─ devslab-kit-core -├─ devslab-kit-identity-{api,core} -├─ devslab-kit-access-{api,core} -├─ devslab-kit-tenant-{api,core} -├─ devslab-kit-menu-{api,core} -├─ devslab-kit-audit-{api,core} -├─ devslab-kit-autoconfigure -├─ devslab-kit-spring-boot-starter -├─ devslab-kit-admin-{api,ui,starter} -├─ devslab-kit-test-support -└─ devslab-kit-sample-app -``` +1. **Product-agnostic.** Only platform concepts live here — never a product's domain types. +2. **Java APIs are the contract.** GraphQL, WebFlux, RabbitMQ, OAuth2, Spring Session are optional add-ons, never core. +3. **Override-friendly auto-configuration.** Every default bean is `@ConditionalOnMissingBean`. +4. **TenantContext is always present** — even single-tenant resolves a default. +5. **Permission knows nothing about Menu.** Menus may reference permissions; the dependency never reverses. +6. **Auth account ≠ service profile.** The platform account holds login / status / tenancy only; per-product profile data lives in product tables. +7. **GraalVM Native friendly.** Reflection-heavy patterns are avoided. -Optional adapter starters (added on demand, not part of the default starter): +## Documentation -```text -devslab-kit-graphql-dgs-starter -devslab-kit-webflux-starter -devslab-kit-rabbitmq-starter -devslab-kit-spring-session-starter -devslab-kit-oauth2-{client,resource-server}-starter -``` +- **Architecture decisions** — [`docs/adr`](docs/adr): ADR 0001 (first-admin + bootstrap), ADR 0002 (pluggable cache). Bilingual (en + ko). +- **Changelog** — [`CHANGELOG.md`](CHANGELOG.md) ([한국어](CHANGELOG.ko.md)). -## Running locally +## Building from source ```bash -./gradlew test -./gradlew bootRun +./gradlew build # compile + test (Testcontainers: Postgres + Redis; needs Docker) +./gradlew publishToMavenLocal # install all modules to ~/.m2 +./gradlew :devslab-kit-sample-app:bootRun # run the reference app ``` -Docker Compose (`compose.yaml`) brings up PostgreSQL and Redis automatically via Spring Boot's -`spring-boot-docker-compose` integration during `bootRun`. Tests use Testcontainers via -`@ServiceConnection`. +Java 21 (the build uses a GraalVM 21 toolchain) and a running Docker for the +integration tests. + +## Versioning + +The library major aligns with the Spring Boot major: **`4.x.y` targets Spring +Boot 4.x**. Releases follow [Semantic Versioning](https://semver.org/); see the +[changelog](CHANGELOG.md) for migration notes. ## License diff --git a/devslab-kit-sample-app/README.ko.md b/devslab-kit-sample-app/README.ko.md new file mode 100644 index 0000000..d37ca50 --- /dev/null +++ b/devslab-kit-sample-app/README.ko.md @@ -0,0 +1,76 @@ +# devslab-kit-sample-app + +[English](README.md) + +`devslab-kit-spring-boot-starter`를 사용하는 작은 Spring Boot 애플리케이션입니다. +**Maven Central에 배포하지 않습니다** — 의존 대상이 아니라 라이브러리를 떠받치기 +위해 존재합니다. + +## 이 모듈이 존재하는 이유 + +### 1. 통합 테스트 하니스 (이 repo 안에 두는 핵심 이유) + +스타터가 **소비자가 실제로 쓰는 방식 그대로** 동작하는지 — 단지 컴파일되는지가 +아니라 — 증명하는 자리입니다. **실제 PostgreSQL과 Redis**(Testcontainers)를 상대로 +**전체 자동 구성**을 부팅해 플랫폼이 통째로 배선되는지 검증합니다: + +- `SampleApplicationTests`는 컨텍스트를 띄우고 모든 스타터 빈이 존재하고 쓸 수 + 있는지 확인합니다 — `TenantResolver`, `TenantContextHolder`, + `CurrentUserProvider`, `PasswordHasher`, `LocalLoginService`, + `PermissionChecker`, `MenuProvider`, `AuditEventPublisher` — 그리고 BCrypt 왕복. +- `BootstrapStatusEndpointTests`는 최초 관리자 부트스트랩(ADR 0001)과 관리자 REST + API 응답을 검증합니다. +- `TestcontainersConfiguration`은 Postgres + Redis를 띄워 `@ServiceConnection`으로 + 연결합니다. + +단위 테스트로는 이 부류의 문제를 잡을 수 없습니다 — 자동 구성 순서, Spring Boot +BOM, 실제 방언 위의 JPA/Flyway, JSONB 바인딩, Redis JSON 직렬화는 전체가 실제 +인프라를 상대로 부팅될 때만 깨집니다. 실제 버그 여럿(예: Redis 캐시 직렬화기, JWT +시계 처리)이 여기서 먼저 드러났습니다. **CI가 이 모듈에 의존**하므로 repo 안에 +남습니다. + +### 2. 살아있는 레퍼런스 설정 + +[`src/main/resources/application.yaml`](src/main/resources/application.yaml)은 +스타터 설정의 완전히 동작하는 예시입니다 — datasource, Redis, 그리고 +`devslab.kit.*` 설정(테넌트 모드/리졸버, 캐시 타입, 감사, 최초 관리자 부트스트랩). +실제 앱의 출발점으로 복사해 쓰세요. + +### 3. 로컬 개발 플레이그라운드 & GraalVM 네이티브 타깃 + +실행해서 관리자 REST API를 둘러볼 수 있고 +([devslab-kit-admin-ui](https://github.com/devslab-kr/devslab-kit-admin-ui)를 +연결해 로그인), `nativeCompile`을 end-to-end로 검증하는 모듈이기도 합니다. + +## 실행 + +### 테스트 (Docker 외 별도 설정 불필요) + +```bash +./gradlew :devslab-kit-sample-app:test +``` + +Testcontainers가 일회용 Postgres + Redis를 자동으로 띄웁니다. 실행 중인 Docker만 +있으면 됩니다. + +### bootRun (Docker만 있으면 됨 — compose 자동) + +이 모듈은 [`compose.yaml`](compose.yaml)(Postgres + Redis)을 포함하고 +`spring-boot-docker-compose`에 의존하므로, `bootRun`이 그 컨테이너를 띄우고 연결까지 +자동으로 배선합니다. 실행 중인 Docker만 있으면 됩니다: + +```bash +./gradlew :devslab-kit-sample-app:bootRun +``` + +`application.yaml`의 `DEVSLAB_*` 기본값(localhost Postgres/Redis)은 대신 *외부* +데이터 저장소를 가리킬 때의 폴백입니다 — 예: CI/스테이징용 +`DEVSLAB_DATASOURCE_URL`, `DEVSLAB_REDIS_HOST`, `DEVSLAB_CACHE_TYPE`. +부트스트랩은 로컬 편의를 위해 비밀번호 강제 변경을 끈 `admin`/`admin`을 시드합니다 — +운영에서는 이 형태를 쓰지 마세요. + +## Maven Central에 없음 — 의도된 것 + +이 모듈은 배포에서 제외됩니다 (루트 `build.gradle.kts`의 `nonPublishedModules`). +테스트/레퍼런스 하니스이며, 라이브러리 모듈만 배포됩니다. 사용자용 데모가 필요하면 +여기가 아니라 [devslab-examples](https://github.com/devslab-kr)에 둡니다. diff --git a/devslab-kit-sample-app/README.md b/devslab-kit-sample-app/README.md new file mode 100644 index 0000000..df35702 --- /dev/null +++ b/devslab-kit-sample-app/README.md @@ -0,0 +1,80 @@ +# devslab-kit-sample-app + +[한국어](README.ko.md) + +A small Spring Boot application that consumes `devslab-kit-spring-boot-starter`. +It is **not published to Maven Central** — it exists to serve the library, not to +be depended on. + +## Why this module exists + +### 1. Integration test harness (the main reason it's kept in this repo) + +This is where we prove the starter actually works *as a consumer would use it* — +not just that the code compiles. It boots the **full auto-configuration** against +**real PostgreSQL and Redis** (via Testcontainers) and asserts the whole platform +wires up: + +- `SampleApplicationTests` brings up the context and checks every starter bean is + present and usable — `TenantResolver`, `TenantContextHolder`, + `CurrentUserProvider`, `PasswordHasher`, `LocalLoginService`, + `PermissionChecker`, `MenuProvider`, `AuditEventPublisher` — plus a BCrypt + round-trip. +- `BootstrapStatusEndpointTests` verifies the first-admin bootstrap (ADR 0001) + and that the admin REST API responds. +- `TestcontainersConfiguration` stands up Postgres + Redis and wires them in with + `@ServiceConnection`. + +Unit tests can't catch this class of problem — auto-configuration ordering, the +Spring Boot BOM, JPA/Flyway against a real dialect, JSONB binding, and Redis JSON +(de)serialization only fail when the whole thing boots against real +infrastructure. Several real bugs (e.g. the Redis cache serializer and the JWT +clock handling) surfaced here first. **CI depends on this module**, so it stays +in-repo. + +### 2. Living reference configuration + +[`src/main/resources/application.yaml`](src/main/resources/application.yaml) is a +complete, working example of how to configure the starter — datasource, Redis, +and the `devslab.kit.*` knobs (tenant mode/resolver, cache type, audit, +first-admin bootstrap). Copy it as a starting point for a real app. + +### 3. Local-dev playground & GraalVM native target + +Run it to click around the admin REST API (point +[devslab-kit-admin-ui](https://github.com/devslab-kr/devslab-kit-admin-ui) at it +to log in), and it's the module used to verify `nativeCompile` end-to-end. + +## Running + +### Tests (no setup beyond Docker) + +```bash +./gradlew :devslab-kit-sample-app:test +``` + +Testcontainers starts throwaway Postgres + Redis automatically; you only need a +running Docker. + +### bootRun (Docker only — compose is automatic) + +The module ships a [`compose.yaml`](compose.yaml) (Postgres + Redis) and depends +on `spring-boot-docker-compose`, so `bootRun` starts those containers and wires +the connection automatically. You just need Docker running: + +```bash +./gradlew :devslab-kit-sample-app:bootRun +``` + +The `DEVSLAB_*` defaults in `application.yaml` (localhost Postgres/Redis) are the +fallback for pointing at an *external* datastore instead — e.g. +`DEVSLAB_DATASOURCE_URL`, `DEVSLAB_REDIS_HOST`, `DEVSLAB_CACHE_TYPE` for CI or +staging. The bootstrap seeds `admin`/`admin` with the forced password change off +for local convenience — do **not** use that shape in production. + +## Not on Maven Central — by design + +This module is excluded from publishing (`nonPublishedModules` in the root +`build.gradle.kts`). It's a test/reference harness; only the library modules ship. +A user-facing demo, if we add one, would live in +[devslab-examples](https://github.com/devslab-kr) rather than here.