Skip to content

멀티 서비스 회원 관리 아키텍처

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_IDSERVICE_CODESERVICE_MEMBER_IDPROVISION_STATUS
1001aiagentMBR_12345PROVISIONED
1001analyticsuser_abc123PROVISIONED
1002aiagentMBR_12346PENDING

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?: string

3.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
└── ROLE

NextMarket 연동 시 변경:

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 회원 처리

옵션설명
ANextMarket 회원으로 마이그레이션 (기존 aiagent 직접 가입 불가)
B병행 운영 (aiagent 직접 가입 + NextMarket 연동 둘 다 가능)
C점진적 전환 (신규는 NextMarket만, 기존은 유지)

Q2: 토큰 검증 방식

옵션설명
AJWT 직접 검증 (RSA 공개키 공유)
BNextMarket 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월