客户端部署的应用中添加License校验
创建微服务项目名称: cloud-license-client,版本:2.5.5,模拟给客户部署的应用。
1.1pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.0.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--在这里修改版本-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
</plugin>
<!---->
</plugins>
</build>
</project>
1.2 application.properties
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_password12345
license.licensePath=D:/workspace/gitee/spring-boot2-license/license/license.lic
license.publicKeysStorePath=D:/workspace/gitee/spring-boot2-license/license/publicCerts.keystore
json.class.type=jackson
1.3 License校验类
package com.example.license;
import com.example.entity.LicenseVerifyParam;
import com.example.helper.LoggerHelper;
import com.example.helper.ParamInitHelper;
import com.example.model.LicenseCustomManager;
import com.example.model.LicenseResult;
import com.example.utils.DateUtils;
import de.schlichtherle.license.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.prefs.Preferences;
/**
* @program: cloud-license-client
* @description: License校验类
* @author: xiaoyang
* @create: 2021-10-11 10:37
**/
@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(LicenseVerifyParam param){
LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param));
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);
}
}
1.4 添加Listener,用于启动项目时安装License证书
package com.example.license;
import com.example.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;
/**
* @program: cloud-license-client
* @description: 在项目启动时安装证书
* @author: xiaoyang
* @create: 2021-10-11 10:40
**/
@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("++++++++ 证书安装结束 ++++++++");
}
}
}
public LicenseVerifyParam getVerifyParam(){
LicenseVerifyParam param = new LicenseVerifyParam();
param.setSubject(subject);
param.setPublicAlias(publicAlias);
param.setStorePass(storePass);
param.setLicensePath(licensePath);
param.setPublicKeysStorePath(publicKeysStorePath);
return param;
}
}
1.5 添加拦截器,用于在登录的时候校验License证书
package com.example.config;
import com.example.license.LicenseCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.*;
/**
* @program: cloud-license-client
* @description: 拦截器配置
* @author: xiaoyang
* @create: 2021-10-11 10:42
**/
import org.springframework.context.annotation.Configuration;
/**
* @Description: 拦截器配置
*/
@Configuration
@ComponentScan(basePackages = {"com.example"})
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Autowired
public LicenseCheckInterceptor licenseCheckInterceptor;
public InterceptorConfig() {
System.out.println("============InterceptorConfig 已装载===========");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("拦截中");
//添加要拦截的url
registry.addInterceptor(licenseCheckInterceptor)
// 拦截的路径
.addPathPatterns("/**");
System.out.println("已拦截");
// 放行的路径
// .excludePathPatterns("/admin/login");
}
}
1.6 创建LicenseManagerHolder类
package com.example.license;
import de.schlichtherle.license.LicenseManager;
import de.schlichtherle.license.LicenseParam;
/**
* @program: cloud-license-client
* @description:
* @author: xiaoyang
* @create: 2021-10-11 11:01
**/
public class LicenseManagerHolder {
private static LicenseManager licenseManager;
public static synchronized LicenseManager getLicenseManager(LicenseParam licenseParam) {
if(null == licenseManager) {
try {
licenseManager = new LicenseManager(licenseParam);
} catch (Exception e) {
e.printStackTrace();
}
}
return licenseManager;
}
public static LicenseManager getInstance(LicenseParam initLicenseParam) {
return new LicenseManager(initLicenseParam);
}
}
1.7 创建LicenseVerifyParam类
package com.example.entity;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @program: cloud-license-client
* @description: License校验参数
* @author: xiaoyang
* @create: 2021-10-11 10:49
**/
@Component
public class LicenseVerifyParam {
// /**证书主题*/
// private String subject;
//
// /**公钥别名*/
// private String publicAlias;
//
// /** 访问公钥库的密码*/
// private String storePass;
//
// /**证书生成路径*/
// private String licensePath;
//
// /**公钥库存储路径*/
// private String publicKeysStorePath;
/**
* 证书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;
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;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getPublicAlias() {
return publicAlias;
}
public void setPublicAlias(String publicAlias) {
this.publicAlias = publicAlias;
}
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 getPublicKeysStorePath() {
return publicKeysStorePath;
}
public void setPublicKeysStorePath(String publicKeysStorePath) {
this.publicKeysStorePath = publicKeysStorePath;
}
@Override
public String toString() {
return "LicenseVerifyParam{" +
"subject='" + subject + '\'' +
", publicAlias='" + publicAlias + '\'' +
", storePass='" + storePass + '\'' +
", licensePath='" + licensePath + '\'' +
", publicKeysStorePath='" + publicKeysStorePath + '\'' +
'}';
}
public LicenseVerifyParam getVerifyParam() {
LicenseVerifyParam param = new LicenseVerifyParam();
param.setSubject(subject);
param.setPublicAlias(publicAlias);
param.setStorePass(storePass);
param.setLicensePath(licensePath);
param.setPublicKeysStorePath(publicKeysStorePath);
return param;
}
}
1.8 创建CustomKeyStoreParam类
package com.example.license;
/**
* @program: cloud-license-serve
* @description: 自定义KeyStoreParam类
* @author: xiaoyang
* @create: 2021-10-11 09:21
**/
import de.schlichtherle.license.AbstractKeyStoreParam;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中。现场使用的时候公钥大部分都不会放在项目中的
*/
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;
}
/**
* AbstractKeyStoreParam里面的getStream()方法默认文件是存储的项目中。
* 用于将公私钥存储文件存放到其他磁盘位置而不是项目中
*/
@Override
public InputStream getStream() throws IOException {
return new FileInputStream(new File(storePath));
}
}
创建FastLicense 自定义注解
package com.example.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FastLicense {
String[] verifies() default{};
}
1.9 创建Controller模拟登录操作
package com.example.controller;
import com.example.annotation.FastLicense;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
/**
* @program: cloud-license-client
* @description: 模拟登录
* @author: xiaoyang
* @create: 2021-10-11 11:10
**/
@Slf4j
@CrossOrigin
@RestController
public class LoginController {
/**
* 模拟登录验证
* @param loginName 用户名
* @param password 密码
*/
@GetMapping(value = "/login")
@FastLicense
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;
}
}
模拟安装证书失败
安装证书成功