License介绍
License,即版权许可证,一般用于收费软件给付费用户提供的访问许可证明。根据应用部署位置的不同,一般可以分为以下两种情况讨论:
- 应用部署在开发者自己的云服务器上。这种情况下用户通过账号登录的形式远程访问,因此只需要在账号登录的时候校验目标账号的有效期、访问权限等信息即可。
- 应用部署在客户的内网环境。因为这种情况开发者无法控制客户的网络环境,也不能保证应用所在服务器可以访问外网,因此通常的做法是使用服务器许可文件,在应用启动的时候加载证书,然后在登录或者其他关键操作的地方校验证书的有效性。
- 部署到客户现场,但是客户服务器可以联网,也可以采用办法许可证书进行验证,也可以远程认证-认证服务器提供认证请求,必须走线上认证才可使用。
license授权机制的原理
TrueLicense是一个开源的证书管理引擎。
- 生成密钥对,使用Keytool生成公私钥证书库。
- 授权者保留私钥,使用私钥对包含授权信息(如使用截止日期,MAC地址等)的license进行数字签名。
公钥给使用者(放在验证的代码中使用),用于验证license是否符合使用条件。
springboot整合TrueLicense-生成License证书
1、引入依赖
<!-- License --><dependency><groupId>de.schlichtherle.truelicense</groupId><artifactId>truelicense-core</artifactId><version>1.33</version></dependency>
2、新增LicenseCheckModel自定义校验参数
```java /**
- @title LicenseCheckModel
- @description 自定义需要校验的License参数
- @author Administrator
@updateTime 2022/4/30 0030 18:19 */ @Data public class LicenseCheckModel implements Serializable{
private static final long serialVersionUID = 8600137500316662317L; /**
- 可被允许的IP地址
*/
private List
ipAddress;
/**
- 可被允许的MAC地址
*/
private List
macAddress;
/**
- 可被允许的CPU序列号 */ private String cpuSerial;
/**
- 可被允许的主板序列号 */ private String mainBoardSerial;
- 可被允许的IP地址
*/
private List
}
<a name="Ls8YF"></a>## 3、新增LicenseCreatorParam生成类参数```java/*** @ProjectName LicenseCreatorParam* @author Administrator* @version 1.0.0* @Description License生成类需要的参数* @createTime 2022/4/30 0030 18:19*/@Datapublic class LicenseCreatorParam implements Serializable {private static final long serialVersionUID = -7793154252684580872L;/*** 证书subject*/private String subject;/*** 密钥别称*/private String privateAlias;/*** 密钥密码(需要妥善保管,不能让使用者知道)*/private String keyPass;/*** 访问秘钥库的密码*/private String storePass;/*** 证书生成路径*/private String licensePath;/*** 密钥库存储路径*/private String privateKeysStorePath;/*** 证书生效时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date issuedTime = new Date();/*** 证书失效时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date expiryTime;/*** 用户类型*/private String consumerType = "user";/*** 用户数量*/private Integer consumerAmount = 1;/*** 描述信息*/private String description = "";/*** 额外的服务器硬件校验信息*/private LicenseCheckModel licenseCheckModel;}
4、新建抽象类AbstractServerInfos
用户获取服务器的硬件信息
/*** @ProjectName AbstractServerInfos* @author Administrator* @version 1.0.0* @Description 用于获取客户服务器的基本信息,如:IP、Mac地址、CPU序列号、主板序列号等* @createTime 2022/4/30 0030 18:15*/public abstract class AbstractServerInfos {private static Logger logger = LogManager.getLogger(AbstractServerInfos.class);/*** @title getServerInfos* @description 组装需要额外校验的License参数* @author Administrator* @updateTime 2022/4/30 0030 18:15*/public LicenseCheckModel getServerInfos(){LicenseCheckModel result = new LicenseCheckModel();try {result.setIpAddress(this.getIpAddress());result.setMacAddress(this.getMacAddress());result.setCpuSerial(this.getCPUSerial());result.setMainBoardSerial(this.getMainBoardSerial());}catch (Exception e){logger.error("获取服务器硬件信息失败",e);}return result;}/*** @title getIpAddress* @description 获取IP地址* @author Administrator* @updateTime 2022/4/30 0030 18:15*/protected abstract List<String> getIpAddress() throws Exception;/*** @title getMacAddress* @description 获取Mac地址* @author Administrator* @updateTime 2022/4/30 0030 18:16*/protected abstract List<String> getMacAddress() throws Exception;/*** @title getCPUSerial* @description 获取CPU序列号* @author Administrator* @updateTime 2022/4/30 0030 18:16*/protected abstract String getCPUSerial() throws Exception;/*** @title getMainBoardSerial* @description 获取主板序列号* @author Administrator* @updateTime 2022/4/30 0030 18:16*/protected abstract String getMainBoardSerial() throws Exception;/*** @title getLocalAllInetAddress* @description 获取当前服务器所有符合条件的InetAddress* @author Administrator* @updateTime 2022/4/30 0030 18:16*/protected List<InetAddress> getLocalAllInetAddress() throws Exception {List<InetAddress> result = new ArrayList<>(4);// 遍历所有的网络接口for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {NetworkInterface iface = (NetworkInterface) networkInterfaces.nextElement();// 在所有的接口下再遍历IPfor (Enumeration inetAddresses = iface.getInetAddresses(); inetAddresses.hasMoreElements(); ) {InetAddress inetAddr = (InetAddress) inetAddresses.nextElement();//排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址if(!inetAddr.isLoopbackAddress() /*&& !inetAddr.isSiteLocalAddress()*/&& !inetAddr.isLinkLocalAddress() && !inetAddr.isMulticastAddress()){result.add(inetAddr);}}}return result;}/*** @title getMacByInetAddress* @description 获取某个网络接口的Mac地址* @author Administrator* @updateTime 2022/4/30 0030 18:16*/protected String getMacByInetAddress(InetAddress inetAddr){try {byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress();StringBuffer stringBuffer = new StringBuffer();for(int i=0;i<mac.length;i++){if(i != 0) {stringBuffer.append("-");}//将十六进制byte转化为字符串String temp = Integer.toHexString(mac[i] & 0xff);if(temp.length() == 1){stringBuffer.append("0" + temp);}else{stringBuffer.append(temp);}}return stringBuffer.toString().toUpperCase();} catch (SocketException e) {e.printStackTrace();}return null;}}
新增LinuxServerInfos获取硬件信息
/*** @ProjectName LinuxServerInfos* @author Administrator* @version 1.0.0* @Description 用于获取客户Linux服务器的基本信息* @createTime 2022/4/30 0030 18:20*/public class LinuxServerInfos extends AbstractServerInfos {@Overrideprotected List<String> getIpAddress() throws Exception {List<String> result = null;//获取所有网络接口List<InetAddress> inetAddresses = getLocalAllInetAddress();if(inetAddresses != null && inetAddresses.size() > 0){result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());}return result;}@Overrideprotected List<String> getMacAddress() throws Exception {List<String> result = null;//1. 获取所有网络接口List<InetAddress> inetAddresses = getLocalAllInetAddress();if(inetAddresses != null && inetAddresses.size() > 0){//2. 获取所有网络接口的Mac地址result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());}return result;}@Overrideprotected String getCPUSerial() throws Exception {//序列号String serialNumber = "";//使用dmidecode命令获取CPU序列号String[] shell = {"/bin/bash","-c","dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"};Process process = Runtime.getRuntime().exec(shell);process.getOutputStream().close();BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line = reader.readLine().trim();if(StringUtils.isNotBlank(line)){serialNumber = line;}reader.close();return serialNumber;}@Overrideprotected String getMainBoardSerial() throws Exception {//序列号String serialNumber = "";//使用dmidecode命令获取主板序列号String[] shell = {"/bin/bash","-c","dmidecode | grep 'Serial Number' | awk -F ':' '{print $2}' | head -n 1"};Process process = Runtime.getRuntime().exec(shell);process.getOutputStream().close();BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line = reader.readLine().trim();if(StringUtils.isNotBlank(line)){serialNumber = line;}reader.close();return serialNumber;}}
新增WindowsServer获取硬件信息
/*** @ProjectName WindowsServerInfos* @author Administrator* @version 1.0.0* @Description 用于获取客户Windows服务器的基本信息* @createTime 2022/4/30 0030 18:20*/public class WindowsServerInfos extends AbstractServerInfos {@Overrideprotected List<String> getIpAddress() throws Exception {List<String> result = null;//获取所有网络接口List<InetAddress> inetAddresses = getLocalAllInetAddress();if(inetAddresses != null && inetAddresses.size() > 0){result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());}return result;}@Overrideprotected List<String> getMacAddress() throws Exception {List<String> result = null;//1. 获取所有网络接口List<InetAddress> inetAddresses = getLocalAllInetAddress();if(inetAddresses != null && inetAddresses.size() > 0){//2. 获取所有网络接口的Mac地址result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());}return result;}@Overrideprotected String getCPUSerial() throws Exception {//序列号String serialNumber = "";//使用WMIC获取CPU序列号Process process = Runtime.getRuntime().exec("wmic cpu get processorid");process.getOutputStream().close();Scanner scanner = new Scanner(process.getInputStream());if(scanner.hasNext()){scanner.next();}if(scanner.hasNext()){serialNumber = scanner.next().trim();}scanner.close();return serialNumber;}@Overrideprotected String getMainBoardSerial() throws Exception {//序列号String serialNumber = "";//使用WMIC获取主板序列号Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber");process.getOutputStream().close();Scanner scanner = new Scanner(process.getInputStream());if(scanner.hasNext()){scanner.next();}if(scanner.hasNext()){serialNumber = scanner.next().trim();}scanner.close();return serialNumber;}}
注:这里使用了模板方法模式,将不变部分的算法封装到抽象类,而基本方法的具体实现则由子类来实现。
5、新增自定义CustomKeyStoreParam
自定义KeyStoreParam类CustomKeyStoreParam类继承AbstractKeyStoreParam,实现里面一些该实现的方法。并且重写getStream()获取文件内容的方法,改成从磁盘位置读取。
/*** @ProjectName CustomKeyStoreParam* @author Administrator* @version 1.0.0* @Description 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中* @createTime 2022/4/30 0030 18:16*/public class CustomKeyStoreParam extends AbstractKeyStoreParam {/*** 公钥/私钥在磁盘上的存储路径*/private String storePath;private String alias;private String storePwd;private String keyPwd;public CustomKeyStoreParam(Class clazz, String resource,String alias,String storePwd,String keyPwd) {super(clazz, resource);this.storePath = resource;this.alias = alias;this.storePwd = storePwd;this.keyPwd = keyPwd;}@Overridepublic String getAlias() {return alias;}@Overridepublic String getStorePwd() {return storePwd;}@Overridepublic String getKeyPwd() {return keyPwd;}/*** @title getStream* @description* 复写de.schlichtherle.license.AbstractKeyStoreParam的getStream()方法<br/>* 用于将公私钥存储文件存放到其他磁盘位置而不是项目中* @author Administrator* @updateTime 2022/4/30 0030 18:16*/@Overridepublic InputStream getStream() throws IOException {final InputStream in = new FileInputStream(new File(storePath));if (null == in){throw new FileNotFoundException(storePath);}return in;}}
6、新增自定义LicenseManager
继承LicenseManager类,增加我们额外信息的验证(TrueLicense默认只给我们验证了时间)。大家需要根据自己的需求在validate()里面增加额外的验证。
/*** @title CustomLicenseManager* @description 自定义LicenseManager,用于增加额外的服务器硬件信息校验* @author Administrator* @updateTime 2022/4/30 0030 18:17*/public class CustomLicenseManager extends LicenseManager{private static Logger logger = LogManager.getLogger(CustomLicenseManager.class);//XML编码private static final String XML_CHARSET = "UTF-8";//默认BUFSIZEprivate static final int DEFAULT_BUFSIZE = 8 * 1024;public CustomLicenseManager() {}public CustomLicenseManager(LicenseParam param) {super(param);}/*** @title create* @description 复写create方法* @author Administrator* @updateTime 2022/4/30 0030 18:17*/@Overrideprotected synchronized byte[] create(LicenseContent content,LicenseNotary notary)throws Exception {initialize(content);this.validateCreate(content);final GenericCertificate certificate = notary.sign(content);return getPrivacyGuard().cert2key(certificate);}/*** @title install* @description 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息* @author Administrator* @updateTime 2022/4/30 0030 18:17*/@Overrideprotected synchronized LicenseContent install(final byte[] key,final LicenseNotary notary)throws Exception {final GenericCertificate certificate = getPrivacyGuard().key2cert(key);notary.verify(certificate);final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());this.validate(content);setLicenseKey(key);setCertificate(certificate);return content;}/*** @title verify* @description 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息* @author Administrator* @updateTime 2022/4/30 0030 18:17*/@Overrideprotected synchronized LicenseContent verify(final LicenseNotary notary)throws Exception {GenericCertificate certificate = getCertificate();// Load license key from preferences,final byte[] key = getLicenseKey();if (null == key){throw new NoLicenseInstalledException(getLicenseParam().getSubject());}certificate = getPrivacyGuard().key2cert(key);notary.verify(certificate);final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());this.validate(content);setCertificate(certificate);return content;}/*** @title validateCreate* @description 校验生成证书的参数信息* @author Administrator* @updateTime 2022/4/30 0030 18:18*/protected synchronized void validateCreate(final LicenseContent content)throws LicenseContentException {final LicenseParam param = getLicenseParam();final Date now = new Date();final Date notBefore = content.getNotBefore();final Date notAfter = content.getNotAfter();if (null != notAfter && now.after(notAfter)){throw new LicenseContentException("证书失效时间不能早于当前时间");}if (null != notBefore && null != notAfter && notAfter.before(notBefore)){throw new LicenseContentException("证书生效时间不能晚于证书失效时间");}final String consumerType = content.getConsumerType();if (null == consumerType){throw new LicenseContentException("用户类型不能为空");}}/*** @title validate* @description 复写validate方法,增加IP地址、Mac地址等其他信息校验* @author Administrator* @updateTime 2022/4/30 0030 18:18*/@Overrideprotected synchronized void validate(final LicenseContent content)throws LicenseContentException {//1. 首先调用父类的validate方法super.validate(content);//2. 然后校验自定义的License参数//License中可被允许的参数信息LicenseCheckModel expectedCheckModel = (LicenseCheckModel) content.getExtra();//当前服务器真实的参数信息LicenseCheckModel serverCheckModel = getServerInfos();if(expectedCheckModel != null && serverCheckModel != null){//校验IP地址if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){throw new LicenseContentException("当前服务器的IP没在授权范围内");}//校验Mac地址if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){throw new LicenseContentException("当前服务器的Mac地址没在授权范围内");}//校验主板序列号if(!checkSerial(expectedCheckModel.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){throw new LicenseContentException("当前服务器的主板序列号没在授权范围内");}//校验CPU序列号if(!checkSerial(expectedCheckModel.getCpuSerial(),serverCheckModel.getCpuSerial())){throw new LicenseContentException("当前服务器的CPU序列号没在授权范围内");}}else{throw new LicenseContentException("不能获取服务器硬件信息");}}/*** @title load* @description 重写XMLDecoder解析XML* @author Administrator* @updateTime 2022/4/30 0030 18:18*/private Object load(String encoded){BufferedInputStream inputStream = null;XMLDecoder decoder = null;try {inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XML_CHARSET)));decoder = new XMLDecoder(new BufferedInputStream(inputStream, DEFAULT_BUFSIZE),null,null);return decoder.readObject();} catch (UnsupportedEncodingException e) {e.printStackTrace();} finally {try {if(decoder != null){decoder.close();}if(inputStream != null){inputStream.close();}} catch (Exception e) {logger.error("XMLDecoder解析XML失败",e);}}return null;}/*** @title getServerInfos* @description 获取当前服务器需要额外校验的License参数* @author Administrator* @updateTime 2022/4/30 0030 18:18*/private LicenseCheckModel getServerInfos(){//操作系统类型String osName = System.getProperty("os.name").toLowerCase();AbstractServerInfos abstractServerInfos = null;//根据不同操作系统类型选择不同的数据获取方法if (osName.startsWith("windows")) {abstractServerInfos = new WindowsServerInfos();} else if (osName.startsWith("linux")) {abstractServerInfos = new LinuxServerInfos();}else{//其他服务器类型abstractServerInfos = new LinuxServerInfos();}return abstractServerInfos.getServerInfos();}/*** @title checkIpAddress* @description* 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内<br/>* 如果存在IP在可被允许的IP/Mac地址范围内,则返回true* @author Administrator* @updateTime 2022/4/30 0030 18:18*/private boolean checkIpAddress(List<String> expectedList,List<String> serverList){if(expectedList != null && expectedList.size() > 0){if(serverList != null && serverList.size() > 0){for(String expected : expectedList){if(serverList.contains(expected.trim())){return true;}}}return false;}else {return true;}}/*** @title checkSerial* @description 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内* @author Administrator* @updateTime 2022/4/30 0030 18:18*/private boolean checkSerial(String expectedSerial,String serverSerial){if(StringUtils.isNotBlank(expectedSerial)){if(StringUtils.isNotBlank(serverSerial)){if(expectedSerial.equals(serverSerial)){return true;}}return false;}else{return true;}}}
7、新增LicenseCreator证书生成类
/*** @ProjectName LicenseCreator* @author Administrator* @version 1.0.0* @Description License生成类* @createTime 2022/4/30 0030 18:19*/public class LicenseCreator {private static Logger logger = LogManager.getLogger(LicenseCreator.class);private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN");private LicenseCreatorParam param;public LicenseCreator(LicenseCreatorParam param) {this.param = param;}/*** @title generateLicense* @description 生成License证书* @author Administrator* @updateTime 2022/4/30 0030 18:19*/public boolean generateLicense(){try {LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam());LicenseContent licenseContent = initLicenseContent();licenseManager.store(licenseContent,new File(param.getLicensePath()));return true;}catch (Exception e){logger.error(MessageFormat.format("证书生成失败:{0}",param),e);return false;}}/*** @title initLicenseParam* @description 初始化证书生成参数* @author Administrator* @updateTime 2022/4/30 0030 18:19*/private LicenseParam initLicenseParam(){Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);//设置对证书内容加密的秘钥CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class,param.getPrivateKeysStorePath(),param.getPrivateAlias(),param.getStorePass(),param.getKeyPass());LicenseParam licenseParam = new DefaultLicenseParam(param.getSubject(),preferences,privateStoreParam,cipherParam);return licenseParam;}/*** @title initLicenseContent* @description 设置证书生成正文信息* @author Administrator* @updateTime 2022/4/30 0030 18:19*/private LicenseContent initLicenseContent(){LicenseContent licenseContent = new LicenseContent();licenseContent.setHolder(DEFAULT_HOLDER_AND_ISSUER);licenseContent.setIssuer(DEFAULT_HOLDER_AND_ISSUER);licenseContent.setSubject(param.getSubject());licenseContent.setIssued(param.getIssuedTime());licenseContent.setNotBefore(param.getIssuedTime());licenseContent.setNotAfter(param.getExpiryTime());licenseContent.setConsumerType(param.getConsumerType());licenseContent.setConsumerAmount(param.getConsumerAmount());licenseContent.setInfo(param.getDescription());//扩展校验服务器硬件信息licenseContent.setExtra(param.getLicenseCheckModel());return licenseContent;}}
8、新增LicenseCreatorController证书生成
这个Controller对外提供了两个RESTful接口,分别是「获取服务器硬件信息」和「生成证书」
/*** @ProjectName LicenseCreatorController* @author Administrator* @version 1.0.0* @Description 于生成证书文件,不能放在给客户部署的代码里* @createTime 2022/4/30 0030 18:13*/@RestController@RequestMapping("/license")public class LicenseCreatorController {/*** 证书生成路径*/@Value("${license.licensePath}")private String licensePath;/*** @title 获取服务器硬件信息* @description @param osName 操作系统类型,如果为空则自动判断* @author Administrator* @updateTime 2022/4/30 0030 18:14*/@RequestMapping(value = "/getServerInfos")public LicenseCheckModel getServerInfos(@RequestParam(value = "osName",required = false) String osName) {//操作系统类型if(StringUtils.isBlank(osName)){osName = System.getProperty("os.name");}osName = osName.toLowerCase();AbstractServerInfos abstractServerInfos = null;//根据不同操作系统类型选择不同的数据获取方法if (osName.startsWith("windows")) {abstractServerInfos = new WindowsServerInfos();} else if (osName.startsWith("linux")) {abstractServerInfos = new LinuxServerInfos();}else{//其他服务器类型abstractServerInfos = new LinuxServerInfos();}return abstractServerInfos.getServerInfos();}/*** @title 生成证书* @description* {* "result": "ok",* "msg": {* "subject": "license_demo",* "privateAlias": "privateKey",* "keyPass": "private_password1234",* "storePass": "public_password1234",* "licensePath": "D:/license/license.lic",* "privateKeysStorePath": "D:/license/privateKeys.keystore",* "issuedTime": "2022-04-10 00:00:01",* "expiryTime": "2022-05-31 23:59:59",* "consumerType": "User",* "consumerAmount": 1,* "description": "这是证书描述信息",* "licenseCheckModel": {* "ipAddress": [],* "macAddress": [],* "cpuSerial": "",* "mainBoardSerial": ""* }* }* }* @author Administrator* @updateTime 2022/4/30 0030 18:14*/@RequestMapping(value = "/generateLicense",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})public Map<String,Object> generateLicense(@RequestBody(required = true) LicenseCreatorParam param) {Map<String,Object> resultMap = new HashMap<>(2);if(StringUtils.isBlank(param.getLicensePath())){param.setLicensePath(licensePath);}LicenseCreator licenseCreator = new LicenseCreator(param);boolean result = licenseCreator.generateLicense();if(result){resultMap.put("result","ok");resultMap.put("msg",param);}else{resultMap.put("result","error");resultMap.put("msg","证书文件生成失败!");}return resultMap;}}
9、使用Keytool生成公私钥证书库
假如我们设置公钥库密码为:public_password1234,私钥库密码为:private_password1234,则生成命令如下:
## 1. 生成私匙库# validity:私钥的有效期多少天# alias:私钥别称# keystore: 指定私钥库文件的名称(生成在当前目录)# storepass:指定私钥库的密码(获取keystore信息所需的密码)# keypass:指定别名条目的密码(私钥的密码)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"## 2. 把私匙库内的公匙导出到一个文件当中# alias:私钥别称# keystore:指定私钥库的名称(在当前目录查找)# storepass: 指定私钥库的密码# file:证书名称keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -file "certfile.cer"## 3. 再把这个证书文件导入到公匙库# alias:公钥别称# file:证书名称# keystore:公钥文件名称# storepass:指定私钥库的密码keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "public_password1234"
述命令执行完成之后,会在当前路径下生成三个文件,分别是:privateKeys.keystore、publicCerts.keystore、certfile.cer。其中文件certfile.cer不再需要可以删除,文件privateKeys.keystore用于当前的 qingfeng-server 项目给客户生成license文件,而文件publicCerts.keystore则随应用代码部署到客户qingfeng-client服务器,用户解密license文件并校验其许可信息。
10、生成license证书
获取服务器硬件信息
请求地址:http://localhost:8000/license/getServerInfos
生成license证书
访问地址:http://localhost:8000/license/generateLicense
请求时需要在Header中添加一个 Content-Type ,其值为:application/json;charset=UTF-8。参数示例如下
{"subject": "license_demo","privateAlias": "privateKey","keyPass": "private_password1234","storePass": "public_password1234","licensePath": "D:/license/license.lic","privateKeysStorePath": "D:/license/privateKeys.keystore","issuedTime": "2022-04-10 00:00:01","expiryTime": "2022-05-31 23:59:59","consumerType": "User","consumerAmount": 1,"description": "这是证书描述信息","licenseCheckModel": {"ipAddress": [],"macAddress": [],"cpuSerial": "","mainBoardSerial": ""}}
如果请求成功,那么最后会在 licensePath 参数设置的路径生成一个 license.lic 的文件,这个文件就是给客户部署代码的服务器许可文件。
新建Test测试类生成证书
/*** @author Administrator* @version 1.0.0* @ProjectName qingfeng-license* @Description TODO* @createTime 2022年04月30日 21:27:00*/@SpringBootTestpublic class LicenseTest {/*** {* "subject": "license_demo",* "privateAlias": "privateKey",* "keyPass": "private_password1234",* "storePass": "public_password1234",* "licensePath": "D:/license/license.lic",* "privateKeysStorePath": "D:/license/privateKeys.keystore",* "issuedTime": "2022-04-10 00:00:01",* "expiryTime": "2022-05-31 23:59:59",* "consumerType": "User",* "consumerAmount": 1,* "description": "这是证书描述信息",* "licenseCheckModel": {* "ipAddress": [],* "macAddress": [],* "cpuSerial": "",* "mainBoardSerial": ""* }* }*/@Testpublic void licenseCreate() {// 生成license需要的一些参数LicenseCreatorParam param = new LicenseCreatorParam();param.setSubject("license_demo");param.setPrivateAlias("privateKey");param.setKeyPass("private_password1234");param.setStorePass("public_password1234");param.setLicensePath("D:/license/license.lic");param.setPrivateKeysStorePath("D:/license/privateKeys.keystore");Calendar issueCalendar = Calendar.getInstance();param.setIssuedTime(issueCalendar.getTime());Calendar expiryCalendar = Calendar.getInstance();expiryCalendar.set(2022, Calendar.JUNE, 31, 23, 59, 59);param.setExpiryTime(expiryCalendar.getTime());param.setConsumerType("user");param.setConsumerAmount(1);param.setDescription("这是证书描述信息");//自定义需要校验的License参数LicenseCheckModel licenseCheckModel = new LicenseCheckModel();licenseCheckModel.setCpuSerial("");licenseCheckModel.setMainBoardSerial("");licenseCheckModel.setIpAddress(new ArrayList<>());licenseCheckModel.setMacAddress(new ArrayList<>());LicenseCreator licenseCreator = new LicenseCreator(param);param.setLicenseCheckModel(licenseCheckModel);// 生成licenselicenseCreator.generateLicense();}}
springboot整合TrueLicense-验证License证书
1、新增License校验类参数
/*** @ProjectName LicenseVerifyParam* @author Administrator* @version 1.0.0* @Description License校验类需要的参数* @createTime 2022/4/30 0030 18:29*/@Datapublic class LicenseVerifyParam {/*** 证书subject*/private String subject;/*** 公钥别称*/private String publicAlias;/*** 访问公钥库的密码*/private String storePass;/*** 证书生成路径*/private String licensePath;/*** 密钥库存储路径*/private String publicKeysStorePath;public LicenseVerifyParam() {}public LicenseVerifyParam(String subject, String publicAlias, String storePass, String licensePath, String publicKeysStorePath) {this.subject = subject;this.publicAlias = publicAlias;this.storePass = storePass;this.licensePath = licensePath;this.publicKeysStorePath = publicKeysStorePath;}}
2、新增LicenseVerify校验类
/*** @ProjectName LicenseVerify* @author Administrator* @version 1.0.0* @Description License校验类* @createTime 2022/4/30 0030 18:28*/public class LicenseVerify {private static Logger logger = LogManager.getLogger(LicenseVerify.class);/*** @title install* @description 安装License证书* @author Administrator* @updateTime 2022/4/30 0030 18:29*/public synchronized LicenseContent install(LicenseVerifyParam param){LicenseContent result = null;DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//1. 安装证书try{LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param));licenseManager.uninstall();result = licenseManager.install(new File(param.getLicensePath()));logger.info(MessageFormat.format("证书安装成功,证书有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter())));}catch (Exception e){logger.error("证书安装失败!",e);}return result;}/*** @title verify* @description 校验License证书* @author Administrator* @updateTime 2022/4/30 0030 18:29*/public boolean verify(){LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//2. 校验证书try {LicenseContent licenseContent = licenseManager.verify();// System.out.println(licenseContent.getSubject());logger.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter())));return true;}catch (Exception e){logger.error("证书校验失败!",e);return false;}}/*** @title initLicenseParam* @description 初始化证书生成参数* @author Administrator* @updateTime 2022/4/30 0030 18:29*/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);}}
3、新增LicenseCheckListener监听器
用于在项目启动的时候安装License证书
/*** @ProjectName LicenseCheckListener* @author Administrator* @version 1.0.0* @Description 在项目启动时安装证书* @createTime 2022/4/30 0030 18:28*/@Componentpublic class LicenseCheckListener implements ApplicationListener<ContextRefreshedEvent> {private static Logger logger = LogManager.getLogger(LicenseCheckListener.class);/*** 证书subject*/@Value("${license.subject}")private String subject;/*** 公钥别称*/@Value("${license.publicAlias}")private String publicAlias;/*** 访问公钥库的密码*/@Value("${license.storePass}")private String storePass;/*** 证书生成路径*/@Value("${license.licensePath}")private String licensePath;/*** 密钥库存储路径*/@Value("${license.publicKeysStorePath}")private String publicKeysStorePath;@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {//root application context 没有parentApplicationContext context = event.getApplicationContext().getParent();if(context == null){if(StringUtils.isNotBlank(licensePath)){logger.info("++++++++ 开始安装证书 ++++++++");LicenseVerifyParam param = new LicenseVerifyParam();param.setSubject(subject);param.setPublicAlias(publicAlias);param.setStorePass(storePass);param.setLicensePath(licensePath);param.setPublicKeysStorePath(publicKeysStorePath);LicenseVerify licenseVerify = new LicenseVerify();//安装证书licenseVerify.install(param);logger.info("++++++++ 证书安装结束 ++++++++");}}}}
上面代码使用参数信息如下所示:
#License相关配置license.subject=license_demolicense.publicAlias=publicCertlicense.storePass=public_password1234license.licensePath=D:/license/license.liclicense.publicKeysStorePath=D:/license/publicCerts.keystore
4、新增拦截器-请求验证证书
/*** @ProjectName LicenseCheckInterceptor* @author Administrator* @version 1.0.0* @Description LicenseCheckInterceptor* @createTime 2022/4/30 0030 18:27*/public class LicenseCheckInterceptor implements HandlerInterceptor {private static Logger logger = LogManager.getLogger(LicenseCheckInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {LicenseVerify licenseVerify = new LicenseVerify();//校验证书是否有效boolean verifyResult = licenseVerify.verify();if(verifyResult){return true;}else{response.setCharacterEncoding("utf-8");Map<String,String> result = new HashMap<>(1);result.put("result","您的证书无效,请核查服务器是否取得授权或重新申请证书!");response.getWriter().write(JSON.toJSONString(result));return false;}}}
5、新建WebMvcConfig注册拦截器
/*** @ProjectName WebMvcConfig* @author Administrator* @version 1.0.0* @Description 注册拦截器* @createTime 2022/4/30 0030 21:11*/@Configurationpublic class WebMvcConfig implements WebMvcConfigurer {/*** 添加拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LicenseCheckInterceptor()).addPathPatterns("/check");}}
6、启动项目测试

