首页 > 其他分享 >Spring Boot2 使用 License 实现系统软件版权许可认保姆级教程

Spring Boot2 使用 License 实现系统软件版权许可认保姆级教程

时间:2025-02-07 09:53:15浏览次数:4  
标签:return String License Spring new Boot2 param org import

# Spring Boot2 使用 License 实现系统软件版权许可认保姆级教程

https://spring.io
https://start.aliyun.com

项目参考:https://gitee.com/zishuimuyu/license

### 1.项目前期准备

#### 1.1 创建创建

父项目、子项目均引入web、lombok、devtools坐标依赖进行创建,创建成功后通过配置pom进行关联

##### 1.1.1 创建父项目
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/02e1f7dc4fab4a049e5ca2784d77cb2e.png)
##### 1.1.2 创建子项目服务端
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/487789c4a5e54e3fa74704d008c88f07.png)
##### 1.1.3 创建子项目客户端
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/5a77f443e9ad4626b75e44b68d9b83f9.png)
#### 1.2 父子项目关联配置

##### 1.2.1 删除无关文件

父项目中:src

子项目中: .gitignore、HELP.md、static、demos

##### 1.2.2 修改配置文件

将application.properties修改成application.yml,并配置开发、生产环境

```
application.yml
application-dev.yml
application-prod.yml
```

##### 1.2.3 修改配置文件

在父项目pom中声明子项目中artifactId及父项目打包方式

```
<packaging>pom</packaging>
<modules>
<module>x-license-client</module>
<module>x-license-server</module>
</modules>
```

删除子项目pom中properties、dependencies、build等标签内,再在子项目pom中引用父项目中artifactId名称

```
<parent>
<groupId>org.cn</groupId>
<artifactId>x-license</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
```

##### 1.2.4 启动项目测试

修改子项目application配置,启动项目进行测试是否正常

#### 1.3 整合Swagger3

##### 1.3.1 导入swagger3坐标

```
<swagger3.version>3.0.0</swagger3.version>
```

```
<!-- swagger3 start -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger3.version}</version>
</dependency>
<!-- swagger3 end -->
```

##### 1.3.2 编写swagger3配置类

```
package org.cn.common.config;

import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Configuration
@EnableOpenApi
@EnableWebMvc
public class Swagger3Config {

/**
* 创建API
* http:localhost:8000/swagger-ui/index.html 原生地址
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30).pathMapping("/")
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
/*.enable(enable)*/
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api,用这种方式更灵活
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 扫描指定包中的swagger注解
//.apis(RequestHandlerSelectors.basePackage("com.cn"))
// 扫描所有 .apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("(?!/ApiError.*).*"))
.paths(PathSelectors.any())
.build()
// 支持的通讯协议集合
.protocols(newHashSet("https", "http"))
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());

}

/**
* 支持的通讯协议集合
*
* @param type1
* @param type2
* @return
*/
private Set<String> newHashSet(String type1, String type2) {
Set<String> set = new HashSet<>();
set.add(type1);
set.add(type2);
return set;
}

/**
* 认证的安全上下文
*/
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> securitySchemes = new ArrayList<>();
securitySchemes.add(new ApiKey("Authorization", "Authorization", "header"));
return securitySchemes;
}

/**
* 授权信息全局应用
*/
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.any()).build());
return securityContexts;
}

private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}


/**
* 添加摘要信息
* @return 返回ApiInfo对象
*/
private ApiInfo apiInfo() {
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("接口文档")
// 服务条款
.termsOfServiceUrl("NO terms of service")
// 描述
.description("权限模型管理系统-接口文档")
// 作者信息
.contact(new Contact("LM", "https://www.cnblogs.com/longronglang/", "lumin@gmail.com"))
// 版本
.version("版本号:V1.0")
//协议
.license("The Apache License")
// 协议url
.licenseUrl("https://www.apache.org/licenses/LICENSE-2.0.html")
.build();
}
}
```

##### 1.3.3 swagger3注解测试

新增测试类、测试方法等,加上Swagger3注解进行测试

http:localhost:9999/swagger-ui/index.html

### 2 生成密钥对

配置Keytool.exe环境变量或直接到该路径下执行命令生成密钥对

#### 2.1 生成私匙库

```
## 1. 生成私匙库
# keysize 密钥长度
# keyalg 加密方式
# validity 私钥的有效期(单位:天)
# alias 私钥别称
# keystore 指定私钥库文件的名称 (生成在当前目录)
# storepass 指定私钥库的密码 (keystore 文件存储密码)
# keypass 指定别名条目的密码 (私钥加解密密码)
# dname 证书个人信息
# CN 为你的姓名
# OU 为你的组织单位名称
# O 为你的组织名称
# L 为你所在的城市名称
# ST 为你所在的省份名称
# C 为你的国家名称 或 区号
```

```
keytool -genkey -keysize 1024 -keyalg DSA -validity 3650 -alias "privateKey" -keystore "privateKeys.keystore" -storepass "xxxxxx" -keypass "xxxxxxx" -dname "CN=cnhqd, OU=cnhqd, O=cnhqd, L=XA, ST=SX, C=CN"
```

```
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.2 导出私匙库中公钥

```
## 2. 把私匙库内的公匙导出到一个文件当中
# alias:私钥别称
# keystore:指定私钥库的名称(在当前目录查找)
# storepass: 指定私钥库的密码
# file:证书名称
```

```
keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "xxxxxx" -file "certfile.cer"
```

```
keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -file "certfile.cer"
```

#### 2.3 证书文件导入公钥库

```
## 3. 再把这个证书文件导入到公匙库
# alias:公钥别称
# file:证书名称
# keystore:公钥文件名称
# storepass:指定私钥库的密码
```

```
keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "xxxxxx"
```

```
keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "public_password1234"
```
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f62a1552a83e4dd1897273b4e6bb2e84.png)

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/12fca219b3e2454499f40f60f01be2eb.png)

上述命令执行完成之后,会在当前路径下生成三个文件:

- certfile.cer 认证证书文件,暂时无用
- privateKeys.keystore 私钥文件,自己保存
- publicKeys.keystore 公钥文件,需要放到客户端项目目录里

### 3 代码实现

#### 3.1 父项目中引入依赖

```
<httpclient.version>4.5.13</httpclient.version>
<fastjson.version>1.2.44</fastjson.version>
<commons-io.version>2.11.0</commons-io.version>
<commons-beanutils.version>1.9.4</commons-beanutils.version>
<commons-fileupload.version>1.5</commons-fileupload.version>
<commons-codec.version>1.15</commons-codec.version>
<commons-collections.version>3.2.2</commons-collections.version>
<commons-lang3.version>3.4</commons-lang3.version>
<truelicense-core.version>1.33</truelicense-core.version>
<nekohtml.version>1.9.22</nekohtml.version>
<junit.version>4.13.2</junit.version>
```

```
<!--Commons-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils.version}</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>

<!-- Jackson对自动解析JSON和XML格式的支持 -->
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

<!-- SLF4J和LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>

<!-- License -->
<dependency>
<groupId>de.schlichtherle.truelicense</groupId>
<artifactId>truelicense-core</artifactId>
<version>${truelicense-core.version}</version>
</dependency>

<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
```

#### 3.2 License服务端
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/13ca91fa89d245a484aedd1011961916.png)
##### 3.2.1 生成证书校验实体类

###### 3.2.1.1 LicenseCreatorParam

**LicenseCreatorParam是License生成证书校验实体基类**

```
package org.cn.server.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
* @Author: 陆敏
* @Description: License生成类需要的参数
* @Date: 2025/1/21 9:13
* @ClassName:LicenseCreatorParam
* @Version:1.0
*/
@Data
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;
}
```

###### 3.2.1.2 LicenseCheckModel

**LicenseCheckModel是License生成证书扩展参数实体类**

```
package org.cn.server.model;

import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
* @Author: 陆敏
* @Description: 自定义需要校验的License参数
* @Date: 2025/1/21 9:10
* @ClassName:LicenseCheckModel
* @Version:1.0
*/
@Data
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;
}
```

##### 3.2.2 获取服务器硬件信息类

###### 3.2.2.1 AbstractServerInfos

**AbstractServerInfos用于获取客户服务器硬件信息抽象类,如:IP、Mac地址、CPU序列号、主板序列号等**

```
package org.cn.server.info;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import model.org.cn.LicenseCheckModel;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

/**
* @Author: 陆敏
* @Description: 用于获取客户服务器的基本信息,如:IP、Mac地址、CPU序列号、主板序列号等
* @Date: 2025/1/21 9:42
* @ClassName:AbstractServerInfos
* @Version:1.0
*/
public abstract class AbstractServerInfos {
private static Logger logger = LogManager.getLogger(AbstractServerInfos.class);

/**
* 组装需要额外校验的License参数
* @return
*/
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;
}

/**
* 获取IP地址
* @return
* @throws Exception
*/
protected abstract List<String> getIpAddress() throws Exception;

/**
* 获取Mac地址
* @return
* @throws Exception
*/
protected abstract List<String> getMacAddress() throws Exception;

/**
* 获取CPU序列号
* @return
* @throws Exception
*/
protected abstract String getCPUSerial() throws Exception;

/**
* 获取CPU序列号
* @return
* @throws Exception
*/
protected abstract String getMainBoardSerial() throws Exception;

/**
* 获取当前服务器所有符合条件的InetAddress
* @return
* @throws Exception
*/
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 inetAddr
* @return
*/
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;
}

}
```

###### 3.2.2.2 LinuxServerInfos

**用于获取客户Linux服务器的基本信息**

```
package org.cn.server.info;

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;

/**
* @Author: 陆敏
* @Description: 用于获取客户Linux服务器的基本信息
* @Date: 2025/1/21 9:49
* @ClassName:LinuxServerInfos
* @Version:1.0
*/
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;
}
}
```

###### 3.2.2.3 WindowsServerInfos

**用于获取客户Windows服务器的基本信息**

```
package org.cn.server.info;

import java.net.InetAddress;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;

/**
* @Author: 陆敏
* @Description: 用于获取客户Windows服务器的基本信息
* @Date: 2025/1/21 9:49
* @ClassName:WindowsServerInfos
* @Version:1.0
*/
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;
}
}
```

##### 3.2.3 自定义获取密钥和校验类

###### 3.2.3.1 CustomKeyStoreParam

**自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中**

```
package org.cn.server.license;

import de.schlichtherle.license.AbstractKeyStoreParam;

import java.io.*;

/**
* @Author: 陆敏
* @Description: 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中
* @Date: 2025/1/21 9:15
* @ClassName:CustomKeyStoreParam
* @Version:1.0
*/
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;
}

@Override
public String getAlias() {
return alias;
}

@Override
public String getStorePwd() {
return storePwd;
}

@Override
public String getKeyPwd() {
return keyPwd;
}

/**
* 复写de.schlichtherle.license.AbstractKeyStoreParam的getStream()方法
* 用于将公私钥存储文件存放到其他磁盘位置而不是项目中
* @return
* @throws IOException
*/
@Override
public InputStream getStream() throws IOException {
final InputStream in = new FileInputStream(new File(storePath));
if (null == in){
throw new FileNotFoundException(storePath);
}

return in;
}
}
```

###### 3.2.3.2 CustomLicenseManager

**自定义LicenseManager,用于增加额外的服务器硬件信息校验**

```
package org.cn.server.custom;

import de.schlichtherle.license.*;
import de.schlichtherle.xml.GenericCertificate;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import info.org.cn.AbstractServerInfos;
import info.org.cn.LinuxServerInfos;
import info.org.cn.WindowsServerInfos;
import model.org.cn.LicenseCheckModel;

import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;

/**
* @Author: 陆敏
* @Description: 自定义LicenseManager,用于增加额外的服务器硬件信息校验
* @Date: 2025/1/21 10:01
* @ClassName:CustomLicenseManager
* @Version:1.0
*/
public class CustomLicenseManager extends LicenseManager {
private static Logger logger = LogManager.getLogger(CustomLicenseManager.class);

//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 content the license content
* - may <em>not</em> be {@code null}.
* @param notary the license notary used to sign the license key
* - may <em>not</em> be {@code null}.
* @return
* @throws Exception
*/
@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 key the license key
* - may <em>not</em> be {@code null}.
* @param notary the license notary used to verify the license key
* - may <em>not</em> be {@code null}.
* @return
* @throws Exception
*/
@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 notary the license notary used to verify the current license key
* - may <em>not</em> be {@code null}.
* @return
* @throws Exception
*/
@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
* @throws LicenseContentException
*/
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 the license content
* - may <em>not</em> be {@code null}.
* @throws LicenseContentException
*/
@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
* @return
*/
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;
}

/**
* 获取当前服务器需要额外校验的License参数
* @return
*/
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范围内
* 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
* @param expectedList
* @param serverList
* @return
*/
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等)序列号是否在可允许范围内
* @param expectedSerial
* @param serverSerial
* @return
*/
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;
}
}

}
```

##### 3.2.4 生成证书文件类

###### 3.2.4.1 LicenseCreator

License生成类

```
package org.cn.server.license;

import de.schlichtherle.license.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import custom.org.cn.CustomKeyStoreParam;
import custom.org.cn.CustomLicenseManager;
import model.org.cn.LicenseCreatorParam;

import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.text.MessageFormat;
import java.util.prefs.Preferences;

/**
* @Author: 陆敏
* @Description: License生成类
* @Date: 2025/1/21 10:14
* @ClassName:LicenseCreator
* @Version:1.0
*/
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;
}

/**
* 生成License证书
* @return
*/
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;
}
}

/**
* 初始化证书生成参数
* @return
*/
private LicenseParam initLicenseParam(){
Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);

//设置对证书内容加密的秘钥
CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
//自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中
KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class
,param.getPrivateKeysStorePath()
,param.getPrivateAlias()
,param.getStorePass()
,param.getKeyPass());

//组织License参数
LicenseParam licenseParam = new DefaultLicenseParam(param.getSubject()
,preferences
,privateStoreParam
,cipherParam);

return licenseParam;
}

/**
* 设置证书生成正文信息
* @return
*/
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;
}

}
```

##### 3.2.5 生成证书文件控制器

###### 3.2.5.1 LicenseCreatorController

```
package org.cn.server.controller;

import org.apache.commons.lang3.StringUtils;
import info.org.cn.AbstractServerInfos;
import info.org.cn.LinuxServerInfos;
import info.org.cn.WindowsServerInfos;
import license.org.cn.LicenseCreator;
import model.org.cn.LicenseCheckModel;
import model.org.cn.LicenseCreatorParam;
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;

/**
* @Author: 陆敏
* @Description: 生成证书文件,不能放在给客户部署的代码里
* @Date: 2025/1/21 10:21
* @ClassName:LicenseCreatorController
* @Version:1.0
*/
@RestController
@RequestMapping("/license")
public class LicenseCreatorController {

/**
* 证书生成路径
*/
@Value("${license.licensePath}")
private String licensePath;

/**
* 获取服务器硬件信息
* @param osName 操作系统类型,如果为空则自动判断
* @return
*/
@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();
}

/**
{
"subject": "license_demo",
"privateAlias": "privateKey",
"keyPass": "private_password1234",
"storePass": "public_password1234",
"licensePath": "F:/install/workspace/sty/x-license/x-license-server/license/license.lic",
"privateKeysStorePath": "F:/install/workspace/sty/x-license/x-license-server/license/privateKeys.keystore",
"issuedTime": "2022-04-10 00:00:01",
"expiryTime": "2025-01-17 10:59:59",
"consumerType": "User",
"consumerAmount": 1,
"description": "这是证书描述信息",
"licenseCheckModel": {
"ipAddress": [
"192.168.2.30",
"192.168.106.1"
],
"macAddress": [
"2C-FD-A1-5D-79-D2",
"00-50-56-C0-00-08"
],
"cpuSerial": "BFEBFBFF000906E9",
"mainBoardSerial": "171216525318649"
}
}
*/

@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;
}

}
```

###### 3.2.5.2 启动服务端项目测试

启动服务端进行获取客户端服务器硬件信息和生成证书文件测试,启动时必须在启动类上必须加上下面注解

```
@ServletComponentScan
@PropertySource({"license-config.properties"}) //加载额外的配置
```

###### 3.2.5.3 在单元测试中生成证书

```
@Test
public void licenseCreate() {
// 生成license需要的一些参数
LicenseCreatorParam param = new LicenseCreatorParam();
param.setSubject("license_demo");
param.setPrivateAlias("privateKey");
param.setKeyPass("private_password1234");
param.setStorePass("public_password1234");
param.setLicensePath("F:/install/workspace/sty/x-license/license/license.lic");
param.setPrivateKeysStorePath("F:/install/workspace/sty/x-license/license/privateKeys.keystore");
Calendar issueCalendar = Calendar.getInstance();
param.setIssuedTime(issueCalendar.getTime());
Calendar expiryCalendar = Calendar.getInstance();
expiryCalendar.set(2025, Calendar.SUNDAY, 17, 11, 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);
// 生成license
licenseCreator.generateLicense();
System.out.println("license生成成功" + licenseCheckModel);
}
```
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3c72a31f4bc34ca8ae38fea74a679b35.png)


###### 3.2.5.4 通过测试工具生成证书

首先获取客户端服务器硬件信息,然后组装证书生成参数,发送生成证书请求生成证书文件:license.lic

```
/**
{
"subject": "license_demo",
"privateAlias": "privateKey",
"keyPass": "private_password1234",
"storePass": "public_password1234",
"licensePath": "F:/install/workspace/sty/x-license/x-license-server/license/license.lic",
"privateKeysStorePath": "F:/install/workspace/sty/x-license/x-license-server/license/privateKeys.keystore",
"issuedTime": "2022-04-10 00:00:01",
"expiryTime": "2025-01-17 10:59:59",
"consumerType": "User",
"consumerAmount": 1,
"description": "这是证书描述信息",
"licenseCheckModel": {
"ipAddress": [
"192.168.2.30",
"192.168.106.1"
],
"macAddress": [
"2C-FD-A1-5D-79-D2",
"00-50-56-C0-00-08"
],
"cpuSerial": "BFEBFBFF000906E9",
"mainBoardSerial": "171216525318649"
}
}
*/
```

获取客户端服务器硬件信息
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b4dd5e8783bd40bbb19fba95317e588c.png)

生成证书文件
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b33a6dbe84b448ccbdcbd05c043daca9.png)


#### 3.3 License客户端
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/414795e95ecf452ca9109f0d4bc84d3e.png)
##### 3.3.1 生成证书校验实体类
###### 3.3.1.1 LicenseCreatorParam

**LicenseCreatorParam是License生成证书校验实体基类**

```
package org.cn.server.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
* @Author: 陆敏
* @Description: License生成类需要的参数
* @Date: 2025/1/21 9:13
* @ClassName:LicenseCreatorParam
* @Version:1.0
*/
@Data
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;
}
```

###### 3.3.1.2 LicenseCheckModel

**LicenseCheckModel是License生成证书扩展参数实体类**

```
package org.cn.server.model;

import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
* @Author: 陆敏
* @Description: 自定义需要校验的License参数
* @Date: 2025/1/21 9:10
* @ClassName:LicenseCheckModel
* @Version:1.0
*/
@Data
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;
}
```

###### 3.3.1.3 LicenseManagerHolder

**LicenseManagerHolder是一个单例,主要用来创建对象。**

**实际上就是获取CustomLicenseManager中,单例模式实质上就是创建一个对象**

```
package org.cn.license;

import de.schlichtherle.license.LicenseManager;
import de.schlichtherle.license.LicenseParam;
import org.cn.custom.CustomLicenseManager;

/**
* @Author: 陆敏
* @Description:
* @Date: 2025/1/21 11:10
* @ClassName:LicenseManagerHolder
* @Version:1.0
*/
public class LicenseManagerHolder {

private static volatile LicenseManager LICENSE_MANAGER;

public static LicenseManager getInstance(LicenseParam param){
if(LICENSE_MANAGER == null){
synchronized (LicenseManagerHolder.class){
if(LICENSE_MANAGER == null){
LICENSE_MANAGER = new CustomLicenseManager(param);
}
}
}
return LICENSE_MANAGER;
}
}
```

###### 3.3.1.4 LicenseVerifyParam

**License证书校验需要的参数类**

```
package org.cn.license;

import lombok.Data;

/**
* @Author: 陆敏
* @Description: License校验类需要的参数
* @Date: 2025/1/21 10:59
* @ClassName:LicenseVerifyParam
* @Version:1.0
*/
@Data
public 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;
}
}
```

###### 3.3.1.5 LicenseVerify

**LicenseVerify主要用于初始化证书、安装证书、校验证书等**

```
package org.cn.license;

import de.schlichtherle.license.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.cn.custom.CustomKeyStoreParam;

import java.io.File;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.prefs.Preferences;

/**
* @Author: 陆敏
* @Description:
* @Date: 2025/1/21 11:07
* @ClassName:LicenseVerify
* @Version:1.0
*/
public class LicenseVerify {
private static Logger logger = LogManager.getLogger(LicenseVerify.class);

/**
* 安装License证书
* @param param
* @return
*/
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;
}

/**
* 校验License证书
* @return
*/
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;
}
}

/**
* 初始化证书生成参数
* @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);
}
}
```

###### **3.3.1.6 注意**校验和扩展实体

**注意:**客户端校验和扩展实体(LicenseCheckModel和LicenseVerify)包位置要放到与服务端包名一样的位置。

解决参考:https://blog.csdn.net/u010361276/article/details/134448242

3.3.1.6.1 生成证书校验实体类错误截图
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/66430690b6d64afeb3c05e7aaaae4d64.png)
3.3.1.6.2 生成证书校验实体类错误解决
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/57cb18ae85b44db1b1b4e8630e139917.png)
3.3.1.6.3 生成证书校验实体类错误解决后的项目结构截图
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/2f0e61d381604ff4818f71e137cf7791.png)
3.3.1.6.4 生成证书校验实体类错误解决后截图
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c43e2608bdde4e23a88a0aa7f807b24e.png)

##### 3.3.2 获取服务器硬件信息类

###### 3.3.2.1 AbstractServerInfos

**AbstractServerInfos用于获取客户服务器硬件信息抽象类,如:IP、Mac地址、CPU序列号、主板序列号等**

```
package org.cn.server.info;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import model.org.cn.LicenseCheckModel;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

/**
* @Author: 陆敏
* @Description: 用于获取客户服务器的基本信息,如:IP、Mac地址、CPU序列号、主板序列号等
* @Date: 2025/1/21 9:42
* @ClassName:AbstractServerInfos
* @Version:1.0
*/
public abstract class AbstractServerInfos {
private static Logger logger = LogManager.getLogger(AbstractServerInfos.class);

/**
* 组装需要额外校验的License参数
* @return
*/
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;
}

/**
* 获取IP地址
* @return
* @throws Exception
*/
protected abstract List<String> getIpAddress() throws Exception;

/**
* 获取Mac地址
* @return
* @throws Exception
*/
protected abstract List<String> getMacAddress() throws Exception;

/**
* 获取CPU序列号
* @return
* @throws Exception
*/
protected abstract String getCPUSerial() throws Exception;

/**
* 获取CPU序列号
* @return
* @throws Exception
*/
protected abstract String getMainBoardSerial() throws Exception;

/**
* 获取当前服务器所有符合条件的InetAddress
* @return
* @throws Exception
*/
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 inetAddr
* @return
*/
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;
}

}
```

###### 3.3.2.2 LinuxServerInfos

**用于获取客户Linux服务器的基本信息**

```
package org.cn.server.info;

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;

/**
* @Author: 陆敏
* @Description: 用于获取客户Linux服务器的基本信息
* @Date: 2025/1/21 9:49
* @ClassName:LinuxServerInfos
* @Version:1.0
*/
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;
}
}
```

###### 3.3.2.3 WindowsServerInfos

**用于获取客户Windows服务器的基本信息**

```
package org.cn.server.info;

import java.net.InetAddress;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;

/**
* @Author: 陆敏
* @Description: 用于获取客户Windows服务器的基本信息
* @Date: 2025/1/21 9:49
* @ClassName:WindowsServerInfos
* @Version:1.0
*/
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;
}
}
```

##### 3.3.3 自定义获取数据类

###### 3.3.3.1 CustomKeyStoreParam

**自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中**

```
package org.cn.server.license;

import de.schlichtherle.license.AbstractKeyStoreParam;

import java.io.*;

/**
* @Author: 陆敏
* @Description: 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中
* @Date: 2025/1/21 9:15
* @ClassName:CustomKeyStoreParam
* @Version:1.0
*/
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;
}

@Override
public String getAlias() {
return alias;
}

@Override
public String getStorePwd() {
return storePwd;
}

@Override
public String getKeyPwd() {
return keyPwd;
}

/**
* 复写de.schlichtherle.license.AbstractKeyStoreParam的getStream()方法
* 用于将公私钥存储文件存放到其他磁盘位置而不是项目中
* @return
* @throws IOException
*/
@Override
public InputStream getStream() throws IOException {
final InputStream in = new FileInputStream(new File(storePath));
if (null == in){
throw new FileNotFoundException(storePath);
}

return in;
}
}
```

###### 3.3.3.2 CustomLicenseManager

**自定义LicenseManager,用于增加额外的服务器硬件信息校验**

```
package org.cn.server.custom;

import de.schlichtherle.license.*;
import de.schlichtherle.xml.GenericCertificate;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import info.org.cn.AbstractServerInfos;
import info.org.cn.LinuxServerInfos;
import info.org.cn.WindowsServerInfos;
import model.org.cn.LicenseCheckModel;

import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;

/**
* @Author: 陆敏
* @Description: 自定义LicenseManager,用于增加额外的服务器硬件信息校验
* @Date: 2025/1/21 10:01
* @ClassName:CustomLicenseManager
* @Version:1.0
*/
public class CustomLicenseManager extends LicenseManager {
private static Logger logger = LogManager.getLogger(CustomLicenseManager.class);

//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 content the license content
* - may <em>not</em> be {@code null}.
* @param notary the license notary used to sign the license key
* - may <em>not</em> be {@code null}.
* @return
* @throws Exception
*/
@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 key the license key
* - may <em>not</em> be {@code null}.
* @param notary the license notary used to verify the license key
* - may <em>not</em> be {@code null}.
* @return
* @throws Exception
*/
@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 notary the license notary used to verify the current license key
* - may <em>not</em> be {@code null}.
* @return
* @throws Exception
*/
@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
* @throws LicenseContentException
*/
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 the license content
* - may <em>not</em> be {@code null}.
* @throws LicenseContentException
*/
@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
* @return
*/
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;
}

/**
* 获取当前服务器需要额外校验的License参数
* @return
*/
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范围内
* 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
* @param expectedList
* @param serverList
* @return
*/
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等)序列号是否在可允许范围内
* @param expectedSerial
* @param serverSerial
* @return
*/
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;
}
}

}
```

##### 3.3.4 客户端证书校验监听类

###### 3.3.4.1 LicenseCheckListener

```
package org.cn.listener;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.cn.license.LicenseVerifyParam;
import org.cn.license.LicenseVerify;
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;

/**
* @Author: 陆敏
* @Description: 在项目启动时安装证书
* @Date: 2025/1/21 11:15
* @ClassName:LicenseCheckListener
* @Version:1.0
*/
@Component
public 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;

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//root application context 没有parent
ApplicationContext 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("++++++++ 证书安装结束 ++++++++");
}
}
}
}
```

##### 3.3.5 License证书安装监听类

###### 3.3.5.1 LicenseCheckInterceptor

用于请求验证证书

```
package org.cn.client.interceptor;

import com.alibaba.fastjson.JSON;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import verify.org.cn.LicenseVerify;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
* @Author: 陆敏
* @Description: 用于请求验证证书
* @Date: 2025/1/21 11:19
* @ClassName:LicenseCheckInterceptor
* @Version:1.0
*/
public class LicenseCheckInterceptor implements HandlerInterceptor {
private static Logger logger = LogManager.getLogger(LicenseCheckInterceptor.class);

@Override
public 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;
}
}
}
```

##### 3.3.6 拦截器类

###### 3.3.5.2 WebMvcConfig

注册拦截器,拦截前端请求

```
package org.cn.client.config;

import interceptor.org.cn.LicenseCheckInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @Author: 陆敏
* @Description: 拦截器
* @Date: 2025/1/21 11:34
* @ClassName:WebMvcConfig
* @Version:1.0
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

/**
* 添加拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LicenseCheckInterceptor()).addPathPatterns("/check");
}
}
```

##### 3.3.6 模拟登录控制器

###### 3.3.6.1 LoginController

```
package org.cn.client.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

/**
* @Author: 陆敏
* @Description:
* @Date: 2025/1/21 11:32
* @ClassName:LoginController
* @Version:1.0
*/
@Controller
public class LoginController {
@PostMapping("/check")
@ResponseBody
public Map<String,Object> check(@RequestParam(required = true) String username, @RequestParam(required = true) String password){
Map<String,Object> result = new HashMap<>(1);
System.out.println(MessageFormat.format("用户名:{0},密码:{1}",username,password));

//模拟登录
System.out.println("模拟登录流程");
result.put("code",200);

return result;
}
}
```

###### 3.3.7.2 模拟登录测试

启动类上必须加上以下注解,否加加载额外配置文件

```
@ServletComponentScan
@PropertySource({"license-config.properties"}) //加载额外的配置
```

```
package org.cn.client.interceptor;

import com.alibaba.fastjson.JSON;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import verify.org.cn.LicenseVerify;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
* @Author: 陆敏
* @Description: 用于请求验证证书
* @Date: 2025/1/21 11:19
* @ClassName:LicenseCheckInterceptor
* @Version:1.0
*/
public class LicenseCheckInterceptor implements HandlerInterceptor {
private static Logger logger = LogManager.getLogger(LicenseCheckInterceptor.class);

@Override
public 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;
}
}
}
```

##### 3.3.6 拦截器类

###### 3.3.5.2 WebMvcConfig

注册拦截器,拦截前端请求

```
package org.cn.client.config;

import interceptor.org.cn.LicenseCheckInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @Author: 陆敏
* @Description: 拦截器
* @Date: 2025/1/21 11:34
* @ClassName:WebMvcConfig
* @Version:1.0
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

/**
* 添加拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LicenseCheckInterceptor()).addPathPatterns("/check");
}
}
```

##### 3.3.6 模拟登录控制器

###### 3.3.6.1 LoginController

```
package org.cn.client.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

/**
* @Author: 陆敏
* @Description:
* @Date: 2025/1/21 11:32
* @ClassName:LoginController
* @Version:1.0
*/
@Controller
public class LoginController {
@PostMapping("/check")
@ResponseBody
public Map<String,Object> check(@RequestParam(required = true) String username, @RequestParam(required = true) String password){
Map<String,Object> result = new HashMap<>(1);
System.out.println(MessageFormat.format("用户名:{0},密码:{1}",username,password));

//模拟登录
System.out.println("模拟登录流程");
result.put("code",200);

return result;
}
}
```

###### 3.3.7.2 模拟登录测试

启动类上必须加上以下注解,否加加载额外配置文件

```
@ServletComponentScan
@PropertySource({"license-config.properties"}) //加载额外的配置
```

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/039363b20be2414bbc8bb80b2c837b91.png)

 

标签:return,String,License,Spring,new,Boot2,param,org,import
From: https://www.cnblogs.com/mrlumin/p/18689934

相关文章