콘텐츠로 이동

Java SDK

kr.signox:signox-sdk — Java(Spring 서버·데스크탑)용 공식 SDK입니다. Java 8 이상에서 동작합니다.

  • 모든 서버 응답을 제품 Ed25519 공개키로 서명검증한 뒤에만 신뢰합니다(위변조·재생 방어).
  • 기기 식별자(hwid)는 SDK가 내부에서 자동 생성합니다 — 호출자가 다룰 필요가 없습니다.
  • 판정은 예외가 아니라 LicenseResult 코드로 전달됩니다. 네트워크 실패와 무효 판정은 코드로 구분됩니다.
<dependency>
<groupId>kr.signox</groupId>
<artifactId>signox-sdk</artifactId>
<version>0.1.0</version>
</dependency>

런타임 의존성은 BouncyCastle bcprov-jdk18on 하나뿐입니다(MIT, 전이 의존 0). Java 8~14는 JDK에 Ed25519가 없어 필수이며, JDK 15 이상에서도 동일 경로로 동작합니다.

import kr.signox.sdk.*;
// 제품 공개키(SPKI PEM)는 반드시 코드에 임베드하세요 (아래 "공개키 임베드" 참고)
String productPublicKey =
"-----BEGIN PUBLIC KEY-----\n....\n-----END PUBLIC KEY-----\n";
SignoxClient client = new SignoxClient(
SignoxClient.builder(productPublicKey)
.baseUrl("https://api.signox.kr") // 기본값
.timeoutMs(10_000) // 기본값
.build());
// 1) 디바이스 활성화 (최초 1회) — 멱등
LicenseResult activated = client.activate("S4K2MB-7IWQ3F-...",
ActivateOptions.builder().name("홍길동의 노트북").build());
if (!activated.isValid()) {
System.out.println("활성화 실패: " + activated.getCode());
}
// 2) 라이센스 검증 (앱 시작·주기적)
LicenseResult r = client.validate("S4K2MB-7IWQ3F-...");
if (r.isValid()) {
boolean pro = r.getFeatureBool("pro_export", false);
long seats = r.getFeatureInt("max_seats", 1);
// ... 기능 활성화
} else if (r.getCode() == ValidationCode.NETWORK_ERROR) {
// 네트워크 문제 — 무효 판정이 아님. 재시도 또는 유예 처리
} else {
// EXPIRED / REVOKED / DEVICE_NOT_ACTIVATED ...
}

SignoxClient 인스턴스는 불변 설정 + 스레드 안전이므로 앱 전역에서 하나만 만들어 공유하면 됩니다.

메서드 용도
validate(licenseKey) 라이센스+디바이스 검증(캐시·오프라인 폴백 포함)
activate(licenseKey[, opts]) 디바이스 활성화(hwid·anomalies 자동 첨부)
deactivate(licenseKey) 디바이스 해제(슬롯 반환)
heartbeat(licenseKey) 생존 신호 + 가벼운 재검증
reportUsage(licenseKey, feature[, value]) 메터링 사용량 보고
requestOfflineFile(licenseKey) .lic 파일 발급(내용은 getFileContent())
validateOffline(licContent) .lic 완전 로컬 검증(네트워크 불필요)
getHwid() 진단용 — 이 기기의 hwid
  • 상태: VALID, IN_GRACE_PERIOD, EXPIRED, SUSPENDED, REVOKED
  • 요청/디바이스: NOT_FOUND, PRODUCT_INACTIVE, HWID_REQUIRED, DEVICE_NOT_ACTIVATED, DEVICE_LIMIT_REACHED, VM_NOT_ALLOWED, FEATURE_NOT_FOUND, USAGE_LIMIT_REACHED
  • SDK 로컬 판정: SIGNATURE_INVALID(서명검증 실패), NONCE_MISMATCH(재생 의심), OFFLINE_FILE_INVALID(.lic 형식 오류), HWID_MISMATCH(.lic 기기 불일치), NETWORK_ERROR(네트워크 실패 + 유효 캐시 없음 — 무효 판정과 구분)

isValid()true인 코드는 VALID, IN_GRACE_PERIOD 둘뿐입니다. 코드별 권장 UX는 온라인 활성화 가이드를 참고하세요.

  • validate 성공 응답은 서버가 준 cache.ttl 동안 신뢰되어 재검증을 생략합니다(rate limit 내 자연 수렴).
  • 캐시에는 서명된 응답 원문(raw) 만 저장되며 읽을 때마다 서명을 재검증합니다(변조 방어).
  • 네트워크 실패 시 오프라인 유예 창(offlineGraceDays, 기본 7일 — 캐시 저장 시점 기준, 0 = 폴백 안 함) 내 유효 캐시가 있으면 그 판정으로 폴백하며 isStale()true가 됩니다.
  • cacheDir를 지정하면 디스크에도 저장되어 프로세스 재시작 후에도 유지됩니다. 미지정 시 인메모리 캐시만 사용합니다.
SignoxClient.builder(productPublicKey)
.cacheDir(new File(System.getProperty("user.home"), ".myapp/signox-cache"))
.offlineGraceDays(14) // 오프라인 유예 14일
.build();

인터넷이 없는 기기는 포털·대시보드 또는 활성 디바이스에서 .lic 파일을 발급받아 완전 로컬로 검증합니다. 전체 플로우는 오프라인 활성화를 참고하세요.

// 발급 (온라인 1회)
LicenseResult issued = client.requestOfflineFile("S4K2MB-7IWQ3F-...");
if (issued.getFileContent() != null) {
Files.write(Paths.get("license.lic"),
issued.getFileContent().getBytes(StandardCharsets.UTF_8));
}
// 검증 (오프라인, 반복)
String lic = new String(Files.readAllBytes(Paths.get("license.lic")), StandardCharsets.UTF_8);
LicenseResult r = client.validateOffline(lic);
// VALID / IN_GRACE_PERIOD / EXPIRED / HWID_MISMATCH / SIGNATURE_INVALID / OFFLINE_FILE_INVALID

제품 공개키는 반드시 애플리케이션에 임베드하세요. SDK는 의도적으로 공개키 조회 엔드포인트를 제공하지 않습니다 — 런타임에 키를 받아오면 중간자가 자신의 키로 바꿔치기하는 위조 벡터가 생기기 때문입니다. 비밀키는 절대 클라이언트에 넣지 마세요(서명은 서버 전용).

BouncyCastle 클래스패스 충돌 (bcprov-jdk15on)

섹션 제목: “BouncyCastle 클래스패스 충돌 (bcprov-jdk15on)”

기존 앱이 구버전 bcprov-jdk15on을 쓰면 클래스가 충돌할 수 있습니다. 앱 쪽 구버전을 최신 bcprov-jdk18on으로 통일하거나, 부득이하면 SDK의 전이 의존을 제외하고 앱이 제공하는 BC(Ed25519 지원 1.70+)를 사용하세요.

<dependency>
<groupId>kr.signox</groupId>
<artifactId>signox-sdk</artifactId>
<version>0.1.0</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</exclusion>
</exclusions>
</dependency>