Appearance
멀티 서비스 회원 관리 아키텍처
NextMarket에서 여러 SaaS 서비스(aiagent, service-b, service-c 등)의 회원을 어떻게 관리할 것인가
1. 문제 정의
1.1 현재 상황
┌─────────────────────────────────────────────────────────────┐
│ NextMarket │
│ (결제 플랫폼 + 회원 관리) │
│ │
│ 회원 A ────────────────────────────────────────────────────│
│ └── aiagent 구독 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────┐
│ aiagent │ ← 현재는 1:1 관계로 단순
│ (회원 A) │
└─────────────┘1.2 확장 시 문제점
┌─────────────────────────────────────────────────────────────┐
│ NextMarket │
│ │
│ 회원 A ────────────────────────────────────────────────────│
│ ├── aiagent 구독 │
│ ├── analytics 구독 │
│ └── crm-tool 구독 │
└─────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ aiagent │ │ analytics│ │ crm-tool │
│ 회원 A? │ │ 회원 A? │ │ 회원 A? │
└──────────┘ └──────────┘ └──────────┘
❓ 각 서비스에 회원을 언제, 어떻게 생성할 것인가?
❓ 회원 정보 변경 시 어떻게 동기화할 것인가?
❓ 인증은 어디서 처리할 것인가?2. 아키텍처 옵션 비교
옵션 A: 중앙 집중형 (NextMarket이 IdP)
┌───────────────────────────────────────────────────────────────────┐
│ NextMarket (Identity Provider) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 회원 마스터 DB │ │
│ │ - 인증 정보 (email, password, token) │ │
│ │ - 기본 프로필 (name, phone) │ │
│ │ - 구독 정보 (어떤 서비스 구독 중인지) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ aiagent │ │ analytics│ │ crm-tool │
│ │ │ │ │ │
│ 서비스별 │ │ 서비스별 │ │ 서비스별 │
│ 프로필만 │ │ 프로필만 │ │ 프로필만 │
└──────────┘ └──────────┘ └──────────┘
- 로그인: NextMarket에서만
- 토큰 검증: 각 서비스가 NextMarket API 호출 또는 JWT 검증
- 회원 정보: NextMarket에서 조회장점:
- 단일 로그인 (SSO 가능)
- 회원 정보 일관성 보장
- 서비스 추가 시 회원 관리 부담 없음
단점:
- NextMarket 장애 시 전체 서비스 영향
- 서비스별 특수한 회원 정보 관리 어려움
- 기존 서비스(aiagent)의 회원 시스템 변경 필요
옵션 B: 분산형 (서비스별 독립 회원)
┌───────────────────────────────────────────────────────────────────┐
│ NextMarket │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 회원 DB (결제/구독 관리용) │ │
│ │ - 결제 정보 │ │
│ │ - 구독 정보 │ │
│ │ - 서비스별 회원 ID 매핑 테이블 │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
│ │ │
│ 구독 시 회원 │ │
│ 정보 전달 │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ aiagent │ │ analytics│ │ crm-tool │
│ │ │ │ │ │
│ 독립 회원 │ │ 독립 회원 │ │ 독립 회원 │
│ 독립 인증 │ │ 독립 인증 │ │ 독립 인증 │
└──────────┘ └──────────┘ └──────────┘
- 로그인: 각 서비스에서 개별 로그인
- 회원 생성: 구독 시 NextMarket → 서비스로 회원 정보 전달장점:
- 기존 서비스 구조 유지 가능
- 서비스별 독립성 보장
- 장애 격리
단점:
- 사용자가 서비스마다 별도 로그인 필요
- 회원 정보 동기화 복잡
- 비밀번호 관리 분산
옵션 C: 하이브리드 (권장)
┌───────────────────────────────────────────────────────────────────┐
│ NextMarket (인증 + 결제 플랫폼) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 회원 마스터 DB │ │
│ │ - 인증 정보 (email, password) │ │
│ │ - 기본 프로필 (name, phone, email) │ │
│ │ - 구독 정보 │ │
│ │ - 서비스별 회원 ID 매핑 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ JWT 발급 (서비스 접근 권한 포함) │
│ │ │
└──────────────────────────────┼────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ aiagent │ │ analytics│ │ crm-tool │
│ │ │ │ │ │
│ JWT 검증 │ │ JWT 검증 │ │ JWT 검증 │
│ 서비스별 │ │ 서비스별 │ │ 서비스별 │
│ 프로필 │ │ 프로필 │ │ 프로필 │
└──────────┘ └──────────┘ └──────────┘
- 인증: NextMarket에서 통합 처리
- JWT에 구독 중인 서비스 목록 포함
- 각 서비스는 JWT 검증 후 서비스별 추가 정보만 관리장점:
- 단일 로그인 (사용자 경험 향상)
- 서비스별 독립적인 확장 데이터 관리 가능
- 기존 서비스 최소 변경으로 적용 가능
단점:
- JWT 토큰 관리 복잡도 증가
- 서비스별 토큰 검증 로직 필요
3. 권장안: 하이브리드 아키텍처 상세
3.1 전체 흐름
┌─────────────────────────────────────────────────────────────────────────┐
│ 사용자 Journey │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. NextMarket 회원가입 │
│ └── CUSTOMERS 테이블에 저장 (마스터) │
│ │
│ 2. aiagent 상품 구독 결제 │
│ └── 결제 완료 시: │
│ a. SUBSCRIPTIONS 테이블에 구독 정보 저장 │
│ b. aiagent 서비스에 회원 프로비저닝 요청 │
│ c. SERVICE_MEMBER_MAPPING 테이블에 매핑 저장 │
│ │
│ 3. aiagent 서비스 접속 │
│ └── NextMarket 토큰으로 aiagent API 호출 │
│ └── aiagent가 토큰 검증 후 서비스 제공 │
│ │
│ 4. 회원 정보 변경 (NextMarket에서) │
│ └── 구독 중인 서비스들에 변경 사항 전파 │
│ │
└─────────────────────────────────────────────────────────────────────────┘3.2 데이터베이스 스키마
SERVICE_MEMBER_MAPPING (서비스별 회원 매핑)
sql
CREATE TABLE SERVICE_MEMBER_MAPPING (
ID BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
CUSTOMER_ID BIGINT NOT NULL COMMENT 'NextMarket 고객 ID',
SERVICE_CODE VARCHAR(50) NOT NULL COMMENT '서비스 코드',
-- 서비스측 회원 정보
SERVICE_MEMBER_ID VARCHAR(100) NULL COMMENT '서비스측 회원 ID',
SERVICE_MEMBER_CODE VARCHAR(100) NULL COMMENT '서비스측 회원 코드',
-- 프로비저닝 상태
PROVISION_STATUS ENUM('PENDING', 'PROVISIONED', 'FAILED', 'DEPROVISIONED')
DEFAULT 'PENDING',
PROVISION_TS DATETIME NULL COMMENT '프로비저닝 완료 시간',
DEPROVISION_TS DATETIME NULL COMMENT '디프로비저닝 시간',
-- 메타 정보
SERVICE_METADATA JSON NULL COMMENT '서비스별 추가 정보',
ERROR_MESSAGE TEXT NULL,
CREATE_TS DATETIME DEFAULT CURRENT_TIMESTAMP,
MODIFY_TS DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX IDX_CUSTOMER_SERVICE (CUSTOMER_ID, SERVICE_CODE),
INDEX IDX_SERVICE_MEMBER (SERVICE_CODE, SERVICE_MEMBER_ID),
INDEX IDX_PROVISION_STATUS (PROVISION_STATUS),
FOREIGN KEY (CUSTOMER_ID) REFERENCES CUSTOMERS(ID) ON DELETE CASCADE,
FOREIGN KEY (SERVICE_CODE) REFERENCES SERVICE_REGISTRY(SERVICE_CODE)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;예시 데이터:
| CUSTOMER_ID | SERVICE_CODE | SERVICE_MEMBER_ID | PROVISION_STATUS |
|---|---|---|---|
| 1001 | aiagent | MBR_12345 | PROVISIONED |
| 1001 | analytics | user_abc123 | PROVISIONED |
| 1002 | aiagent | MBR_12346 | PENDING |
3.3 JWT 토큰 구조
typescript
interface NextMarketJwtPayload {
// 기본 정보
sub: number; // NextMarket 고객 ID
username: string; // 로그인 아이디
email: string;
name: string;
role: string; // USER | ADMIN
// 구독 정보 (서비스 접근 권한)
subscriptions: {
serviceCode: string; // 'aiagent', 'analytics' 등
serviceMemberId: string; // 해당 서비스의 회원 ID
plan: string; // 구독 플랜
expiresAt: string; // 구독 만료일
}[];
// 토큰 메타
iat: number;
exp: number;
}토큰 예시:
json
{
"sub": 1001,
"username": "user@example.com",
"email": "user@example.com",
"name": "홍길동",
"role": "USER",
"subscriptions": [
{
"serviceCode": "aiagent",
"serviceMemberId": "MBR_12345",
"plan": "SUBSCRIBE:M01",
"expiresAt": "2026-02-15T00:00:00Z"
},
{
"serviceCode": "analytics",
"serviceMemberId": "user_abc123",
"plan": "PRO",
"expiresAt": "2026-03-01T00:00:00Z"
}
],
"iat": 1705312800,
"exp": 1705313700
}3.4 회원 프로비저닝 흐름
A. 구독 시 회원 생성 (Lazy Provisioning)
┌─────────────────────────────────────────────────────────────────────────┐
│ 구독 결제 완료 시 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. StepPay Webhook 수신 (subscription.paid) │
│ │ │
│ ▼ │
│ 2. SERVICE_MEMBER_MAPPING 확인 │
│ └── 해당 서비스에 회원이 이미 있는가? │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ [있음] [없음] │
│ │ │ │
│ │ ▼ │
│ │ 3. 서비스에 회원 생성 요청 │
│ │ POST /v1/member/provision │
│ │ { │
│ │ customerId: 1001, │
│ │ email: "user@example.com", │
│ │ name: "홍길동", │
│ │ phone: "010-1234-5678" │
│ │ } │
│ │ │ │
│ │ ▼ │
│ │ 4. 서비스측 응답 │
│ │ { │
│ │ success: true, │
│ │ memberId: "MBR_12345" │
│ │ } │
│ │ │ │
│ │ ▼ │
│ │ 5. SERVICE_MEMBER_MAPPING 저장 │
│ │ │ │
│ └───────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ 6. 구독 활성화 완료 │
│ │
└─────────────────────────────────────────────────────────────────────────┘B. 회원가입 시 즉시 생성 (Eager Provisioning) - 선택적
┌─────────────────────────────────────────────────────────────────────────┐
│ 회원가입 완료 시 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. NextMarket 회원가입 완료 │
│ │ │
│ ▼ │
│ 2. 기본 서비스들에 회원 사전 생성 (선택적) │
│ - 무료 체험이 가능한 서비스 │
│ - 기본 제공 서비스 │
│ │ │
│ ▼ │
│ 3. SERVICE_MEMBER_MAPPING 저장 (PROVISION_STATUS = 'PROVISIONED') │
│ │
└─────────────────────────────────────────────────────────────────────────┘3.5 서비스별 API 요구사항
각 서비스는 다음 API를 제공해야 함:
yaml
# 회원 프로비저닝 API (필수)
POST /v1/member/provision
Request:
customerId: number # NextMarket 고객 ID
email: string
name: string
phone?: string
metadata?: object # 서비스별 추가 정보
Response:
success: boolean
memberId: string # 서비스측 회원 ID
memberCode?: string # 서비스측 회원 코드
# 회원 정보 업데이트 API (선택)
PATCH /v1/member/{memberId}
Request:
name?: string
phone?: string
metadata?: object
# 회원 비활성화 API (선택)
DELETE /v1/member/{memberId}
Response:
success: boolean
# 토큰 검증 API (또는 JWT 직접 검증)
POST /v1/auth/verify
Request:
token: string # NextMarket JWT
Response:
valid: boolean
customerId?: number
serviceMemberId?: string3.6 aiagent 현재 구조와의 비교
현재 aiagent 회원 구조:
SST_MEMBER (회원)
├── MBR_IDX # 회원 IDX (PK)
├── MBR_ID # 회원 ID (로그인용)
├── MBR_NM # 회원명
├── MBR_PHONE # 휴대폰
└── ...
SST_STORE (매장)
├── STR_IDX # 매장 IDX
├── STR_NM # 매장명
└── ...
SST_STORE_MEMBER (매장-회원 매핑)
├── STR_IDX
├── MBR_IDX
└── ROLENextMarket 연동 시 변경:
SST_MEMBER (회원) - 변경 없음 또는 최소 변경
├── MBR_IDX
├── MBR_ID
├── MBR_NM
├── MBR_PHONE
├── NEXTMARKET_CUSTOMER_ID # 추가: NextMarket 고객 ID
└── ...
새로운 프로비저닝 API 추가:
POST /v1/member/provision
- NextMarket에서 호출
- MBR 생성 후 MBR_IDX 반환4. 서비스 접속 흐름
4.1 NextMarket 토큰으로 서비스 접속
┌─────────────────────────────────────────────────────────────────────────┐
│ 서비스 접속 흐름 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 사용자 NextMarket aiagent │
│ │ │ │ │
│ │ 1. 로그인 요청 │ │ │
│ │ ──────────────────────> │ │ │
│ │ │ │ │
│ │ 2. JWT 발급 │ │ │
│ │ (구독 정보 포함) │ │ │
│ │ <────────────────────── │ │ │
│ │ │ │ │
│ │ 3. aiagent API 호출 │ │
│ │ Authorization: Bearer {JWT} │ │
│ │ ─────────────────────────────────────────────> │ │
│ │ │ │ │
│ │ │ 4. JWT 검증 │ │
│ │ │ - 서명 확인 │ │
│ │ │ - 구독 확인 │ │
│ │ │ - 만료일 확인 │ │
│ │ │ │ │
│ │ 5. 서비스 응답 │ │ │
│ │ <───────────────────────────────────────────── │ │
│ │ │ │ │
└─────────────────────────────────────────────────────────────────────────┘4.2 각 서비스의 JWT 검증 방법
방법 A: JWT 직접 검증 (권장)
typescript
// aiagent-api에서 NextMarket JWT 검증
import * as jwt from 'jsonwebtoken';
const NEXTMARKET_PUBLIC_KEY = process.env.NEXTMARKET_JWT_PUBLIC_KEY;
function verifyNextMarketToken(token: string) {
try {
const payload = jwt.verify(token, NEXTMARKET_PUBLIC_KEY, {
algorithms: ['RS256'],
issuer: 'nextmarket'
});
// aiagent 구독 확인
const aiagentSub = payload.subscriptions?.find(
s => s.serviceCode === 'aiagent'
);
if (!aiagentSub) {
throw new Error('aiagent 구독이 없습니다');
}
if (new Date(aiagentSub.expiresAt) < new Date()) {
throw new Error('구독이 만료되었습니다');
}
return {
valid: true,
customerId: payload.sub,
serviceMemberId: aiagentSub.serviceMemberId,
plan: aiagentSub.plan
};
} catch (error) {
return { valid: false, error: error.message };
}
}방법 B: NextMarket API 호출 검증
typescript
// 토큰 검증을 NextMarket에 위임
async function verifyTokenViaAPI(token: string) {
const response = await axios.post(
`${NEXTMARKET_API_URL}/auth/verify`,
{ token, serviceCode: 'aiagent' }
);
return response.data;
}5. 회원 정보 동기화
5.1 동기화 이벤트
| 이벤트 | 설명 | 동기화 대상 |
|---|---|---|
member.updated | 회원 정보 변경 | 구독 중인 모든 서비스 |
member.deactivated | 회원 비활성화 | 구독 중인 모든 서비스 |
subscription.activated | 구독 활성화 | 해당 서비스만 |
subscription.cancelled | 구독 해지 | 해당 서비스만 |
5.2 동기화 흐름
┌─────────────────────────────────────────────────────────────────────────┐
│ 회원 정보 변경 시 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. NextMarket에서 회원 정보 변경 │
│ PATCH /api/v1/users/me │
│ { name: "김철수", phone: "010-9999-8888" } │
│ │ │
│ ▼ │
│ 2. CUSTOMERS 테이블 업데이트 │
│ │ │
│ ▼ │
│ 3. 구독 중인 서비스 목록 조회 │
│ SELECT * FROM SERVICE_MEMBER_MAPPING │
│ WHERE CUSTOMER_ID = ? AND PROVISION_STATUS = 'PROVISIONED' │
│ │ │
│ ▼ │
│ 4. 각 서비스에 변경 사항 전파 (비동기) │
│ ├── aiagent: PATCH /v1/member/MBR_12345 │
│ └── analytics: PATCH /v1/member/user_abc123 │
│ │ │
│ ▼ │
│ 5. MEMBER_SYNC_LOG 테이블에 결과 저장 │
│ │
└─────────────────────────────────────────────────────────────────────────┘6. 구현 단계
Phase 1: 기반 구축
- [ ] SERVICE_MEMBER_MAPPING 테이블 생성
- [ ] JWT에 subscriptions 필드 추가
- [ ] 회원 정보 동기화 서비스 구현
Phase 2: aiagent 연동
- [ ] aiagent에 프로비저닝 API 추가 (
/v1/member/provision) - [ ] aiagent에 NextMarket JWT 검증 미들웨어 추가
- [ ] 기존 aiagent 회원과 NextMarket 회원 매핑
Phase 3: 통합 테스트
- [ ] 회원가입 → 구독 → 서비스 접속 전체 흐름 테스트
- [ ] 회원 정보 변경 동기화 테스트
- [ ] 구독 해지 시 접근 차단 테스트
7. 결정 필요 사항
Q1: 기존 aiagent 회원 처리
| 옵션 | 설명 |
|---|---|
| A | NextMarket 회원으로 마이그레이션 (기존 aiagent 직접 가입 불가) |
| B | 병행 운영 (aiagent 직접 가입 + NextMarket 연동 둘 다 가능) |
| C | 점진적 전환 (신규는 NextMarket만, 기존은 유지) |
Q2: 토큰 검증 방식
| 옵션 | 설명 |
|---|---|
| A | JWT 직접 검증 (RSA 공개키 공유) |
| B | NextMarket API 호출 검증 |
| C | 하이브리드 (캐시 + API fallback) |
Q3: 회원 프로비저닝 시점
| 옵션 | 설명 |
|---|---|
| A | 구독 시 생성 (Lazy) |
| B | 회원가입 시 전체 생성 (Eager) |
| C | 서비스별 선택 (일부 Lazy, 일부 Eager) |
8. 참고: 기존 시스템과의 호환성
aiagent 현재 인증 방식
javascript
// aiagent-api의 현재 세션 방식
exports.verifyMemberSession = async (request, response, next) => {
const token = getBearerToken(request);
const session = await cache.get(`SAAS_MEMBER_SESSION:${token}`);
// ...
}NextMarket 연동 후 추가되는 검증
javascript
// NextMarket JWT 검증 미들웨어 추가
exports.verifyNextMarketToken = async (request, response, next) => {
const token = getBearerToken(request);
// 1. NextMarket JWT인지 확인 (iss: 'nextmarket')
const decoded = jwt.decode(token);
if (decoded?.iss === 'nextmarket') {
// NextMarket 토큰 검증
const verified = verifyNextMarketJWT(token);
// ...
} else {
// 기존 aiagent 세션 검증 (호환성 유지)
await verifyMemberSession(request, response, next);
}
}작성일: 2026년 1월