I. Project background
-
During the trial period, you do not want your code to be copied by customers. During this period, licenses are used to set the validity period, bind IP addresses, and bind MAC addresses.
-
The licensor directly generates a license for the user. To extend the trial period, you only need to generate a new license without manually modifying the source code.
Ii. Project structure
The picture above shows the overall directory structure of the license project:
-
License: Unified project version management
-
License-api: jar package that a third-party project relies on for certificate verification
-
License-common: a shared package for the API and generate project
-
License-generate: license generation service, used to generate licenses
3. The License project is loaded
package com.xlh.license.api.init; import com.xlh.license.api.license.LicenseVerify; import com.xlh.license.api.param.LicenseVerifyParam; import com.xlh.license.common.constant.RestConstant; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.util.concurrent.atomic.AtomicBoolean; /** * @author: XLH * @description: License start registration * @date: Create in 17:09 2020/9/17 0017 */@Slf4j@Componentpublic class LicenseListener implements ApplicationListener<ContextRefreshedEvent> {/** * certificate subject */ private String subject = "company name "; /** * Public key alias */ private String publicAlias = "publicCert"; /** * Password for accessing the public key library */ private String storePass = "public_password1234"; private final static AtomicBoolean atomicBol = new AtomicBoolean(false); @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (! atomicBol.get()) { log.info(RestConstant.LICENSE_INSTALLING); LicenseVerifyParam param = new LicenseVerifyParam(); param.setSubject(subject); param.setPublicAlias(publicAlias); param.setStorePass(storePass); LicenseVerify licenseVerify = new LicenseVerify(); // Install the License licenseverify. install(param); log.info(RestConstant.LICENSE_FINISH); atomicBol.set(true); } }} package com.xlh.license.api.license; import com.xlh.license.api.holder.LicenseManagerHolder; import com.xlh.license.api.param.LicenseVerifyParam; import com.xlh.license.api.cache.CacheLicenseContextHolder; import com.xlh.license.common.constant.CommonConstant; import com.xlh.license.common.constant.RestConstant; import com.xlh.license.common.exception.LicenseException; import com.xlh.license.common.param.CustomKeyStoreParam; import de.schlichtherle.license.*; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; import org.springframework.util.FileCopyUtils; import java.io.File; import java.io.InputStream; import java.text.DateFormat; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.prefs.Preferences; /** * @author: XLH * @description: License verification class * @date: Create in 17:06 2020/9/17 0017 */@Slf4jpublic class LicenseVerify { private static String LICENSE_PATH; private static String PUBLIC_KEY_STORE_PATH; private static final String PREFIX = "license"; private static final String SUFFIX = ".lic"; { ClassPathResource resource = new ClassPathResource(CommonConstant.LICENSE_FILE); LICENSE_PATH = resource.getPath(); resource = new ClassPathResource(CommonConstant.PUBLIC_KEY_STORE_FILE); PUBLIC_KEY_STORE_PATH = resource.getPath(); } /** * Install a License certificate * @param param * @return */ public synchronized LicenseContent install(LicenseVerifyParam param){if (null == LICENSE_PATH || "" == LICENSE_PATH) { throw new LicenseException(RestConstant.ERROR_CODE, RestConstant.NOT_FOUND_LICENSE); } if (null == PUBLIC_KEY_STORE_PATH || "" == PUBLIC_KEY_STORE_PATH) { throw new LicenseException(RestConstant.ERROR_CODE, RestConstant.NOT_FOUND_PUBLIC_KEY_STORE); } param.setLicensePath(LICENSE_PATH); param.setPublicKeysStorePath(PUBLIC_KEY_STORE_PATH); LicenseContent result; DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); / / install License try {LicenseManager LicenseManager = LicenseManagerHolder. GetInstance (initLicenseParam (param)); licenseManager.uninstall(); ClassPathResource resource = new ClassPathResource(param.getLicensePath()); InputStream inputStream = resource.getInputStream(); File license = File.createTempFile(PREFIX, SUFFIX); byte[] bytes = FileCopyUtils.copyToByteArray(inputStream); FileCopyUtils.copy(bytes, license); result = licenseManager.install(license); log.info(MessageFormat.format(RestConstant.INSTALL_LICENSE_EFFECTIVE, format.format(result.getNotBefore()),format.format(result.getNotAfter()))); CacheLicenseContextHolder.setLicense(result.getNotAfter()); }catch (Exception e){ log.error(RestConstant.LICENSE_FAILURE,e); throw new LicenseException(RestConstant.ERROR_CODE, e.getMessage()); } return result; } /** * public Boolean verify(){DateFormat format = new SimpleDateFormat(" YYYY-MM-DD HH:mm:ss"); Date expiryTime = CacheLicenseContextHolder.getLicense(); log.info(MessageFormat.format(RestConstant.LICENSE_EXPIRY_TIME, format.format(expiryTime))); boolean result = expiryTime.after(new Date()); if (! result) { throw new LicenseException(RestConstant.ERROR_CODE, RestConstant.LICENSE_INVALID); } return true; } /** * Initialize certificate generation parameters * @param param * @return */ private LicenseParam initLicenseParam(LicenseVerifyParam param){ Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class); CipherParam cipherParam = new DefaultCipherParam(param.getStorePass()); KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class ,param.getPublicKeysStorePath() ,param.getPublicAlias() ,param.getStorePass() ,null); return new DefaultLicenseParam(param.getSubject() ,preferences ,publicStoreParam ,cipherParam); }}Copy the code
After the preceding loading, the parent thread caches the license expiration time. Each invocation obtains the cache time of the parent thread to determine whether the license is invalid. If the license is invalid, an exception message is thrown, which is encapsulated in a unified manner and returned to the front end for display
package com.xlh.license.api.cache; import java.util.Date; /** * @author: XLH * @description: License cache * @date: Create in 11:01 2020/10/12 0012 */public class CacheLicenseContextHolder { private final static InheritableThreadLocal<Date> LICENSE_THREAD_LOCAL = new InheritableThreadLocal<>(); public static void setLicense(Date expiryTime) { LICENSE_THREAD_LOCAL.set(expiryTime); } public static Date getLicense() { return LICENSE_THREAD_LOCAL.get(); } public static void clearLicense() { LICENSE_THREAD_LOCAL.remove(); }}Copy the code
4. Verify the License on the client
package com.xlh.license.api.interceptor; import com.xlh.license.api.license.LicenseVerify; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author: XLH * @description: License inspector * @date: Create in 17:13 2020/9/17 0017 */@Slf4j@Componentpublic class LicenseInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { LicenseVerify licenseVerify = new LicenseVerify(); // Verify the License licenseVerify. Verify (); return true; }}Copy the code
5. Use the JDK keytool to generate public and private key certificates
Keytool -genkeypair -keysize 1024 -validity 3650 -alias "privateKey" -keystore "privatekeys. keystore" -storepass "public_password1234" -keypass "private_password1234" -dname "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN"# export command keytool -exportcert -alias "privateKey" -keystore "privatekeys. keystore" -storepass "public_password1234" -file "certfile.cer"# import command keytool -import -alias "publicCert" -file "certfile. -storepass "public_password1234"Copy the code
After the preceding command is executed, three files are generated in the current path: Keystore file privatekeys. keystore is used to generate the license file for the project, and publicCerts.keystore file is deployed to the client server along with the application code. Users decrypt license files and verify license information.
Generate Licesne file
{"subject": "company name ", "privateAlias": "privateKey", "keyPass": "private_password1234", "storePass": "public_password1234", "privateKeysStorePath": "privateKeys.keystore", "expiryTime": "2199-12-01 23:59:59", "consumerType": "User", "consumerAmount": 1, "description": Copyright @ 2020-09-23 to 2199-12-01 all Rights Reserved, "licenseModel": {"ipAddress": [" 10.23.16.14 10.23.16.128 ", ""," 10.23.16.12 ", "10.23.16.111", "172.17.0.1]", "macAddress" : ["44:39:C4:32:E7:1E", "8c:89:a5:bb:e9:5c", "44:39:C4:32:E7:F6", "44:39:C4:4F:DD:65", "02-42-6E-B7-E8-3C"], "cpuSerial": "-1", "mainBoardSerial": "-1" }}Copy the code