Skip to content

Repository files navigation

Insurance GA Compliance Core

Java Spring Boot MySQL License

English | 한국어

A backend portfolio project that reproduces contact-eligibility decisions and controls access to personal data at the customer touchpoints of an insurance General Agency (GA) and its call center.

The detailed design covers 58 tables and 28 target APIs. This repository implements the first delivery slice — customer, consent, contact restriction, and audit — as a working Java application. Rather than plain CRUD, it focuses on the controls that must not fail in an insurance sales system: tenant isolation, sensitive-data encryption, idempotency, the Transactional Outbox pattern, and fail-closed policy.

Project at a glance

Item Detail
Runtime Java 21, Spring Boot 4.1.0, Maven Wrapper
Persistence MySQL 8.4, Spring JDBC, Flyway
Security OAuth2 Resource Server, JWT permission/tenant claims, RBAC + tenant guard
Privacy AES-256-GCM, HMAC-SHA256 equality search, masked by default
Reliability Idempotency-Key, Outbox, audit hash chain, fail closed
Implemented 6 domains, 9 API operations, 58-table baseline
Verification 8 tests + MySQL 8.4.10 HTTP end-to-end flow

Architecture

System architecture

Areas where local consistency between domains matters live inside a modular monolith. Recording verification, STT, search, external integration, and data destruction are designed to scale out as Outbox-driven asynchronous workers.

com.company.insurance.sales
├── customer          customer creation, search, masking
├── privacy           current consent state and immutable events
├── contactpolicy     contact restrictions and eligibility policy
├── audit             audit queries
└── shared
    ├── security      JWT permissions, tenant claim checks, request context
    ├── persistence   encryption, HMAC search, tenant reference
    ├── integration   idempotency handling and transactional outbox
    └── web           RFC 9457 errors and correlation ID

The full logical model is available in the design artifacts below.

What is implemented

1. Tenant isolation

  • Every /v1/** request requires an X-Tenant-Id header.
  • The header is not a value the caller freely chooses — it is checked against the tenants claim in the JWT.
  • Repository SQL always includes tenant_id in its predicates, and only ULIDs are exposed externally.
  • Guessing another tenant's ULID or tampering with the header is blocked, and this is covered by HTTP integration tests.

2. Privacy-safe customer search

  • Customer names and phone numbers are encrypted with AES-256-GCM.
  • Tenant, resource, and field name are combined into the AAD to prevent ciphertext substitution attacks.
  • Search uses secret-keyed HMAC-SHA256 tokens instead of a plaintext index.
  • API responses return masked values only, such as 홍*동 and +821****5678.
  • Without production keys the application fails to start; no plaintext fallback is permitted.

3. Reproducible contact decision

Contact eligibility is composed in the following order.

ABSOLUTE BLOCK
  → EXTERNAL DNC
  → COMPANY OPT-OUT
  → CONSENT / LEGAL BASIS
  → CONTACT TIME
  → FREQUENCY
  → AGENT ELIGIBILITY
  → CAMPAIGN POLICY

Each decision is stored as a snapshot containing the policy version, the highest-priority reason, the evaluation timestamp, and the evidence ID. When the external Do-Not-Call system cannot be reached, the production default is UNAVAILABLE and marketing contact is resolved to DENY.

4. Transactional evidence

  • POSTs at risk of duplication store an Idempotency-Key plus a request hash and replay the identical response.
  • Reusing the same key with a different body returns 409 IDEMPOTENCY_CONFLICT.
  • Consent, opt-out, and contact decisions write business data and the Outbox event in a single transaction.
  • Sensitive reads and writes are recorded as audit events chaining the correlation ID and the previous event hash.
  • Registering an opt-out immediately removes scheduled follow-up tasks and campaign targets.

API scope

Method Endpoint Permission Description
POST /v1/customers customer.write Create an encrypted customer
GET /v1/customers customer.read HMAC-based customer search
GET /v1/customers/{customerId} customer.read Masked customer detail
POST /v1/customers/{customerId}/consents consent.write Register consent and its immutable event
GET /v1/customers/{customerId}/consents consent.read Read a customer's consent history
POST /v1/customers/{customerId}/contact-restrictions contact.write Register opt-out / contact restriction
GET /v1/customers/{customerId}/contact-restrictions contact.read Read contact restrictions
POST /v1/contact-eligibility/evaluations contact.evaluate Evaluate contact eligibility just before dialing
GET /v1/audit-events audit.read Cursor-based audit search

The full target OpenAPI 3.1 contract defines 28 operations spanning campaigns, CTI/recording, consultation, contracts, complaints, and data destruction. Later APIs are deliberately left out of the table above to keep the implemented scope distinct from the target contract.

Run locally

Requirements

  • Java 21
  • Docker Desktop with Compose v2

1. Run all tests

./mvnw verify

The default test run applies the full 58-table Flyway baseline to H2 in MySQL compatibility mode.

2. Verify against MySQL 8.4

./scripts/test-mysql.sh

The script waits for the MySQL container to become healthy, recreates an isolated insurance_ga_it database on every run, and then verifies over real HTTP the flow of customer creation → consent → contact allowed → opt-out → contact denied, along with idempotent replay and cross-tenant blocking. The insurance_ga local development database is left untouched. The container stays up after the test and can be stopped with:

docker compose down

3. Start the API

docker compose up -d --wait mysql
SPRING_PROFILES_ACTIVE=local ./mvnw spring-boot:run

The local profile idempotently seeds development reference data for tenant, organization, agent, and consent.

curl -i http://localhost:8080/v1/customers \
  -H 'Content-Type: application/json' \
  -H 'X-Tenant-Id: 01J00000000000000000000000' \
  -H 'Idempotency-Key: customer-demo-0001' \
  -d '{
    "partyType": "PERSON",
    "displayName": "홍길동",
    "mobileNumber": "010-1234-5678"
  }'

Header-based development authentication is enabled only in the local profile. If X-Actor-Id, X-Permissions, and X-Allowed-Tenants are omitted, the local agent and the default permissions for the implemented scope are used.

Verification evidence

Local verification results as of 2026-07-19.

Check Result
./mvnw verify 8 tests, 0 failures, 0 errors
MySQL image MySQL Community Server 8.4.10
Flyway V1__baseline.sql, success
Schema 58 design tables confirmed created
End-to-end customer → consent → ALLOW → opt-out → DENY passed
Evidence 5 outbox records and 5 audit events confirmed

GitHub Actions also runs the H2 verification and the MySQL 8.4 service-container verification as separate jobs.

Configuration

Use .env.example as a reference, and never commit production secrets to the repository.

Variable Purpose
DB_URL, DB_USERNAME, DB_PASSWORD Production database connection details
APP_DATA_KEY_BASE64 Base64 value of the AES-256 data key
APP_HMAC_KEY_BASE64 Base64 value of the search HMAC key
OAUTH2_JWK_SET_URI Production IdP JWKS URL
EXTERNAL_DNC_MODE ALLOW, DENY, UNAVAILABLE

The local profile is not used in production. The JWT permissions array is interpreted as API permissions, and the tenants array as the accessible tenants.

Design artifacts

Trade-offs and roadmap

  • Internal primary keys are BIGINT while external identifiers are ULIDs, trading off index efficiency against unguessability.
  • Complex queries use Spring JDBC for explicit SQL control, and API DTOs are kept separate from database models.
  • The external DNC integration is currently a mode-based adapter and fails closed on marketing requests until the production integration lands.
  • Recording originals are designed to be stored in S3-compatible object storage rather than as database BLOBs — design only at this stage.
  • Next up: CTI inbox, recording integrity verification, consultation, insurer contract synchronization, legal hold, and the destruction worker.

Disclaimer

This repository is a portfolio demonstrating architecture and backend implementation skills. It is not a product ready for real insurance sales operations or legal advice. Retention periods, consent wording, Do-Not-Call integration, outsourcing relationships, and access rights must all be reviewed against the laws in force at the time of adoption and by the company's compliance and privacy functions.

License

Code is available under the MIT License.


Insurance GA Compliance Core (한국어)

English | 한국어

보험 GA·콜센터의 고객 접점에서 연락 가능성 판단을 재현하고 개인정보 접근을 통제하는 백엔드 포트폴리오 프로젝트입니다.

58개 테이블과 28개 목표 API로 구성된 상세설계 중 첫 번째 구현 단계인 고객·동의·접촉 제한·감사 영역을 실제 동작하는 Java 애플리케이션으로 구현했습니다. 단순 CRUD보다 테넌트 격리, 민감정보 암호화, 멱등성, Transactional Outbox, Fail Closed 정책처럼 보험 영업 시스템에서 실패하면 안 되는 통제에 집중했습니다.

Project at a glance

항목 내용
Runtime Java 21, Spring Boot 4.1.0, Maven Wrapper
Persistence MySQL 8.4, Spring JDBC, Flyway
Security OAuth2 Resource Server, JWT permission/tenant claims, RBAC + tenant guard
Privacy AES-256-GCM, HMAC-SHA256 동등검색, 기본 마스킹
Reliability Idempotency-Key, Outbox, 감사 해시 체인, Fail Closed
Implemented 6개 도메인, 9개 API operation, 58개 테이블 기준선
Verification 8 tests + MySQL 8.4.10 HTTP end-to-end flow

Architecture

System architecture

도메인 간 로컬 정합성이 중요한 영역은 모듈형 모놀리스에 두고, 녹취 검증·STT·검색·외부 연계·파기는 Outbox 기반 비동기 워커로 확장하는 구조입니다.

com.company.insurance.sales
├── customer          고객 생성·검색·마스킹
├── privacy           동의 현재 상태와 불변 이벤트
├── contactpolicy     접촉 제한과 연락 가능성 정책
├── audit             감사 조회
└── shared
    ├── security      JWT 권한, tenant claim 검증, request context
    ├── persistence   암호화, HMAC 검색, tenant reference
    ├── integration   멱등 처리와 transactional outbox
    └── web           RFC 9457 오류와 correlation ID

전체 논리 모델은 다음 설계 자료에서 확인할 수 있습니다.

What is implemented

1. Tenant isolation

  • 모든 /v1/** 요청에 X-Tenant-Id를 요구합니다.
  • 헤더 값은 사용자가 자유롭게 선택하는 값이 아니라 JWT의 tenants claim과 대조합니다.
  • Repository SQL은 항상 tenant_id를 조건으로 포함하고, 외부에는 ULID만 노출합니다.
  • 타 테넌트 ULID를 추측하거나 헤더를 변조하는 흐름을 HTTP 통합 테스트에서 차단합니다.

2. Privacy-safe customer search

  • 고객명과 연락처 원문은 AES-256-GCM으로 암호화합니다.
  • 테넌트·리소스·필드명을 AAD로 결합해 암호문 교체 공격을 방지합니다.
  • 검색은 평문 인덱스 대신 비밀키 기반 HMAC-SHA256 토큰을 사용합니다.
  • API 응답에는 홍*동, +821****5678처럼 마스킹 값만 반환합니다.
  • 운영 키가 없으면 애플리케이션 시작을 실패시키며 평문 fallback을 허용하지 않습니다.

3. Reproducible contact decision

연락 가능성 평가는 다음 순서로 합성됩니다.

ABSOLUTE BLOCK
  → EXTERNAL DNC
  → COMPANY OPT-OUT
  → CONSENT / LEGAL BASIS
  → CONTACT TIME
  → FREQUENCY
  → AGENT ELIGIBILITY
  → CAMPAIGN POLICY

판단 결과에는 정책 버전, 최우선 사유, 평가시각, 증적 ID를 스냅샷으로 저장합니다. 외부 두낫콜 시스템을 확인할 수 없는 경우 운영 기본값은 UNAVAILABLE이며 마케팅 연락을 DENY 처리합니다.

4. Transactional evidence

  • 중복 위험 POST는 Idempotency-Key와 요청 해시를 저장하고 동일 응답을 재현합니다.
  • 같은 키를 다른 본문에 사용하면 409 IDEMPOTENCY_CONFLICT를 반환합니다.
  • 동의·수신거부·연락 판단은 업무 데이터와 Outbox 이벤트를 한 트랜잭션에 기록합니다.
  • 중요 조회·변경은 correlation ID와 이전 이벤트 해시를 연결한 감사 이벤트로 남깁니다.
  • 수신거부 등록 시 예약 후속업무와 캠페인 대상을 즉시 제외합니다.

API scope

Method Endpoint Permission Description
POST /v1/customers customer.write 암호화 고객 생성
GET /v1/customers customer.read HMAC 기반 고객 검색
GET /v1/customers/{customerId} customer.read 마스킹 고객 상세
POST /v1/customers/{customerId}/consents consent.write 동의와 불변 이벤트 등록
GET /v1/customers/{customerId}/consents consent.read 고객 동의 이력 조회
POST /v1/customers/{customerId}/contact-restrictions contact.write 수신거부·접촉 제한 등록
GET /v1/customers/{customerId}/contact-restrictions contact.read 접촉 제한 조회
POST /v1/contact-eligibility/evaluations contact.evaluate 발신 직전 연락 가능성 평가
GET /v1/audit-events audit.read 커서 기반 감사 검색

전체 목표 OpenAPI 3.1 계약은 캠페인·CTI/녹취·상담·계약·민원·파기까지 28개 operation을 정의합니다. 현재 구현 범위와 목표 계약을 구분하기 위해 후속 API는 위 표에 포함하지 않았습니다.

Run locally

Requirements

  • Java 21
  • Docker Desktop with Compose v2

1. Run all tests

./mvnw verify

기본 테스트는 H2의 MySQL 호환 모드에 전체 58개 테이블 Flyway 기준선을 적용합니다.

2. Verify against MySQL 8.4

./scripts/test-mysql.sh

스크립트는 MySQL 컨테이너를 healthy 상태까지 기다리고 격리된 insurance_ga_it 데이터베이스를 매번 새로 만든 후, 고객 생성 → 동의 → 연락 허용 → 수신거부 → 연락 차단 흐름과 멱등 재현·타 테넌트 차단을 실제 HTTP로 검증합니다. insurance_ga 로컬 개발 DB는 변경하지 않습니다. 테스트 후 컨테이너는 유지되며 다음 명령으로 중지할 수 있습니다.

docker compose down

3. Start the API

docker compose up -d --wait mysql
SPRING_PROFILES_ACTIVE=local ./mvnw spring-boot:run

로컬 프로필은 개발용 테넌트·조직·상담사·동의 기준정보를 멱등하게 준비합니다.

curl -i http://localhost:8080/v1/customers \
  -H 'Content-Type: application/json' \
  -H 'X-Tenant-Id: 01J00000000000000000000000' \
  -H 'Idempotency-Key: customer-demo-0001' \
  -d '{
    "partyType": "PERSON",
    "displayName": "홍길동",
    "mobileNumber": "010-1234-5678"
  }'

로컬 프로필에서만 헤더 기반 개발 인증이 활성화됩니다. X-Actor-Id, X-Permissions, X-Allowed-Tenants를 생략하면 로컬 상담사와 구현 범위의 기본 권한을 사용합니다.

Verification evidence

2026-07-19 기준 로컬 검증 결과입니다.

Check Result
./mvnw verify 8 tests, 0 failures, 0 errors
MySQL image MySQL Community Server 8.4.10
Flyway V1__baseline.sql, success
Schema 설계 테이블 58개 생성 확인
End-to-end customer → consent → ALLOW → opt-out → DENY 통과
Evidence Outbox 5건, audit event 5건 생성 확인

GitHub Actions도 H2 검증과 MySQL 8.4 서비스 컨테이너 검증을 별도 job으로 실행합니다.

Configuration

.env.example을 참고하되 운영 비밀값을 저장소에 커밋하지 않습니다.

Variable Purpose
DB_URL, DB_USERNAME, DB_PASSWORD 운영 DB 접속 정보
APP_DATA_KEY_BASE64 AES-256 데이터 키의 Base64 값
APP_HMAC_KEY_BASE64 검색 HMAC 키의 Base64 값
OAUTH2_JWK_SET_URI 운영 IdP JWKS URL
EXTERNAL_DNC_MODE ALLOW, DENY, UNAVAILABLE

운영 환경에서는 local 프로필을 사용하지 않습니다. JWT의 permissions 배열은 API 권한으로, tenants 배열은 접근 가능한 테넌트로 해석합니다.

Design artifacts

Trade-offs and roadmap

  • 내부 PK는 BIGINT, 외부 식별자는 ULID를 사용해 인덱스 효율과 추측 방지를 절충했습니다.
  • 복잡 조회는 SQL 제어가 명확한 Spring JDBC를 사용하고 API DTO와 DB 모델을 분리했습니다.
  • 현재 외부 DNC는 모드 기반 adapter이며 운영 연계 전까지 마케팅 요청을 Fail Closed 처리합니다.
  • 녹취 원본은 DB BLOB이 아니라 S3 호환 오브젝트 스토리지에 저장하도록 설계만 포함합니다.
  • 다음 단계는 CTI Inbox, 녹취 무결성 검증, 상담, 보험사 계약 동기화, Legal Hold와 파기 워커입니다.

Disclaimer

이 저장소는 아키텍처·백엔드 구현 역량을 보여주기 위한 포트폴리오이며 실제 보험 영업 또는 법률 자문에 바로 사용할 수 있는 제품이 아닙니다. 보존기간, 동의 문안, 두낫콜 연계, 위탁관계, 접근 권한은 적용 시점의 법령과 회사 준법·개인정보보호 검토를 거쳐야 합니다.

License

Code is available under the MIT License.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages