How to Generate and Verify License Certificates with TrueLicense in Spring Boot
This article walks through the complete process of creating and validating software license certificates using the open‑source TrueLicense engine, covering keytool key‑pair generation, Spring Boot integration, RESTful license issuance, and runtime verification with system‑info checks.
Background
License certificates are used to prove that a paying user is authorized to access paid software. Depending on deployment, two scenarios are considered: SaaS‑style cloud deployment where validation occurs at login, and on‑premises deployment where a server‑side license file is loaded and verified during startup or critical operations.
TrueLicense Overview
TrueLicense is an open‑source certificate management engine for generating and validating licenses. The first step is to create a key pair using the JDK keytool utility.
# 1. Generate a private keystore
# validity: number of days the private key is valid
# alias: name of the private key
# keystore: file name for the private keystore (created in the current directory)
# storepass: password for the keystore
# keypass: password for the private key entry
keytool -genkeypair -keysize 1024 -validity 3650 -alias "privateKey" -keystore "privateKeys.keystore" -storepass "123456" -keypass "123456" -dname "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN"
# 2. Export the public key from the private keystore
keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "123456" -file "certfile.cer"
# 3. Import the public certificate into a public keystore
keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "123456"After running the three commands, three files are produced:
privateKeys.keystore – the private key (must be kept secret)
publicCerts.keystore – the public key used by the application to read license information
certfile.cer – an intermediate certificate file that can be deleted
Spring Boot Integration
With the key pair ready, the article shows how to integrate TrueLicense into a Spring Boot project.
Dependency
<dependency>
<groupId>de.schlichtherle.truelicense</groupId>
<artifactId>truelicense-core</artifactId>
<version>1.33</version>
</dependency>RESTful License Generation
A LicenseController provides an endpoint that creates a license file and streams it to the client.
@RestController
@Api(tags = "license management")
@RequestMapping("/license")
public class LicenseController {
@Resource
private LicenseCreator licenseCreator;
@Resource
private LicenseProperties licenseProperties;
@PostMapping
@ApiOperation("Generate license")
public void create(LicenseCreatorParam creatorParam, HttpServletResponse response) throws IOException {
boolean flag = licenseCreator.generateLicense(creatorParam);
List<String> list = StrUtil.split(licenseProperties.getLicensePath(), "/");
String fileName = list.get(list.size() - 1);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
BufferedInputStream inputStream = FileUtil.getInputStream(licenseProperties.getLicensePath());
IoUtil.copy(inputStream, response.getOutputStream());
}
}The creation parameters ( LicenseCreatorParam) include expiry time and server system information.
/**
* Certificate expiry time
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date expiryTime;
/**
* Server system information
*/
private SystemInfo systemInfo;To prevent simple copying of a license file to another server, the article adds verification of unique system identifiers such as CPU ID or system UUID. MAC address verification is omitted because containerized deployments have unstable MAC addresses.
License Creation Logic
@Component
public class LicenseCreator {
@Resource
private LicenseProperties licenseProperties;
private static final Logger logger = LogManager.getLogger(LicenseCreator.class);
private static final X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN");
/** Generate a license certificate */
public boolean generateLicense(LicenseCreatorParam param) {
try {
LicenseManager licenseManager = new LicenseManager(initLicenseParam());
LicenseContent licenseContent = initLicenseContent(param);
licenseManager.store(licenseContent, new File(licenseProperties.getLicensePath()));
return true;
} catch (Exception e) {
logger.error("Certificate generation failed:", e);
throw new BizException("Failed to generate license certificate");
}
}
/** Initialize parameters for license generation */
private LicenseParam initLicenseParam() {
Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);
CipherParam cipherParam = new DefaultCipherParam(licenseProperties.getStorePass());
KeyStoreParam privateStoreParam = new CustomKeyStoreParam(
LicenseCreator.class,
licenseProperties.getPrivateKeysStorePath(),
licenseProperties.getPrivateAlias(),
licenseProperties.getStorePass(),
licenseProperties.getKeyPass());
return new DefaultLicenseParam(licenseProperties.getSubject(), preferences, privateStoreParam, cipherParam);
}
/** Fill the license content */
private LicenseContent initLicenseContent(LicenseCreatorParam param) {
LicenseContent licenseContent = new LicenseContent();
licenseContent.setHolder(DEFAULT_HOLDER_AND_ISSUER);
licenseContent.setIssuer(DEFAULT_HOLDER_AND_ISSUER);
licenseContent.setIssued(param.getIssuedTime());
licenseContent.setNotBefore(param.getIssuedTime());
licenseContent.setNotAfter(param.getExpiryTime() == null ? addYears(new Date(), 10) : param.getExpiryTime());
licenseContent.setConsumerType(param.getConsumerType());
licenseContent.setConsumerAmount(param.getConsumerAmount());
licenseContent.setInfo(param.getDescription());
licenseContent.setExtra(param.getSystemInfo());
return licenseContent;
}
public Date addYears(Date date, int n) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.YEAR, n);
return cal.getTime();
}
}License Verification
Two verification scenarios are supported: (1) validation at service startup, which checks legality, expiry, and system info; (2) annotation‑based validation on specific API methods.
The verification flow is illustrated by the following diagram:
Runtime verification is performed by implementing ApplicationRunner to invoke LicenseVerify.install() during startup.
@Order(OrderConstant.RUNNER_LICENSE)
public class LicenseCheckApplicationRunner implements ApplicationRunner {
@Resource
private LicenseVerify licenseVerify;
@Override
public void run(ApplicationArguments args) throws Exception {
LicenseContent content = licenseVerify.install();
}
}Conditional bean registration makes the verification component pluggable based on a configuration property.
@Bean
@ConditionalOnProperty(name = "ptc.license.start-check", havingValue = "true", matchIfMissing = true)
public LicenseCheckApplicationRunner licenseCheckApplicationRunner() {
return new LicenseCheckApplicationRunner();
}An AOP aspect ( @License annotation) provides method‑level checks.
@Aspect
@Order(OrderConstant.AOP_LICENSE)
public class LicenseAspect extends AbstractAspectSupport {
@Resource
private LicenseVerify licenseVerify;
@Pointcut("@annotation(com.plasticene.boot.license.core.anno.License)")
public void licenseAnnotationPointcut() {}
@Around("licenseAnnotationPointcut()")
public Object aroundLicense(ProceedingJoinPoint pjp) throws Throwable {
boolean b = licenseVerify.verify();
if (b) {
return pjp.proceed();
}
return null;
}
}Core Verification Logic
@Component
public class LicenseVerify {
@Resource
private LicenseProperties licenseProperties;
private static final Logger logger = LogManager.getLogger(LicenseVerify.class);
private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/** Install the license at service startup */
public synchronized LicenseContent install() {
try {
LicenseManager licenseManager = new LicenseManager(initLicenseParam());
licenseManager.uninstall();
LicenseContent result = licenseManager.install(new File(licenseProperties.getLicensePath()));
verifySystemInfo(result);
logger.info("License installed successfully, valid period: {} - {}", df.format(result.getNotBefore()), df.format(result.getNotAfter()));
return result;
} catch (Exception e) {
logger.error("License installation failed:", e);
throw new BizException("License installation failed");
}
}
/** Verify license during API calls */
public boolean verify() {
try {
LicenseManager licenseManager = new LicenseManager(initLicenseParam());
LicenseContent licenseContent = licenseManager.verify();
verifyExpiry(licenseContent);
return true;
} catch (Exception e) {
logger.error("License verification failed:", e);
throw new BizException("License verification failed");
}
}
private LicenseParam initLicenseParam() {
Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);
CipherParam cipherParam = new DefaultCipherParam(licenseProperties.getStorePass());
KeyStoreParam publicStoreParam = new CustomKeyStoreParam(
LicenseVerify.class,
licenseProperties.getPublicKeysStorePath(),
licenseProperties.getPublicAlias(),
licenseProperties.getStorePass(),
null);
return new DefaultLicenseParam(licenseProperties.getSubject(), preferences, publicStoreParam, cipherParam);
}
private void verifyExpiry(LicenseContent licenseContent) {
Date expiry = licenseContent.getNotAfter();
Date current = new Date();
if (current.after(expiry)) {
throw new BizException("License has expired");
}
}
private void verifySystemInfo(LicenseContent licenseContent) {
if (licenseProperties.getVerifySystemSwitch()) {
SystemInfo systemInfo = (SystemInfo) licenseContent.getExtra();
VerifySystemType verifySystemType = licenseProperties.getVerifySystemType();
switch (verifySystemType) {
case CPU_ID:
checkCpuId(systemInfo.getCpuId());
break;
case SYSTEM_UUID:
checkSystemUuid(systemInfo.getUuid());
break;
default:
}
}
}
private void checkCpuId(String cpuId) {
cpuId = cpuId.trim().toUpperCase();
String systemCpuId = DmcUtils.getCpuId().trim().toUpperCase();
logger.info("Configured cpuId = {}, system cpuId = {}", cpuId, systemCpuId);
if (!Objects.equals(cpuId, systemCpuId)) {
throw new BizException("License CPU ID mismatch");
}
}
private void checkSystemUuid(String uuid) {
uuid = uuid.trim().toUpperCase();
String systemUuid = DmcUtils.getSystemUuid().trim().toUpperCase();
logger.info("Configured uuid = {}, system uuid = {}", uuid, systemUuid);
if (!Objects.equals(uuid, systemUuid)) {
throw new BizException("License UUID mismatch");
}
}
}The article concludes that the full workflow for generating and validating a TrueLicense‑based license is now complete, and the complete source code is available at the provided GitHub repository.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
