本文中主要涉及到的为现有的框架中认证流程的讲解
关于证书的基本配置在上一篇 License认证本地生成文件 中有提到
本文关于证书CustomKeyStoreParam类不再描述,涉及到认证的主要有以下几个类
首先是证书安装配置的配置文件LicenseConfig,通过 @Configuration 和 @Bean 注解来注入到spring
通过 @Value 获取到配置文件中设置的授权证书的值,来实现证书的安装,具体的安装和卸载方法为 @Bean(initMethod = “installLicense”, destroyMethod = “unInstallLicense”) 通过Bean的生命周期来调用具体Bean方法
import com.hlyz.base.license.manager.LicenseVerify;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author fanghao10
*/
@Slf4j
@Configuration
public class LicenseConfig {
/**
* 证书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;
@Bean(initMethod = "installLicense", destroyMethod = "unInstallLicense")
public LicenseVerify licenseVerify() {
log.info("初始化License验证");
return new LicenseVerify(subject, publicAlias, storePass, licensePath, publicKeysStorePath);
}
}
其次是具体安装过程与验证过程的方法,通过LicenseVerify类来实现,也是Config中配置的Bean对象实例
包含一个部分参数的构造方法,通过构造方法获取到指定路径下的证书并获取详细内容
由于需要不影响启动,使用静态变量 installSuccess 作为判断是否安装成功的标志,并将其他可能会产生变更或者需要实时校验的参数保存到静态变量中
import com.alibaba.fastjson.JSONObject;
import com.hlyz.base.license.entity.CustomKeyStoreParam;
import de.schlichtherle.license.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import java.io.File;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.prefs.Preferences;
/**
* License校验类
*
* @author fanghao10
*/
@Slf4j
@Data
public class LicenseVerify {
/**
* 证书subject
*/
private String subject;
/**
* 公钥别称
*/
private String publicAlias;
/**
* 访问公钥库的密码
*/
private String storePass;
/**
* 证书生成路径
*/
private String licensePath;
/**
* 密钥库存储路径
*/
private String publicKeysStorePath;
/**
* LicenseManager
*/
private CustomLicenseManager licenseManager;
/**
* 标识证书是否安装成功
*/
public static boolean installSuccess;
/**
* 标识证书是否安装成功
*/
public static List<String> authUri;
public LicenseVerify(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;
log.info("获取证书参数{}", this);
}
private ApplicationContext context;
/**
* 安装License证书,读取证书相关的信息, 在bean加入容器的时候自动调用
*/
public void installLicense() {
try {
Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);
CipherParam cipherParam = new DefaultCipherParam(storePass);
KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class,
publicKeysStorePath,
publicAlias,
storePass,
null);
LicenseParam licenseParam = new DefaultLicenseParam(subject, preferences, publicStoreParam, cipherParam);
licenseManager = new CustomLicenseManager(licenseParam);
licenseManager.uninstall();
LicenseContent licenseContent = licenseManager.install(new File(licensePath));
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
installSuccess = true;
// 如果安装成功了 获取配置中可访问的路径
authUri = (JSONObject.parseObject((String) licenseContent.getExtra())).getJSONArray("authUri").toJavaList(String.class);
log.info("------------------------------- 证书安装成功 -------------------------------");
log.info(MessageFormat.format("证书有效期:{0} - {1}", format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter())));
} catch (Exception e) {
installSuccess = false;
log.error("------------------------------- 证书安装失败 -------------------------------");
log.error(e.getMessage());
// 抛出错误会导致启动失败
// throw new LicenseException(e.getMessage());
// new Thread(()->System.exit(0)).start();
}
}
/**
* 卸载证书,在bean从容器移除的时候自动调用
*/
public void unInstallLicense() {
if (installSuccess) {
try {
licenseManager.uninstall();
} catch (Exception e) {
// ignore
}
}
}
/**
* 校验License证书
*/
public boolean verify() {
try {
LicenseContent licenseContent = licenseManager.verify();
return true;
} catch (Exception e) {
return false;
}
}
}
然后是自定义的LicenseManager类CustomLicenseManager,继承trueLicense包中的父类并复写部分方法,用于证书详细的校验规则;
构造方法与创建父级对象,create 方法为初始化,调用统计的其他初始化与校验方法,通过指针的方式取值、校验等。
由于复写了父类的install方法,所以在LicenseVerify中会调用子类方法
又通过重写 verify 方法,调用其他方法后,不修改父类流程,经过几层嵌套后进入详细的校验 validate 方法
在方法中校验启动时单次生效不会变更的属性,例如IP地址与MAC地址等
通过抛出自定义错误的方法控制代码
import com.alibaba.fastjson.JSONObject;
import com.hlyz.base.common.core.utils.IpUtils;
import com.hlyz.base.license.entity.LicenseExtraModel;
import de.schlichtherle.license.*;
import de.schlichtherle.xml.GenericCertificate;
import de.schlichtherle.xml.XMLConstants;
import lombok.extern.slf4j.Slf4j;
import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
/**
* 自定义LicenseManager,用于增加额外的信息校验(除了LicenseManager的校验,我们还可以在这个类里面添加额外的校验信息)
*
* @author fanghao10
*/
@Slf4j
public class CustomLicenseManager extends LicenseManager {
public CustomLicenseManager(LicenseParam param) {
super(param);
}
/**
* 复写create方法
*/
@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地址等其他信息
*/
@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地址等其他信息
*/
@Override
protected synchronized LicenseContent verify(final LicenseNotary notary) throws Exception {
// Load license key from preferences,
final byte[] key = getLicenseKey();
if (null == key) {
throw new NoLicenseInstalledException(getLicenseParam().getSubject());
}
GenericCertificate certificate = getPrivacyGuard().key2cert(key);
notary.verify(certificate);
final LicenseContent content = (LicenseContent) this.load(certificate.getEncoded());
this.validate(content);
setCertificate(certificate);
return 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方法,用于增加我们额外的校验信息
*/
@Override
protected synchronized void validate(final LicenseContent content) throws LicenseContentException {
//1. 首先调用父类的validate方法
super.validate(content);
//2. 然后校验自定义的License参数,去校验我们的license信息
LicenseExtraModel expectedCheckModel = JSONObject.parseObject((String) content.getExtra(), LicenseExtraModel.class);
try {
// 做我们自定义的校验
String ip = expectedCheckModel.getIp();
String hostIp = IpUtils.getHostIp();
log.info("本机ip{},证书校验ip{}", hostIp, ip);
if (!ip.equals(hostIp)) {
throw new LicenseContentException("ip校验失败");
}
String mac = expectedCheckModel.getMac();
String macAddress = IpUtils.getMacAddress();
log.info("本机mac{},证书校验mac{}", macAddress, mac);
if (!macAddress.equals(mac)) {
throw new LicenseContentException("mac校验失败");
}
} catch (LicenseContentException licenseContentException) {
// 抛出错误 能被上层接收
throw licenseContentException;
} catch (Exception e) {
log.error(e.getMessage());
}
}
/**
* 重写XMLDecoder解析XML
*/
private Object load(String encoded) {
BufferedInputStream inputStream = null;
XMLDecoder decoder = null;
try {
inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XMLConstants.XML_CHARSET)));
decoder = new XMLDecoder(new BufferedInputStream(inputStream, XMLConstants.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);
}
}
return null;
}
}
以上注入到Spring的方法全部结束后,证书的安装与部分校验完成,在本项目框架中需要每次调用接口都需要校验URI是否能够通过
通过拦截器实现 LicenseInterceptor
通过静态变量 installSuccess 作为判断是否安装成功的标志,静态变量 authUri 判断是否为可通过的uri
需要将本拦截器加入到 WebMvcConfiguration 中注册
import com.hlyz.base.common.core.constant.SystemConstant;
import com.hlyz.base.common.core.dto.ResultDTO;
import com.hlyz.base.common.core.enums.ResultCodeEnum;
import com.hlyz.base.common.core.utils.CommonFuntions;
import com.hlyz.base.common.core.utils.JsonUtils;
import com.hlyz.base.common.core.utils.PropertiesUtils;
import com.hlyz.base.license.manager.LicenseVerify;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* token校验拦截器
*
* @author fanghao10
*/
@Configuration
public class LicenseInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 如果是开发环境 就不校验
if (SystemConstant.ENV_DEV.equals(PropertiesUtils.getProperty(SystemConstant.SPRING_PROFILES_ACTIVE))) {
return true;
}
// 校验是否安装成功
if (!LicenseVerify.installSuccess) {
try {
ResultDTO<String> result = new ResultDTO<>();
result.set(ResultCodeEnum.ERROR_LICENSE_NOT_VALID, "License is not valid !");
String errJson = JsonUtils.getJson(result);
response.getWriter().write(errJson);
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
String uri = request.getRequestURI();
boolean isPassUri = this.checkUri(uri, LicenseVerify.authUri);
if (isPassUri) {
return true;
}
try {
ResultDTO<String> result = new ResultDTO<>();
result.set(ResultCodeEnum.ERROR_LICENSE_NOT_VALID, "license uri is not allowed!");
String errJson = JsonUtils.getJson(result);
response.getWriter().write(errJson);
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
public boolean checkUri(String uri, List<String> authUri) {
// 为空的时候设置全通过
if (CommonFuntions.isEmptyObject(authUri)) {
return true;
}
for (String s : authUri) {
if (uri.contains(s)) {
return true;
}
}
return false;
}
}