English summary below. 한국어 문서는 그 아래에서 이어집니다.
auth-service is an OAuth2 / OIDC Identity Provider (IdP). Where the other
services in this portfolio act as resource servers that verify JWTs, this
service is the issuer that mints them. It bundles JWT issuance, JWK
rotation, refresh-token rotation with reuse detection, RBAC, multi-tenancy,
TOTP-based 2FA, token introspection/revocation, and append-only audit into one
deployable.
- Stack — Kotlin · Spring Boot 3.5 · JVM 21 toolchain · Spring Authorization Server 1.5
- Storage — Postgres + Flyway + JPA · Redis (refresh reuse detection, rate limit, MFA challenge)
- Architecture — Hexagonal (ports & adapters), 6 Gradle modules
- Decisions — 18 ADRs in
docs/adr
| Capability | What it does |
|---|---|
| OAuth2 / OIDC | Spring Authorization Server exposes /oauth2/token, /oauth2/jwks, /.well-known/openid-configuration. First-party /api/v1/auth/* endpoints coexist. |
| JWK rotation | RS256 / RSA-2048, 24h cycle, previous key kept for one grace cycle (ADR-0003). |
| Refresh rotation + reuse detection | Only the SHA-256 hash is stored; replay of a rotated token force-revokes the family (5s grace window) (ADR-0004). |
| RBAC + ABAC | User → Role → Permission claims, plus OPA Rego policies with an embedded equivalence test (ADR-0005, ADR-0016). |
| Multi-tenancy | JWT tnt claim + tenant_id enforced on every query (ADR-0006). |
| 2FA — TOTP | RFC 6238, secret stored AES-GCM encrypted, challenge token consumed once (ADR-0007). |
| RFC 7662 Introspection | /oauth2/introspect — resource servers confirm a token's active state (ADR-0017). |
| RFC 7009 Revocation | /oauth2/revoke — admin force-revoke; access JWT goes to a Redis blocklist, refresh to REVOKED_BY_ADMIN (ADR-0018). |
| Append-only audit | Login attempts / security events written REQUIRES_NEW, masked PII (ADR-0008). |
The domain core has zero framework dependencies. Application use cases depend
only on ports (interfaces); concrete I/O lives in inbound/outbound
adapters. auth-bootstrap wires everything into a Spring Boot app, and
e2e-tests exercises the assembled system against Testcontainers.
flowchart TB
subgraph adapterIn["auth-adapter-in (inbound adapters)"]
REST["REST controllers<br/>/api/v1/*"]
AS["Spring Authorization Server<br/>/oauth2/*"]
end
subgraph application["auth-application (use cases + ports)"]
UC["11 use cases<br/>@Service / @Transactional"]
PIN["inbound ports"]
POUT["outbound ports"]
end
subgraph domain["auth-domain (pure Kotlin, 0 Spring)"]
DOM["User · Tenant · Role · Permission<br/>RefreshToken · MfaSecret · AuditEvent"]
end
subgraph adapterOut["auth-adapter-out (outbound adapters)"]
JPA["JPA / Postgres / Flyway"]
REDIS["Redis (reuse · rate-limit · MFA)"]
CRYPTO["TOTP · BCrypt · AES-GCM · Nimbus JOSE"]
OPA["OPA Rego / embedded ABAC"]
end
REST --> PIN
AS --> PIN
PIN --> UC
UC --> DOM
UC --> POUT
POUT -. implemented by .-> JPA
POUT -. implemented by .-> REDIS
POUT -. implemented by .-> CRYPTO
POUT -. implemented by .-> OPA
BOOT["auth-bootstrap<br/>Spring Boot main · JWK rotation · SecurityFilterChain · Flyway"]
E2E["e2e-tests<br/>Testcontainers Postgres + Redis"]
BOOT --> REST
BOOT --> UC
BOOT --> JPA
BOOT --> DOM
E2E --> BOOT
Dependency rule: arrows point inward toward the domain. The domain knows nothing about Spring, JPA, or Redis; adapters depend on ports, never the reverse.
make up # start infra (Postgres / Redis / Mailhog)
make run # in another shell: :auth-bootstrap bootRun on :8080
make demo # client_credentials issue -> call -> introspect -> revoke demo
make test # full verification (unit + integration + e2e)- OIDC discovery —
http://localhost:8080/.well-known/openid-configuration - JWKS —
http://localhost:8080/oauth2/jwks - Swagger UI —
http://localhost:8080/swagger-ui.html(needsAUTH_OPENAPI_ENABLED=true)
See Coverage for how the aggregated test-coverage report is built. The full Korean documentation follows.
OAuth2 / OIDC IdP. 다른 internal service 들이 JWT 를 검증하는 consumer 라면 이 서비스는 JWT 를 발행하는 issuer 입니다. JWT 발행, JWK rotation, refresh token rotation, RBAC, multi-tenant, 2FA, audit 를 한 묶음으로 제공합니다.
- Kotlin / Spring Boot 3.5 / JVM 21 toolchain / Spring Authorization Server 1.5
- Postgres + Flyway + JPA / Redis (refresh reuse 감지, rate limit, MFA challenge)
- 헥사고날 (ports & adapters)(= 핵심 로직을 가운데 두고 DB·Redis·웹은 콘센트(port)·플러그(adapter)로만 연결해, 바깥을 바꿔도 핵심 코드는 안 건드리는 구조) + 모듈 6개
- ADR 18개로 핵심 결정 정리 (docs/adr) — RBAC + ABAC (OPA), JWK rotation, Refresh reuse detection (+ grace)(= 장기 출입증을 쓸 때마다 새것으로 바꾸고, 이미 버린 옛것이 다시 들어오면 탈취로 의심해 그 사용자 세션을 전부 끊되, 회전 직후 5초·같은 IP 재시도는 정상 모바일로 봐주는 것), 2FA TOTP, Audit append-only, RFC 7662 Introspection, RFC 7009 Revocation 등
| 모듈 | 역할 |
|---|---|
auth-domain |
User / Tenant / Role / Permission / RefreshToken / MfaSecret / AuditEvent. Spring 의존성 0 |
auth-application |
11개 use case + in/out port. @Service / @Transactional 까지만 |
auth-adapter-out |
JPA / Redis / TOTP / BCrypt / AES-GCM / Nimbus JOSE 구현체 |
auth-adapter-in |
REST controller + Spring Authorization Server endpoint |
auth-bootstrap |
Spring Boot main + JWK rotation + SecurityFilterChain 조립 + Flyway |
e2e-tests |
Postgres + Redis Testcontainer 통합 시나리오 |
| use case | 핵심 |
|---|---|
RegisterUserUseCase |
email + password (BCrypt cost=12) + tenant. 메일 verification mock |
LoginUseCase |
bad credentials / locked / not-found 모두 동일한 응답으로 정보 누설을 차단. MFA 활성 사용자는 401 + mfaToken |
VerifyMfaUseCase |
TOTP 검증. challenge 토큰은 1회 consume (replay 차단). tenant context 보존 |
RefreshTokenUseCase |
rotation + reuse detection. 회전된 token 이 다시 들어오면 모든 세션을 강제 revoke (grace window 5초 — 같은 IP 의 mobile retry 보호) |
RevokeSessionUseCase |
사용자가 자신의 세션 목록에서 특정 세션을 즉시 revoke |
ListMySessionsUseCase |
활성 refresh 목록 (디바이스 / IP / 마지막 사용 시각) |
AssignRoleUseCase |
운영자가 사용자에 role 부여. cross-tenant role 부여 거부 |
AuditLoginAttemptsUseCase |
append-only audit. REQUIRES_NEW 로 호출 트랜잭션 rollback 무관 |
LinkOrCreateUserFromOidcUseCase |
Google OIDC consumer — 외부 IdP 사용자와 매핑 / 자동 가입 |
IntrospectTokenUseCase |
RFC 7662 — access JWT / refresh 의 active 여부 + claim 응답. 외부 issuer / 가짜 / revoke 모두 {active:false} |
RevokeTokenByAdminUseCase |
RFC 7009 — admin 의 강제 revoke. access JWT 는 Redis 블록리스트 (잔여 TTL), refresh 는 REVOKED_BY_ADMIN 상태 |
- Spring Authorization Server 1.5 —
/oauth2/token,/oauth2/jwks,/.well-known/openid-configuration자동 노출. 자체 first-party endpoint (/api/v1/auth/*) 와 공존. - JWK rotation — RS256 / RSA 2048. 24h cycle + previous 키 1 cycle grace (ADR-0003)
- Refresh token rotation + reuse detection — refresh rotation 패턴. SHA-256 hash 만 DB 보관, 평문 로그 / DB 금지 (ADR-0004)
- RBAC — User → Role → Permission. JWT claim 에
roles+permissions를 담음 (ADR-0005) - Multi-tenant 격리 — JWT
tntclaim + 모든 query 에 tenant_id 강제 (ADR-0006) - TOTP 2FA — RFC 6238, AES-GCM 암호화 secret. WebAuthn 후속 (ADR-0007)
- Append-only audit log — 보안 사고 사후 분석 (ADR-0008)
- Rate limit — bucket4j-lettuce CAS,
(IP, tenant, email)키. brute-force 차단
- 평문 비밀번호 / TOTP secret / refresh token 평문은 도메인 객체에 들어오지 않습니다.
- User:
passwordHash만, BCrypt 결과 (cost 12). - MfaSecret:
secretCipher만, AES-GCM 암호화. master key 는 환경변수 / KMS. - RefreshToken:
tokenHash(SHA-256) 만,unique제약 + 비관적 잠금.
- User:
- 도메인 객체
toString은 평문 / hash 모두 노출하지 않습니다 — 디버그 / 로그 / audit 어디서도 안전. - Email 은 PII 로 마스킹 (
alice@example.com→a***e@e***e.com). application.yml에 master key 평문 금지 —${AUTH_MFA_AES_KEY}placeholder 만 둡니다.audit_events테이블에는 평문 비밀이 절대 들어가지 않습니다 (호출자가 마스킹 책임).
make help로 전체 명령을 볼 수 있습니다. 가장 빠른 길:make up # 인프라 기동 (Postgres / Redis / Mailhog) make run # 다른 셸에서 auth 서비스 (:auth-bootstrap bootRun, :8080) make demo # client_credentials 발급 → 호출 → introspect → revoke 시연 make test # 전체 검증 (단위 + 통합 + e2e)
외부 인프라 없이 바로 띄우려면 (zero-infra):
./gradlew :auth-bootstrap:bootRun --args='--spring.profiles.active=dev'
dev프로파일(application-dev.yml)은 H2 in-memory + Redis/메일 health off + 인메모리 rate-limit 으로, Postgres / Redis / Mailhog 없이 컨텍스트가 뜨고 엔드포인트를 제공합니다.
# 로컬 개발 (Postgres + Redis + Mailhog + auth)
docker compose -f infrastructure/docker/docker-compose.yml up --build
# 또는 로컬 인프라만 띄우고 IDE 에서 :auth-bootstrap:bootRun
docker compose -f infrastructure/docker/docker-compose.yml up postgres redis mailhog
./gradlew :auth-bootstrap:bootRunOIDC discovery: http://localhost:8080/.well-known/openid-configuration
JWKS: http://localhost:8080/oauth2/jwks
OpenAPI UI: http://localhost:8080/swagger-ui.html (env AUTH_OPENAPI_ENABLED=true 필요)
Mailhog UI: http://localhost:8025
운영(production)에서는 OpenAPI / Swagger UI 노출을 끄는 것을 기본으로 합니다 (
AUTH_OPENAPI_ENABLED=false, default). IdP 의 endpoint 매핑 정보가 외부에 보일 이유가 없고, 내부 사용 시 사설망 / VPN 안에서만 접근하도록 합니다.
# 1) 회원가입
curl -X POST localhost:8080/api/v1/auth/register \
-H 'Content-Type: application/json' \
-d '{"tenantSlug":"acme","email":"alice@example.com","password":"longenoughpw1234"}'
# 2) 로그인 → access + refresh
curl -X POST localhost:8080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"tenantSlug":"acme","email":"alice@example.com","password":"longenoughpw1234"}'
# 3) refresh 회전
curl -X POST localhost:8080/api/v1/auth/refresh \
-H 'Content-Type: application/json' \
-d '{"refreshToken":"<plain>"}'
# 4) MFA 활성 사용자 — 1단계 (비밀번호) 응답에 mfaToken
curl -X POST localhost:8080/api/v1/auth/verify-mfa \
-H 'Content-Type: application/json' \
-d '{"mfaToken":"<from login>","code":"123456"}'
# 5) 내 세션 목록 / revoke
curl -H 'Authorization: Bearer <access>' localhost:8080/api/v1/me/sessions
curl -X DELETE -H 'Authorization: Bearer <access>' localhost:8080/api/v1/me/sessions/<sessionId>
# 6) Token Introspection (ADR-0017) — Resource Server 가 access / refresh 의 유효성을 직접 확인
curl -X POST localhost:8080/oauth2/introspect \
-u internal-service:internal-service-secret-change-me \
-d 'token=<access_or_refresh>&token_type_hint=access_token'
# 7) Token Revocation (ADR-0018) — 운영자 / 보안 콘솔이 강제 종료 (token.revoke scope 필요)
curl -X POST localhost:8080/oauth2/revoke \
-u internal-admin:internal-admin-secret-change-me \
-d 'token=<access_or_refresh>&token_type_hint=refresh_token'introspect 는 매 요청마다 IdP 왕복이라 Resource Server 측 cache 가 필수입니다. 권장값:
- TTL = 10초 — admin revoke (ADR-0018) 가 모든 노드에 반영되는 SLA 가 최대 10초. 더 길게 잡으면 사용자 정지 시나리오의 차단 지연이 길어집니다.
- 키 =
sha256(token)— 평문 토큰을 cache 키로 두지 않음 (메모리 dump 위험). - introspect 응답의
exp가 cache 만료보다 더 가까우면 그 시점까지만 cache.
./gradlew check # 모든 모듈 단위 + 통합 + e2e (Postgres + Redis 컨테이너)
./gradlew :auth-domain:test
./gradlew :auth-application:test
./gradlew :e2e-tests:test테스트 카운트 (실행되는 테스트 invocation 기준 — @ParameterizedTest 는 case 수만큼 전개):
| 모듈 | invocation | 비고 |
|---|---|---|
| domain | 26 | 순수 도메인 (Spring/Docker 불필요) |
| application | 58 | Mockito 단위 (Spring/Docker 불필요) |
| adapter-in | 13 | @WebMvcTest 슬라이스 |
| adapter-out | 46 | @Test 25 + OPA Rego ↔ embedded 동등성 @ParameterizedTest 21 case |
| bootstrap | 8 | 컨텍스트 부팅 / Hikari / readiness |
| e2e | 17 | OpenAPI spec 정확성 4 + JWK rotation 2 포함 |
adapter-out 의 46 은
@Test메서드 25개 + 단일@ParameterizedTest가 펼치는 21개 case (OpaRegoEquivalenceTest) 의 합입니다.grep -c '@Test'만으로는 25 로 보이니 주의하세요. 나머지 모듈은 모두@Test메서드 수와 invocation 수가 같습니다.
멀티모듈 통합 커버리지는 Gradle 내장 jacoco-report-aggregation 으로 각 모듈의
test.exec 를 모아 단일 리포트로 합산합니다 (build.gradle.kts).
# 통합 리포트 (Docker 필요 — adapter-out / bootstrap / e2e 가 Testcontainers 를 띄움)
./gradlew testCodeCoverageReport
# → build/reports/jacoco/testCodeCoverageReport/index.html (+ .xml)
# Docker 없이 빠르게 보고 싶을 때 — 순수 단위 모듈만
./gradlew :auth-domain:test :auth-domain:jacocoTestReport
./gradlew :auth-application:test :auth-application:jacocoTestReport
# → <module>/build/reports/jacoco/test/html/index.htmlCI 의 test 잡이 testCodeCoverageReport 를 실행하고 결과를 coverage-report
아티팩트로 업로드합니다. 배지는 커버리지 리포트 생성 수단(JaCoCo) 을 가리키며,
수치 배지는 호스팅 커버리지 서비스(Codecov 등) 연동 시 교체할 수 있습니다.
Kotlin 도메인 value object 는 컴파일러가 합성하는
Companion/toString/equalsaccessor 때문에 instruction 커버리지가 line 커버리지보다 낮게 나옵니다 — 합산 수치를 읽을 때 참고하세요.
Dockerfile— multi-stage (gradle build → temurin-21-jre-alpine), non-root, healthcheck.infrastructure/docker/docker-compose.yml— postgres / redis / mailhog / auth.infrastructure/k8s/— namespace / configmap / secret(skeleton) / deployment / service / hpa / pdb.readOnlyRootFilesystem,runAsNonRoot,seccomp RuntimeDefault,drop ALL caps.helm/auth-service/— 같은 manifest 의 Helm chart 버전. dev / staging / prod 환경 분기를 values 로 처리 (자세한 설명은 Deployment 절)..github/workflows/ci.yml—push/pull_request(main) + 수동workflow_dispatch. gradle wrapper 검증 →./gradlew check+ 통합 JaCoCo 커버리지 → docker buildx (no push) → helm lint..github/workflows/codeql.yml— CodeQL Java/Kotlin SAST. push / PR + 주 1회 정기 스캔..github/dependabot.yml— gradle / github-actions 의존성 주 1회 점검.
raw K8s manifest (infrastructure/k8s/) 와 동일한 모양을 Helm chart 로도 제공합니다.
환경별 분기를 values.yaml (dev) / values-prod.yaml (prod) 로 나눠 관리합니다.
# dev — 기본 values
helm install auth-service ./helm/auth-service \
--namespace auth --create-namespace
# prod — values-prod.yaml override + image tag / host 주입
helm upgrade auth-service ./helm/auth-service \
--namespace auth \
--values ./helm/auth-service/values-prod.yaml \
--set image.tag=v0.1.0 \
--set ingress.hosts[0].host=auth.your-domain.com
# 검증
helm lint ./helm/auth-service
helm template ./helm/auth-service --values ./helm/auth-service/values-prod.yamlprod 차이: replica 3 + HPA (cpu 70% / memory 80%, min 2 max 10), ingress + TLS, 보수적 probe
threshold, OPA sidecar (ADR-0016), JWK 는 KMS / Vault (secret.create=false + extraEnvFrom
로 외부 SealedSecret / ExternalSecret 참조). 자세한 사용법은
helm/auth-service/README.md 참고.
이 레포는 단독 IdP 가 아니라 10 레포 포트폴리오의 issuer 입니다. 다른 9 레포는 본 레포가
발급한 JWT 를 받아 resource server 로서 검증합니다 (JWK Set 공개 — /oauth2/jwks,
introspect — /oauth2/introspect, revoke — /oauth2/revoke). 프로필 README:
ssa1004/ssa1004.
| 레포 | 한 줄 소개 | 본 레포와의 관계 |
|---|---|---|
| auth-service | OAuth2 / OIDC IdP — JWT 발행 / JWK rotation / 2FA / introspect / revoke | 자신 (issuer) |
| bid-ask-marketplace | 주문 매칭 엔진 + 동시성 제어 | client_credentials 로 token 발급 후 주문 API 호출 |
| billing-platform | 사용량 집계 / 청구서 / 결제 게이트웨이 | client_credentials 로 token 발급 후 결제 API 호출 |
| gpu-job-orchestrator | GPU job 큐 / 스케줄러 | client_credentials 로 token 발급 후 job submit |
| search-service | 검색 색인 + 질의 (OpenSearch) | client_credentials 로 색인 갱신 / 질의 |
| notification-hub | 알림 fan-out (mail / push / slack) | client_credentials 로 알림 발송 trigger |
| realtime-feed-service | trade 이벤트 WebSocket / SSE 스트리밍 | Bearer JWT 로 스트림 구독 인증 |
| graphql-gateway | 도메인 API 를 묶는 GraphQL BFF | 전달받은 JWT 를 검증 후 하위 서비스로 전파 |
| security-log-search | 감사 로그 / 보안 이벤트 검색 | 본 레포의 audit log 를 SIEM outbox 로 수신 |
| commerce-ops | MSA + observability 플레이그라운드 | 모든 레포 metric / trace / log 의 통합 대시보드 |
sequenceDiagram
autonumber
participant C as Caller (bid-ask-marketplace 등)
participant A as auth-service (IdP)
participant R as Resource Server (해당 도메인)
C->>A: POST /oauth2/token (client_credentials, Basic auth)
A-->>C: access_token (RS256 JWT, exp=15m)
C->>R: 도메인 API + Authorization: Bearer <jwt>
R->>A: GET /oauth2/jwks (cached, kid 매칭 시 skip)
A-->>R: JWK Set
R-->>C: 200 OK (서명 + exp + scope 검증)
Note over R,A: 의심스러운 토큰은 /oauth2/introspect 로 즉시 active 여부 확인
Note over A: admin revoke 시 /oauth2/revoke → Redis 블록리스트 / refresh REVOKED_BY_ADMIN
검증 비용 절감을 위해 resource server 측은 JWK Set 을 캐싱하고 (kid mismatch 시에만 재조회), introspect 는 의심 / 강제 차단 시나리오에서만 호출합니다 (위 "Resource Server 측 introspection 가이드" 참고).
레포 안에서 client_credentials 발급 → 가상 resource server 호출 → introspect → revoke 까지
한 번에 돌려볼 수 있는 데모를 제공합니다.
# auth + demo resource server (nginx + JWKS 검증) 띄움
docker compose -f infrastructure/docker/docker-compose.integration.yml up -d --build
# 발급 / 호출 / introspect / revoke 시연
./scripts/integration-demo.sh데모 resource server 는 OpenResty + lua-resty-jwt 로 본 레포의 JWK Set 을 가져와 RS256
서명 / exp / iss 를 in-process 검증합니다. 외부 의존은 컨테이너 안에서 닫혀 있고 실 외부
API 호출은 없습니다.
k6 (JS 시나리오 부하 도구) 로 OAuth2 endpoint 부하 / refresh rotation
invariant 를 검증합니다. 자세한 시나리오 / 임계 / 측정 항목은
load/README.md.
| 시나리오 | endpoint | 패턴 | 임계 |
|---|---|---|---|
| token-issue | POST /oauth2/token (client_credentials) |
constant 500 req/s | p95 < 100ms |
| token-introspect | POST /oauth2/introspect (RFC 7662) |
constant 1000 req/s | p95 < 20ms (Redis 캐시 적중) |
| jwks-fetch | GET /oauth2/jwks |
constant 2000 req/s | p95 < 10ms (정적 + 캐싱) |
| login-refresh | POST /auth/login → /auth/refresh |
ramping 0 → 100 VU | login/refresh p95 < 150ms, err < 1% |
| refresh-reuse-detection | refresh 의도적 reuse | 1 VU N iterations | 두 번째 사용 → 401 + family revoke 100% |
brew install k6
# 단일 실행
k6 run load/k6/scenarios/token-issue.js
# 일괄 실행 (사용자 사전 register 포함)
./scripts/run-load.sh18개 ADR 로 핵심 결정을 정리했습니다. 각 ADR 는 결정의 배경 / 선택 / 근거 / 장단점을 짧게 정리하는 형식입니다.
어디부터 볼지 막막하면 docs/backend-skills-index.md — 이 레포의 패턴(JWK rotation, refresh reuse + grace, OPA ABAC, RFC 7662 / 7009 등)을 코드 → ADR(왜) → 이론(dev-lab) 으로 잇는 학습 인덱스 + 추천 학습 순서.
| 번호 | 제목 |
|---|---|
| 0001 | 헥사고날 + Spring Authorization Server 도입 |
| 0002 | RS256 vs EdDSA — JWT 서명 알고리즘 선택 |
| 0003 | JWK rotation 24h + grace period |
| 0004 | Refresh token rotation + reuse detection |
| 0005 | RBAC vs ABAC — RBAC 로 시작 |
| 0006 | Multi-tenant 격리 — JWT claim + query filter |
| 0007 | 2FA TOTP 선택 (WebAuthn 후속) |
| 0008 | Audit log append-only |
| 0009 | HikariCP 튜닝 + leak detection |
| 0010 | K8s 3종 probe + readiness coordinator |
| 0011 | Graceful shutdown — SIGTERM 처리 |
| 0012 | Audit log SIEM 아웃박스(= 보안 감사 기록을, 보낼 메시지를 같은 트랜잭션 안 '편지함' 테이블에 같이 적고 일꾼이 꺼내 보안 로그 분석 시스템(SIEM)으로 흘려보내 한쪽만 성공하는 사고를 막는 방식) |
| 0013 | Social login (OIDC) skeleton |
| 0014 | JWK 외부 KMS 추상화 |
| 0015 | Refresh reuse 의 grace window |
| 0016 | OPA 기반 ABAC 정책 엔진 도입 |
| 0017 | Token Introspection (RFC 7662) — JWT self-validate vs introspection |
| 0018 | Token Revocation (RFC 7009) — admin 강제 revoke 표준 endpoint |
- WebAuthn / passkey — 디바이스 등록 / 복구 흐름 (ADR-0007)
- ABAC — 정책 엔진 (OPA / Casbin) 도입 (ADR-0005)
- Social login — 추가 OIDC provider 확장 (ADR-0013)
- SAML 2.0 — 엔터프라이즈 SSO
- JWK 외부 KMS 주입 — k8s leader election + Vault dynamic secrets (ADR-0014)
- audit log SIEM sink — Kafka → ClickHouse / S3+Athena (ADR-0012)
- refresh token grace window 의 IP 매칭 정밀도 개선 (ADR-0015)