[SpringBoot] 라이선스 관리 AOP 적용해보기

2025. 10. 13. 15:23·Development

라이선스 관리 AOP 적용기: 

본 문서는 프로젝트에서 적용한 “기능(Feature) 단위 라이선스 검증 AOP”의 목적, 구조, 흐름, 구현 포인트, 운영/테스트 전략을 정리한 기술 포스팅입니다.

현재 개발 중인 프로젝트의 요구사항으로 '라이선스' 기능이 추가되었다.

여러 구독 플랫폼에서 써보기나 했지, 구현해 보는 것은 처음이라 자료 조사부터 시작하게 되었다.

더보기

라이선스란?

라이선스는 소프트웨어, 기술, 또는 콘텐츠의 사용, 배포, 수정에 대한 권리와 조건을 명시하는 법적 계약 문서이다. 라이선스는 개발자나 권리자가 사용자에게 어떤 권한을 줄지, 그리고 어떤 제한을 둘지 정의한다.

참고로, 라이센스가 아닌 라이선스가 올바른 표준말이라고 한다.
https://stdict.korean.go.kr/search/searchView.do?word_no=100131&searchKeywordTo=3

 

주요 라이선스 유형

주요 라이선스 유형은 아래와 같다.

  • 독점(상용) 라이선스: 사용만 가능할 뿐, 수정이나 재배포가 거의 불가.
  • 오픈소스 라이선스: 소스코드 접근·수정·재배포가 가능하며, 라이선스 종류에 따라 공개 의무나 상업적 이용 여부 등이 다릅니다(MIT, Apache, GPL, LGPL 등).
  • 프리웨어/셰어웨어: 무료 공개 또는 일정 기간 무료 제공 후 유료 전환되는 형태.
  • SaaS(구독 라이선스): 일정 기간 사용 권한을 제공하고, 서비스 방식으로 접근.

사용자, 장치, 사이트별 라이선스 등 계약 형태에 따라 기준을 나누기도 한다.

 

SaaS 플랫폼에서 라이선스 기능을 도입할 때는 아래와 같은 모델이 있다.

 

1. 기능 기반 라이선싱 (Feature-Based / Tiered) 🌟

가장 일반적인 모델로 고객이 구독하는 요금제 등급(Tier)에 따라 접근할 수 있는 기능(Feature)을 제한하는 방식이다.

  • 예시:
    • Free Plan: 핵심 기능 A만 사용 가능
    • Pro Plan: A + 고급 기능 B, C 사용 가능
    • Enterprise Plan: A, B, C + 관리자 대시보드 D, 보안 감사 로그 E 사용 가능
  • 대표 서비스: Slack, Notion, GitHub
  • 구현 핵심: 사용자가 특정 기능을 실행하려 할 때(예: API 호출), 현재 구독 중인 플랜이 해당 기능을 포함하는지 확인한다.

 

2. 사용자 기반 라이선싱 (Per-User / Per-Seat) 👥

조직(Tenant) 내에서 플랫폼을 사용할 수 있는 사용자(Seat) 수를 기준으로 라이선스를 부과하는 방식이다.

  • 예시:
    • Basic Plan: 사용자 5명까지
    • Team Plan: 사용자 20명까지 (추가 사용자당 월 $10)
  • 대표 서비스: Jira, Google Workspace, Figma
  • 구현 핵심: 사용자가 조직에 새 멤버를 초대하거나 등록하려 할 때, 현재 조직의 구독 플랜이 허용하는 사용자 한도에 도달했는지 확인한다.

 

3. 사용량 기반 라이선싱 (Consumption-Based / Metered) 📊

고객이 사용한 만큼만 비용을 지불하는 종량제 모델이다. 기능이나 사용자 수 대신 사용한 리소스의 양을 제한하거나 측정한다.

  • 예시:
    • API 호출 1,000건당 $1
    • 사용한 스토리지 GB당 $0.1
    • 빌드 시간(분)당 $0.05
  • 대표 서비스: AWS, Twilio (메시지 건수), Snowflake (데이터 처리량)
  • 구현 핵심: 특정 작업(API 호출, 파일 업로드, 빌드 실행)이 발생할 때마다 사용량을 누적하고, 이 누적량이 할당된 한도(Quota)에 도달했는지 확인한다.

 

4. 하이브리드 모델 (Hybrid Models)

현실의 많은 SaaS 플랫폼은 위 모델들을 조합하여 사용한다.

  • 예시 (Jira): 기능 기반 (Standard 플랜, Premium 플랜 기능 다름) + 사용자 기반 (플랜별 사용자 수 제한)을 함께 사용한다.
  • 예시 (Vercel): Freemium (기능/사용량 제한) + Pro (사용자 기반) + Enterprise (기용 기반 + 사용량 기반)를 복잡하게 조합한다.

 

왜 AOP로 라이선스를 검증했나

  • 라이선스 검증은 “가로지르는 관심사(cross-cutting concern)”이다.
  • 각 기능 진입점마다 동일한 검증 로직을 반복하면 누락/중복/일관성 문제가 발생한다.
  • AOP로 진입 전 검증을 표준화하면
    • 선언적 적용(ex. `@CheckLicense`)으로 가독성↑
    • 중복 로직↓, 변경 용이성↑
    • 감사/로깅/메트릭 연계 용이

가장 큰건, 기존에 운영되고 있는 기능의 비즈니스 로직을 수정하고 싶지 않았기 때문이다. 

위 이유로, AOP를 적용하고자 했다.

 


개발 환경

  • Spring boot(Java 17)
  • React

 

특이사항

  • Redis 없음, DB 기준으로만 사용량 판단
  • 디비 적재된 데이터 기준, 신규 등록 시에 체크
  • 적용 대상은 기존 기능(화면 기준) 약 50개 중 3개 정도 (예: 사용자 수 200명, 프로젝트 100개 제한 등)

 

현재 라이선스 키를 저장하는 테이블이 생성되어있는 상태다.

서버에서는 쓰기 직전에만 검사하도록 설정한다. (AOP/인터셉터)

  • 적용 대상 API 3개(예: 사용자 생성, 프로젝트 생성, 팀 생성 등)에만 어노테이션 붙이기
  • 트랜잭션 안에서 라이선스 행을 잠그고(SELECT ... FOR UPDATE) → 현재 개수 카운트 → 초과 시 예외 → 아니면 계속.

 


아키텍처 한눈에 보기

구성 모듈

  • [BackEnd] core-lib
    • @CheckLicense 어노테이션
    • LicenseCheckAspect (AOP)
  • [BackEnd] common-api (공통 모듈)
    • 라이선스 검증 비즈니스 (LicensePortImpl)
    • REST 엔드포인트(/license_management/v1/validate 등)
    • AOP 구성(LicenseAspectConfig)
  • [BackEnd] domain-api (도메인 모듈)
    • Feign/REST로 common-api 검증 위임 또는 core-lib AOP 직접 사용
  • [FrontEnd] react-app
    • 라이선스 관리 화면, 생성/조회/검증 UI

 

흐름 요약(요청 → 검증 → 비즈니스)

  1. 클라이언트가 기능 API 호출
  2. 해당 기능 메서드가 @CheckLicense(featureType=..., count=...)로 선언됨
  3. LicenseCheckAspect가 메서드 실행 전 인터셉트
  4. LicensePort를 통해 라이선스 검증 실행
    • 만료/사용량/키 유효성 체크
  5. 검증 성공 시 실제 비즈니스 실행
  6. 실패 시 표준 에러 응답 반환 (HTTP 402)

동작 상세

검증 항목

  • 만료일자(expiration) 초과 여부
  • 발급 단위/사용량 제한(FeatureType 별) 초과 여부
  • 라이선스 키 무결성 및 상태(활성/중지 등)

선언적 적용 방법

// 예: 사용자 등록 API에 검증 적용
@CheckLicense(featureType = FeatureType.USER, count = 1)
public UserResponse createUser(UserRequest req) {
  // ... 비즈니스 로직 ...
}

AOP 핵심

  • Pointcut: @annotation(CheckLicense)가 붙은 메서드
  • Around/Before Advice: 실행 전 라이선스 검증 수행
  • 실패 시 예외 전파(예: LicenseValidationException) → 표준 에러 포맷 매핑

서비스 위임(Port)

  • 도메인 모듈은 라이선스 세부 규칙을 몰라도 됨
  • LicensePort 인터페이스로 추상화, 구현은 common-api에 위치
    • 단일 책임, 테스트 용이성, 재사용성 확보
  • 일부 모듈은 common-api /validate로 위임(Feign/REST)

API 레이어

주요 엔드포인트

  • GET /license_management/v1/select 라이선스 정보 조회
  • POST /license_management/v1/create 라이선스 등록
  • POST /license_management/v1/validate 기능별 사용 가능 여부 검증

에러 관련

AOP 적용 시, bean 에러 발생

 

설정과 운영

버전 정보 주입

  • @Value("${release.version:0.0.0}")로 버전 문자열 주입
    • local/k8s 프로필의 application-*.yml에 release.version 정의
    • Spring Cloud Config 사용 시 Config Server → 클라이언트 자동 주입
    • 기본값 폴백(0.0.0)으로 안전성 확보

이 부분은 소스단의 하드코딩을 피하기 위해 임시로 작업해둔 거라, 수정이 필요하다.

환경 프로필

  • local: 로컬 개발 디폴트
  • k8s: 운영/스테이징에서 k8s 값을 사용
  • 우선순위: 환경변수 > Config Server > 애플리케이션 yml > 기본값

예외/로깅/감사

예외 응답(예시)

  • 401 LICENSE_EXPIRED: 라이선스 만료
  • 402 LICENSE_LIMIT_EXCEEDED: 사용량 초과

라이선스 관련한 모든 예외는 위 에러 응답 포맷으로 매핑하여 프론트에서 라이선스 관련 Alert 메세지 표시되도록 처리하였다.


성능/확장 고려

현재는 초기 기능으로 구조만 개발해둔 상태이며, 기능 개선을 위해 고민 중인 사항은 아래와 같다. 

  • 검증은 “빠르고 가볍게”
    • 빈번한 조회 정보(키 메타, 만료일자) 캐시 고려
    • DB I/O 최소화, 필요 시 로컬/분산 캐시
  • FeatureType 추가 시
    • @CheckLicense 파라미터/Enum 확장
    • 포트 구현의 룰만 증분 수정
    • 기존 선언부는 변경 없음(개방-폐쇄 원칙 준수 OCP)

테스트

  • 단위 테스트
    • Aspect: @CheckLicense가 붙은 메서드 호출 시 검증 호출/차단 여부
    • Port/Service: 만료/사용량 등 경계값 테스트

실사용 요약

  • 개발자는 적용할 API 컨트롤러 메서드에 @CheckLicense(featureType=..., count=...)어노테이션을 붙인다.
  • AOP가 실행 전 자동 검증 → 실패 시 예외로 차단 → 성공 시 비즈니스 실행.
  • 검증 로직은 공통 모듈에 집중, 규칙 변경 시 한 곳에서 제어.
  • 환경/버전/운영 옵션은 yml/Config Server에서 일원 관리.

마무리

AOP로 라이선스 검증을 “선언적”으로 공통화함으로써 품질과 유지보수성을 동시에 끌어올리고자 하였다. 적용되어야할 새로운 기능이 늘어나도 어노테이션 한 줄이면 일관된 정책을 적용할 수 있고, 규칙 변경도 중앙에서 안전하게 관리할 수 있을 것으로 기대된다.

'Development' 카테고리의 다른 글

[CS] "나누기 0"은 왜 어떤 언어에선 에러가 나고, 어떤 언어에선 무한대(Infinity)가 될까?  (1) 2025.11.03
[React] 배포 후 간헐적으로 개발 서버에서 화면 에러가 뜨는 이유  (0) 2025.10.14
[Web] 브라우저 “탭( Tab )”을 노리는 공격, Tabnabbing  (0) 2025.10.13
[C언어] return 0, 그리고 0과 1의 진짜 의미  (0) 2025.10.06
[macOS] port 빠르게 종료하기  (0) 2025.09.30
'Development' 카테고리의 다른 글
  • [CS] "나누기 0"은 왜 어떤 언어에선 에러가 나고, 어떤 언어에선 무한대(Infinity)가 될까?
  • [React] 배포 후 간헐적으로 개발 서버에서 화면 에러가 뜨는 이유
  • [Web] 브라우저 “탭( Tab )”을 노리는 공격, Tabnabbing
  • [C언어] return 0, 그리고 0과 1의 진짜 의미
곽진돔
곽진돔
Developer
  • 곽진돔
    echo "곽박한 세상";
    곽진돔
  • 전체
    오늘
    어제
    • 분류 전체보기 (232)
      • Development (95)
        • Linux (13)
        • k8s (3)
        • Docker (5)
        • AWS (1)
        • PHP (35)
        • Python (21)
        • Java (5)
        • SpringBoot (4)
        • JavaScript (2)
        • React (12)
        • MySql (19)
        • MongoDB (1)
      • Daily (8)
      • Study (7)
        • TIL (2)
        • license (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
    • 설정
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    springboot
    크롤링
    php
    HTML
    인코딩
    db
    SQL
    nodejs
    chromedriver
    ssh
    JavaScript
    MySQL
    IP
    docker
    CentOS7
    리눅스
    Selenium
    CentOS
    Java
    Python
    GitHub
    react
    Git
    스프링부트
    UTF8
    Shell
    정규표현식
    Mac
    Linux
    AI
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
곽진돔
[SpringBoot] 라이선스 관리 AOP 적용해보기
상단으로

티스토리툴바