赞
踩
License即版权许可证,一般用于收费软件给付费用户提供的访问许可证明。根据应用部署位置的不同,一般可以分为以下两种情况讨论:
Spring Boot2 使用 TrueLicense 为客户生成 License 许可文件,创建微服务项目名称: cloud-license-serve,版本:2.3.0
- <!-- License -->
- <dependency>
- <groupId>de.schlichtherle.truelicense</groupId>
- <artifactId>truelicense-core</artifactId>
- <version>1.33</version>
- </dependency>
-
- <dependency>
- <groupId>net.sourceforge.nekohtml</groupId>
- <artifactId>nekohtml</artifactId>
- <version>1.9.18</version>
- </dependency>
- server.port=8081
-
- #启用优雅关机
- server.shutdown=graceful
- #缓冲10秒
- spring.lifecycle.timeout-per-shutdown-phase=10s
-
- #License相关配置
- license.licensePath=D:/workspace/gitee/spring-boot2-license/license/license.lic
- package com.modules.entity;
-
- import com.fasterxml.jackson.annotation.JsonFormat;
- import com.modules.entity.LicenseCheckModel;
-
- import java.io.Serializable;
- import java.util.Date;
-
- /**
- * License生成类需要的参数
- *
- */
- public 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;
-
- public String getSubject() {
- return subject;
- }
-
- public void setSubject(String subject) {
- this.subject = subject;
- }
-
- public String getPrivateAlias() {
- return privateAlias;
- }
-
- public void setPrivateAlias(String privateAlias) {
- this.privateAlias = privateAlias;
- }
-
- public String getKeyPass() {
- return keyPass;
- }
-
- public void setKeyPass(String keyPass) {
- this.keyPass = keyPass;
- }
-
- public String getStorePass() {
- return storePass;
- }
-
- public void setStorePass(String storePass) {
- this.storePass = storePass;
- }
-
- public String getLicensePath() {
- return licensePath;
- }
-
- public void setLicensePath(String licensePath) {
- this.licensePath = licensePath;
- }
-
- public String getPrivateKeysStorePath() {
- return privateKeysStorePath;
- }
-
- public void setPrivateKeysStorePath(String privateKeysStorePath) {
- this.privateKeysStorePath = privateKeysStorePath;
- }
-
- public Date getIssuedTime() {
- return issuedTime;
- }
-
- public void setIssuedTime(Date issuedTime) {
- this.issuedTime = issuedTime;
- }
-
- public Date getExpiryTime() {
- return expiryTime;
- }
-
- public void setExpiryTime(Date expiryTime) {
- this.expiryTime = expiryTime;
- }
-
- public String getConsumerType() {
- return consumerType;
- }
-
- public void setConsumerType(String consumerType) {
- this.consumerType = consumerType;
- }
-
- public Integer getConsumerAmount() {
- return consumerAmount;
- }
-
- public void setConsumerAmount(Integer consumerAmount) {
- this.consumerAmount = consumerAmount;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- public LicenseCheckModel getLicenseCheckModel() {
- return licenseCheckModel;
- }
-
- public void setLicenseCheckModel(LicenseCheckModel licenseCheckModel) {
- this.licenseCheckModel = licenseCheckModel;
- }
-
- @Override
- public String toString() {
- return "LicenseCreatorParam{" +
- "subject='" + subject + '\'' +
- ", privateAlias='" + privateAlias + '\'' +
- ", keyPass='" + keyPass + '\'' +
- ", storePass='" + storePass + '\'' +
- ", licensePath='" + licensePath + '\'' +
- ", privateKeysStorePath='" + privateKeysStorePath + '\'' +
- ", issuedTime=" + issuedTime +
- ", expiryTime=" + expiryTime +
- ", consumerType='" + consumerType + '\'' +
- ", consumerAmount=" + consumerAmount +
- ", description='" + description + '\'' +
- ", licenseCheckModel=" + licenseCheckModel +
- '}';
- }
- }
- package com.modules.entity;
-
- import java.io.Serializable;
- import java.util.List;
-
- /**
- * 自定义需要校验的License参数
- *
- */
- public class LicenseCheckModel implements Serializable{
-
- private static final long serialVersionUID = 8600137500316662317L;
- /**
- * 可被允许的IP地址
- */
- private List<String> ipAddress;
-
- /**
- * 可被允许的MAC地址
- */
- private List<String> macAddress;
-
- /**
- * 可被允许的CPU序列号
- */
- private String cpuSerial;
-
- /**
- * 可被允许的主板序列号
- */
- private String mainBoardSerial;
-
- public List<String> getIpAddress() {
- return ipAddress;
- }
-
- public void setIpAddress(List<String> ipAddress) {
- this.ipAddress = ipAddress;
- }
-
- public List<String> getMacAddress() {
- return macAddress;
- }
-
- public void setMacAddress(List<String> macAddress) {
- this.macAddress = macAddress;
- }
-
- public String getCpuSerial() {
- return cpuSerial;
- }
-
- public void setCpuSerial(String cpuSerial) {
- this.cpuSerial = cpuSerial;
- }
-
- public String getMainBoardSerial() {
- return mainBoardSerial;
- }
-
- public void setMainBoardSerial(String mainBoardSerial) {
- this.mainBoardSerial = mainBoardSerial;
- }
-
- @Override
- public String toString() {
- return "LicenseCheckModel{" +
- "ipAddress=" + ipAddress +
- ", macAddress=" + macAddress +
- ", cpuSerial='" + cpuSerial + '\'' +
- ", mainBoardSerial='" + mainBoardSerial + '\'' +
- '}';
- }
- }
- package com.modules.license;
-
- import com.modules.entity.LicenseCheckModel;
- import lombok.extern.slf4j.Slf4j;
-
- import java.net.InetAddress;
- import java.net.NetworkInterface;
- import java.net.SocketException;
- import java.util.ArrayList;
- import java.util.Enumeration;
- import java.util.List;
-
- /**
- * 用于获取客户服务器的基本信息,如:IP、Mac地址、CPU序列号、主板序列号等
- *
- */
- @Slf4j
- public abstract class AbstractServerInfos {
-
- /**
- * 组装需要额外校验的License参数
- * @since 1.0.0
- * @return demo.LicenseCheckModel
- */
- 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){
- log.error("获取服务器硬件信息失败",e);
- }
-
- return result;
- }
-
- /**
- * 获取IP地址
- * @return java.util.List<java.lang.String>
- */
- protected abstract List<String> getIpAddress() throws Exception;
-
- /**
- * 获取Mac地址
- * @return java.util.List<java.lang.String>
- */
- protected abstract List<String> getMacAddress() throws Exception;
-
- /**
- * 获取CPU序列号
- * @return java.lang.String
- */
- protected abstract String getCPUSerial() throws Exception;
-
- /**
- * 获取主板序列号
- * @return java.lang.String
- */
- protected abstract String getMainBoardSerial() throws Exception;
-
- /**
- * 获取当前服务器所有符合条件的InetAddress
- * @return java.util.List<java.net.InetAddress>
- */
- protected List<InetAddress> getLocalAllInetAddress() throws Exception {
- List<InetAddress> result = new ArrayList<>(4);
-
- // 遍历所有的网络接口
- for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {
- NetworkInterface iface = (NetworkInterface) networkInterfaces.nextElement();
- // 在所有的接口下再遍历IP
- for (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;
- }
-
- /**
- * 获取某个网络接口的Mac地址
- * @param
- * @return void
- */
- 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();
- log.error("获取某个网络接口的Mac地址异常", e.getMessage());
- }
- return null;
- }
- }
获取客户Linux服务器的基本信息
- package com.modules.license;
-
- import org.apache.commons.lang3.StringUtils;
-
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.net.InetAddress;
- import java.util.List;
- import java.util.stream.Collectors;
-
- /**
- * 用于获取客户Linux服务器的基本信息
- *
- */
- public class LinuxServerInfos extends AbstractServerInfos {
-
- @Override
- protected 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;
- }
-
- @Override
- protected 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;
- }
-
- @Override
- protected 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;
- }
-
- @Override
- protected 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;
- }
- }
获取客户Windows服务器的基本信息
- package com.modules.license;
-
- import java.net.InetAddress;
- import java.util.List;
- import java.util.Scanner;
- import java.util.stream.Collectors;
-
- /**
- * 用于获取客户Windows服务器的基本信息
- *
- */
- public class WindowsServerInfos extends AbstractServerInfos {
-
- @Override
- protected 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;
- }
-
- @Override
- protected 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;
- }
-
- @Override
- protected 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;
- }
-
- @Override
- protected 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;
- }
- }
注:这里使用了模板方法模式,将不变部分的算法封装到抽象类,而基本方法的具体实现则由子类来实现。
- package com.modules.license;
-
- import com.modules.entity.LicenseCheckModel;
- import de.schlichtherle.license.*;
- import de.schlichtherle.xml.GenericCertificate;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
-
- import java.beans.XMLDecoder;
- import java.io.BufferedInputStream;
- import java.io.ByteArrayInputStream;
- import java.io.UnsupportedEncodingException;
- import java.util.Date;
- import java.util.List;
-
- /**
- * 自定义LicenseManager,用于增加额外的服务器硬件信息校验
- *
- */
- @Slf4j
- public class CustomLicenseManager extends LicenseManager{
-
- //XML编码
- private static final String XML_CHARSET = "UTF-8";
- //默认BUFSIZE
- private static final int DEFAULT_BUFSIZE = 8 * 1024;
-
- public CustomLicenseManager() {
-
- }
-
- public CustomLicenseManager(LicenseParam param) {
- super(param);
- }
-
- /**
- * 复写create方法
- * @param
- * @return byte[]
- */
- @Override
- protected synchronized byte[] create(
- LicenseContent content,
- LicenseNotary notary)
- throws Exception {
- initialize(content);
- this.validateCreate(content);
- final GenericCertificate certificate = notary.sign(content);
- return getPrivacyGuard().cert2key(certificate);
- }
-
- /**
- * 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息
- * @param
- * @return de.schlichtherle.license.LicenseContent
- */
- @Override
- protected 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;
- }
-
- /**
- * 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息
- * @param
- * @return de.schlichtherle.license.LicenseContent
- */
- @Override
- protected 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;
- }
-
- /**
- * 校验生成证书的参数信息
- * @param content 证书正文
- */
- 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("用户类型不能为空");
- }
- }
-
-
- /**
- * 复写validate方法,增加IP地址、Mac地址等其他信息校验
- * @param content LicenseContent
- */
- @Override
- protected 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("不能获取服务器硬件信息");
- }
- }
-
-
- /**
- * 重写XMLDecoder解析XML
- * @param encoded XML类型字符串
- * @return java.lang.Object
- */
- 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) {
- log.error("XMLDecoder解析XML失败",e.getMessage());
- }
- }
-
- return null;
- }
-
- /**
- * 获取当前服务器需要额外校验的License参数
- * @return demo.LicenseCheckModel
- */
- 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();
- }
-
- /**
- * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内<br/>
- * 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
- * @return boolean
- */
- 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;
- }
- }
-
- /**
- * 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内
- * @return boolean
- */
- 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;
- }
- }
-
- }
- package com.modules.license;
-
- import com.modules.entity.LicenseCreatorParam;
- import de.schlichtherle.license.*;
- import lombok.extern.slf4j.Slf4j;
-
- import javax.security.auth.x500.X500Principal;
- import java.io.File;
- import java.text.MessageFormat;
- import java.util.prefs.Preferences;
-
- /**
- * License生成类
- *
- */
- @Slf4j
- public class LicenseCreator {
-
- 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;
- }
-
- /**
- * 生成License证书
- * @return boolean
- */
- public boolean generateLicense(){
- try {
- LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam());
- LicenseContent licenseContent = initLicenseContent();
-
- licenseManager.store(licenseContent,new File(param.getLicensePath()));
-
- return true;
- }catch (Exception e){
- log.error(MessageFormat.format("证书生成失败:{0}",param),e.getMessage());
- return false;
- }
- }
-
- /**
- * 初始化证书生成参数
- * @return de.schlichtherle.license.LicenseParam
- */
- 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;
- }
-
- /**
- * 设置证书生成正文信息
- * @return de.schlichtherle.license.LicenseContent
- */
- 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;
- }
-
- }
这个Controller对外提供了两个RESTful接口,分别是 [获取服务器硬件信息] 和 [生成证书],这样做不安全,可以自己写测试类。
- package com.modules.controller;
-
- import com.modules.entity.LicenseCheckModel;
- import com.modules.entity.LicenseCreatorParam;
- import com.modules.license.*;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.http.MediaType;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.bind.annotation.RestController;
-
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- *
- * 用于生成证书文件,不能放在给客户部署的代码里
- * @since 1.0.0
- */
- @RestController
- @RequestMapping("/license")
- public class LicenseCreatorController {
-
- /**
- * 证书生成路径
- */
- @Value("${license.licensePath}")
- private String licensePath;
-
- /**
- * 获取服务器硬件信息
- * @param osName 操作系统类型,如果为空则自动判断
- * @return com.ccx.models.license.LicenseCheckModel
- */
- @RequestMapping(value = "/getServerInfos",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
- 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();
- }
-
- /**
- * 生成证书
- * @param param 生成证书需要的参数,如:{"subject":"ccx-models","privateAlias":"privateKey","keyPass":"5T7Zz5Y0dJFcqTxvzkH5LDGJJSGMzQ","storePass":"3538cef8e7","licensePath":"C:/Users/zifangsky/Desktop/license.lic","privateKeysStorePath":"C:/Users/zifangsky/Desktop/privateKeys.keystore","issuedTime":"2018-04-26 14:48:12","expiryTime":"2018-12-31 00:00:00","consumerType":"User","consumerAmount":1,"description":"这是证书描述信息","licenseCheckModel":{"ipAddress":["192.168.245.1","10.0.5.22"],"macAddress":["00-50-56-C0-00-01","50-7B-9D-F9-18-41"],"cpuSerial":"BFEBFBFF000406E3","mainBoardSerial":"L1HF65E00X9"}}
- * @return java.util.Map<java.lang.String,java.lang.Object>
- */
- @RequestMapping(value = "/generateLicense",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
- public Map<String,Object> generateLicense(@RequestBody 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;
- }
- }
假如我们设置公钥库密码为:public_password1234,私钥库密码为:private_password1234,创建目录,在目录地址输入cmd,文件生成命令如下:
- #生成命令
- 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"
-
- #导出命令
- keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -file "certfile.cer"
-
- #导入命令
- keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "public_password1234"
上述命令执行完成之后,会在当前路径下生成三个文件,分别是:privateKeys.keystore、publicCerts.keystore、certfile.cer。其中文件certfile.cer不再需要可以删除,文件privateKeys.keystore用于当前的 cloud-license-serve 项目给客户生成license文件,而文件publicCerts.keystore则随应用代码部署到客户服务器,用户解密license文件并校验其许可信息。
将 cloud-license-serve 项目部署到客户服务器,通过以下接口获取服务器的硬件信息(等license文件生成后需要删除这个项目。当然也可以通过命令手动获取客户服务器的硬件信息,然后在开发者自己的电脑上生成license文件),使用postman 测试。
获取服务器硬件信息
生成简单证书
- {
- "subject": "license_demo",
- "privateAlias": "privateKey",
- "keyPass": "private_password1234",
- "storePass": "public_password1234",
- "licensePath": "D:/workspace/gitee/spring-boot2-license/license/license.lic",
- "privateKeysStorePath": "D:/workspace/gitee/spring-boot2-license/license/privateKeys.keystore",
- "issuedTime": "2021-05-14 00:00:01",
- "expiryTime": "2021-05-14 11:21:00",
- "consumerType": "User",
- "consumerAmount": 1,
- "description": "这是证书描述信息"
- }
生成复杂证书
- {
- "subject": "license_demo",
- "privateAlias": "privateKey",
- "keyPass": "private_password1234",
- "storePass": "public_password1234",
- "licensePath": "D:/workspace/gitee/spring-boot2-license/license/license.lic",
- "privateKeysStorePath": "D:/workspace/gitee/spring-boot2-license/license/privateKeys.keystore",
- "issuedTime": "2021-05-14 00:00:01",
- "expiryTime": "2021-05-14 23:59:59",
- "consumerType": "User",
- "consumerAmount": 1,
- "description": "这是证书描述信息",
- "ipAddress": [
- "172.16.1.126",
- "192.168.56.1"
- ],
- "macAddress": [
- "00-E1-4C-68-07-B5",
- "0A-00-27-00-00-10"
- ],
- "cpuSerial": "178BFBFF00860F01",
- "mainBoardSerial": "BBDW0820AG001635"
- }
创建微服务项目名称: cloud-license-client,版本:2.3.0,模拟给客户部署的应用。
- <!-- License -->
- <dependency>
- <groupId>de.schlichtherle.truelicense</groupId>
- <artifactId>truelicense-core</artifactId>
- <version>1.33</version>
- </dependency>
-
- <dependency>
- <groupId>net.sourceforge.nekohtml</groupId>
- <artifactId>nekohtml</artifactId>
- <version>1.9.18</version>
- </dependency>
- server.port=8082
-
- #启用优雅关机
- server.shutdown=graceful
- #缓冲10秒
- spring.lifecycle.timeout-per-shutdown-phase=10s
-
- #License相关配置
- license.subject=license_demo
- license.publicAlias=publicCert
- license.storePass=public_password1234
- license.licensePath=D:/workspace/gitee/spring-boot2-license/license/license.lic
- license.publicKeysStorePath=D:/workspace/gitee/spring-boot2-license/license/publicCerts.keystore
- package com.modules.license;
-
- import com.modules.entity.LicenseVerifyParam;
- import de.schlichtherle.license.*;
- import lombok.extern.slf4j.Slf4j;
-
- import java.io.File;
- import java.text.DateFormat;
- import java.text.MessageFormat;
- import java.text.SimpleDateFormat;
- import java.util.prefs.Preferences;
-
- /**
- * License校验类
- */
- @Slf4j
- public class LicenseVerify {
- /**
- * 安装License证书
- */
- 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()));
- log.info(MessageFormat.format("证书安装成功,证书有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter())));
- }catch (Exception e){
- log.error("证书安装失败!",e);
- }
-
- return result;
- }
-
- /**
- * 校验License证书
- * @return boolean
- */
- 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());
-
- log.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter())));
- return true;
- }catch (Exception e){
- log.error("证书校验失败!",e);
- return false;
- }
- }
-
- /**
- * 初始化证书生成参数
- * @param param License校验类需要的参数
- * @return de.schlichtherle.license.LicenseParam
- */
- 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);
- }
-
- }
- package com.modules.license;
-
- import com.modules.entity.LicenseVerifyParam;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.ApplicationListener;
- import org.springframework.context.event.ContextRefreshedEvent;
- import org.springframework.stereotype.Component;
-
- /**
- * 在项目启动时安装证书
- */
- @Slf4j
- @Component
- public class LicenseCheckListener implements ApplicationListener<ContextRefreshedEvent> {
-
- /**
- * 证书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;
-
- @Override
- public void onApplicationEvent(ContextRefreshedEvent event) {
- //root application context 没有parent
- ApplicationContext context = event.getApplicationContext().getParent();
- if(context == null){
- if(StringUtils.isNotBlank(licensePath)){
- log.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);
-
- log.info("++++++++ 证书安装结束 ++++++++");
- }
- }
- }
- }
- package com.modules.common.config;
-
- import com.modules.license.LicenseCheckInterceptor;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
- import javax.annotation.Resource;
-
- /**
- * @Description: 拦截器配置
- */
- @Configuration
- public class InterceptorConfig implements WebMvcConfigurer {
-
- @Resource
- private LicenseCheckInterceptor licenseCheckInterceptor;
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
-
- //添加要拦截的url
- registry.addInterceptor(licenseCheckInterceptor)
- // 拦截的路径
- .addPathPatterns("/**");
- // 放行的路径
- // .excludePathPatterns("/admin/login");
- }
- }
- package com.modules.controller;
-
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.*;
-
- import java.text.MessageFormat;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * 登录
- */
- @Slf4j
- @CrossOrigin
- @RestController
- public class LoginController {
-
- /**
- * 模拟登录验证
- * @param loginName 用户名
- * @param password 密码
- */
- @GetMapping(value = "login")
- public Map<String,Object> test(@RequestParam(required = true) String loginName, @RequestParam(required = true) String password){
- Map<String,Object> result = new HashMap<>(1);
- log.info(MessageFormat.format("登录名称:{0},密码:{1}",loginName,password));
- //模拟登录
- log.info("模拟登录被拦截检查证书可用性");
- result.put("code",200);
- return result;
- }
- }
在客户项目启动时,验证许可证可使用性,如果验证报错则验证失败,调用接口会被拦截(只要在拦截范围内),进行许可证不可用提示。
在客户项目启动时,验证许可证可使用性,如果验证成功,调用接口不会被拦截,系统正常操作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。