본문으로 건너뛰기

권한 (Entitlement) 시스템

문서 정보

  • 작성일: 2026-02-27
  • 최종 업데이트: 2026-02-27
  • 버전: v1.0.0

TL;DR

Entitlement 테이블은 사용자 1명당 1개의 레코드로 현재 멤버십 권한을 관리합니다. API 요청 시 이 테이블 하나만 조회하면 O(1)으로 권한 판단이 완료되며, 구독/체험/관리자 부여 등 어떤 경로로 권한을 받았든 동일한 방식으로 처리합니다.


목차

  1. 왜 Entitlement 테이블이 필요한가
  2. Entitlement 테이블 구조
  3. 권한 부여 소스
  4. 권한 판별 흐름
  5. Guard 기반 접근 제어
  6. Entitlement 라이프사이클
  7. 만료 처리 스케줄러
  8. 기능별 접근 권한 정리
  9. FAQ

왜 Entitlement 테이블이 필요한가

기존 방식의 문제

Entitlement 도입 이전에는 사용자의 PLUS 권한을 확인하려면 여러 테이블을 동시에 조회해야 했습니다.

문제설명
느린 응답매 요청마다 2~3개 테이블을 조회해야 함
복잡한 로직구독 상태, 체험 기간, 관리자 부여를 모두 종합해서 판단
확장 어려움새로운 권한 부여 방식이 생길 때마다 판단 로직 수정 필요

Entitlement의 해결 방식

핵심 아이디어는 간단합니다. "복잡한 판단은 권한이 변경될 때 한 번만 하고, 조회는 항상 단순하게"

항목기존 방식Entitlement 방식
조회 횟수2~3개 테이블1개 테이블, 1건
판단 시점API 요청 시 매번이벤트 발생 시 한 번
Guard 역할여러 테이블 종합 판단단순 조건 비교만
새 권한 소스 추가Guard 로직 수정 필요EntitlementService만 확장

Entitlement 테이블 구조

사용자 1명당 정확히 1개의 레코드만 존재합니다. userId가 곧 기본 키(Primary Key)입니다.

필드설명예시
userId사용자 ID (기본 키, User와 1:1)12345
plan현재 플랜PLUS 또는 FREE
status권한 상태ACTIVE, INACTIVE, SUSPENDED
validFrom권한 시작일2026-02-27T00:00:00Z
validUntil권한 종료일2026-03-27T00:00:00Z
sourceType권한이 어디서 왔는지SUBSCRIPTION, TRIAL, ADMIN
sourceId원본 레코드 ID구독 ID 또는 정책 적용 ID
핵심 원칙

하나의 사용자에게 항상 하나의 레코드만 유지됩니다. 여러 권한 소스가 경쟁할 경우 가장 강한 권한이 우선합니다.

상태(status) 의미

상태의미서비스 접근
ACTIVE유효한 멤버십허용
INACTIVE만료 또는 미가입차단
SUSPENDED환불/부정 사용 등으로 정지차단

만료 시 레코드 처리

레코드는 삭제되지 않고 업데이트됩니다. 만료되면 다음과 같이 변경됩니다:

필드만료 전만료 후
planPLUSFREE
statusACTIVEINACTIVE
sourceTypeSUBSCRIPTION 또는 TRIALnull
sourceId원본 IDnull

따라서 모든 가입 사용자에게 Entitlement 레코드가 존재합니다 (회원가입 시 체험으로 자동 생성).


권한 부여 소스

PLUS 멤버십은 세 가지 경로를 통해 부여됩니다.

소스설명sourceId가 가리키는 곳
SUBSCRIPTION유료 정기 구독구독(Subscription) 레코드
TRIAL회원가입 시 무료 체험마케팅 정책 적용 레코드
ADMIN관리자가 수동으로 부여없음 (null)
체험과 구독의 기능 차이

체험(TRIAL)과 구독(SUBSCRIPTION)은 모두 PLUS 멤버십이지만, 일부 기능은 구독 전용입니다. 자세한 내용은 기능별 접근 권한 정리를 참고하세요.


권한 판별 흐름

API 요청이 들어왔을 때, 권한을 판별하는 전체 흐름입니다.

구독 갱신 유예 시간 (Grace Period)

구독(SUBSCRIPTION) 사용자에게는 30분의 유예 시간이 적용됩니다. 정기 결제 갱신과 권한 체크 사이의 시간차로 인해 일시적으로 서비스가 차단되는 것을 방지하기 위한 장치입니다.

소스유예 시간이유
SUBSCRIPTION30분결제 갱신 처리 시간차 방지
TRIAL없음 (정확히 만료)정해진 기간만 보장
ADMIN없음관리자가 직접 관리

Guard 기반 접근 제어

@RequiresPlus() 데코레이터

API 엔드포인트에 @RequiresPlus()를 붙이면 PLUS 멤버십 사용자만 접근할 수 있습니다. 권한 소스(sourceType)에 따라 세밀하게 제어할 수 있습니다.

사용 방식의미실제 사용 예시
@RequiresPlus()모든 PLUS 사용자 허용웨어러블 기기 연결
@RequiresPlus(['SUBSCRIPTION', 'ADMIN'])구독자 + 관리자만 허용콕 리워드, 마이콕, 리워드 전환

PlusGuard의 동작 원리

PlusGuard는 다음 세 가지 조건만 확인합니다:

  1. planPLUS인가?
  2. statusACTIVE인가?
  3. validUntil현재 시각 이후인가?

세 조건을 모두 통과하면 PLUS 멤버십이 유효합니다. 추가로 sourceType 제한이 있으면 허용 목록과 비교합니다.

Guard가 절대 하지 않는 것

Guard는 오직 Entitlement 테이블만 조회합니다. 다음은 Guard에서 절대 수행하지 않습니다:

  • 구독 테이블 직접 조회
  • 마케팅 정책 테이블 조회
  • 여러 레코드를 종합해서 판단
  • 결제 상태 해석

모든 복잡한 판단은 EntitlementService가 이벤트 발생 시점에 처리하고, Guard는 그 결과만 확인합니다.


Entitlement 라이프사이클

사용자의 Entitlement는 다음과 같은 흐름으로 변화합니다.

주요 이벤트별 처리

이벤트처리 메서드결과
회원가입createFromTrial()PLUS 체험 시작
구독 결제 성공upsertFromSubscription()구독 기반 PLUS로 전환
구독 갱신upsertFromSubscription()validUntil 연장
체험 만료deactivateExpiredTrials()FREE로 변경
구독 만료deactivateExpiredSubscriptions()FREE로 변경
환불restoreTrialAfterRefund()남은 체험 복원 또는 FREE

구독 전환 시 동작

체험 기간 중에 유료 구독을 시작하면, Entitlement가 SUBSCRIPTION으로 덮어써집니다. 이는 구독이 체험보다 더 강한 권한이기 때문입니다.

항목변경 전 (체험)변경 후 (구독)
sourceTypeTRIALSUBSCRIPTION
sourceId정책 적용 ID구독 ID
validUntil체험 종료일다음 결제일
validFrom유지유지 (최초 생성 시점 보존)

만료 처리 스케줄러

ExpireEntitlementScheduler

매일 새벽 3시(KST)에 실행되어 만료된 체험과 구독을 일괄 처리합니다.

처리 순서와 이유

순서대상조건이유
1체험 정책 적용expiresAt < now & ACTIVE원본 데이터 상태를 먼저 기록
2체험 EntitlementsourceType = TRIAL & validUntil < now정책 상태 변경 후 권한 비활성화
3구독 EntitlementsourceType = SUBSCRIPTION & validUntil < now - 30분Grace Period 적용 후 비활성화

로그 및 리포트

만료 처리 결과는 두 곳에 기록됩니다:

기록 위치형식용도
logs/entitlement/YYYY-MM-DD/로그 파일상세 처리 내역 보관
Discord Daily Report요약 메시지운영팀 모니터링

기능별 접근 권한 정리

모든 PLUS 사용자 (체험 포함)

체험(TRIAL)과 구독(SUBSCRIPTION) 모두 사용 가능한 기능입니다.

기능설명
웨어러블 기기 연결웨어러블 디바이스 등록 및 연동

구독 전용 기능

유료 구독자만 사용 가능하며, 체험 사용자는 접근할 수 없습니다.

기능설명제한 이유
콕 리워드콕 시청 완료 시 리워드 적립유료 전환 인센티브
마이콕콕 개인 저장소스토리지 비용
리워드 전환리워드 → 캐쉬 전환재화 정책

접근 제어 구조

체험은 구독의 부분집합입니다. 구독 전용 기능은 유료 결제를 통해서만 접근할 수 있습니다.


FAQ

Q: Entitlement 레코드가 없는 사용자가 있을 수 있나요?

체험 자동 적용이 도입되기 이전에 가입한 사용자는 Entitlement 레코드가 없을 수 있습니다. 이 경우 API 응답에서 entitlement: null로 표시되며, 권한 체크에서는 비(非)PLUS로 처리됩니다.

Q: 체험 기간 중에 구독하면 체험은 어떻게 되나요?

Entitlement가 SUBSCRIPTION으로 덮어써집니다. 만약 구독을 환불하면, 남은 체험 기간이 있을 경우 자동으로 체험이 복원됩니다.

Q: 구독 만료 직후에 서비스가 바로 차단되나요?

아닙니다. 구독(SUBSCRIPTION) 사용자에게는 30분의 유예 시간(Grace Period)이 적용됩니다. 정기 결제 갱신 처리 중에 일시적으로 서비스가 차단되는 것을 방지하기 위한 장치입니다. 체험(TRIAL)에는 유예 시간이 적용되지 않습니다.

Q: 관리자가 직접 PLUS를 부여할 수 있나요?

네. sourceType = ADMIN으로 Entitlement를 부여할 수 있습니다. 관리자 부여 권한은 구독과 동일한 기능을 사용할 수 있습니다.

Q: Guard는 왜 구독 테이블을 직접 조회하지 않나요?

Guard가 구독, 마케팅 정책 등 여러 테이블을 조회하면 매 요청마다 복잡한 쿼리가 실행됩니다. Entitlement 테이블은 이런 복잡성을 이벤트 발생 시점으로 이동시켜, Guard는 단순히 "지금 이 사용자가 PLUS인가?"만 빠르게 확인합니다.

Q: 만료 스케줄러가 실행되기 전에 접근하면 어떻게 되나요?

Guard는 validUntil > 현재 시각 조건으로 실시간 체크합니다. 스케줄러가 아직 status를 변경하지 않았더라도, 유효 기간이 지났으면 자동으로 차단됩니다. 스케줄러는 데이터 정리 역할을 할 뿐, 실시간 접근 제어에는 영향을 주지 않습니다.


관련 문서


변경 이력

버전날짜변경 내용
v1.0.02026-02-27Wiki 형식으로 전면 재작성
- 비개발자 대상 설명 중심으로 재구성
- Mermaid 다이어그램으로 시각화 전면 교체
- 권한 판별 흐름 flowchart 추가
- 만료 스케줄러 처리 순서 상세화
- FAQ 섹션 추가
- Grain/Tol → 캐쉬, Point → 리워드 용어 통일