背景
因为项目需要,要实现一个数字签名的功能。数字签名功能,需要加签,验签两个方法。这两个方法,通常是通过专门的签名服务器实现。签名服务器的供应商不只一家。同时在开发初期,也不会购买签名服务器,那就需要一个临时方法,让开发可以正常进行。而且为了在以后的生产环境部署,可以使用不同供应商的签名服务器,需要数字签名实现类的无缝切换。就是不修改已有的代码,通过配置调用不同的实现类。
具体的说,就是没有签名服务器的时候,通过配置调用临时实现。有签名服务器时,根据不同的签名服务器,配置不同的参数,调用对应的实现。并且这种调用,不需要修改已有的代码。
如果新增一个新的供应商的签名服务器,那么就新增一个对应实例类,并定义一个对应参数。就可以通过配置改参数,实现无缝切换。
Maven依赖
1. 导入springboot
<parent>
<artifactId>spring-boot-dependencies</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.3.RELEASE</version>
</parent>
2. 导入spring-boot-starter-web和spring-boot-starter-test
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
3.项目结构
项目启动类
package cn.com.soulfox;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @create 2023/11/6 16:17
*/
@SpringBootApplication
public class FactoryModelRun {
public static void main(String[] args) {
SpringApplication.run(FactoryModelRun.class, args);
}
}
Spring上下文工具类
这个类提供了通过BeanName从spring容器中获取实例的方法
package cn.com.soulfox.utils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
/**
* spring工具类
* @author jiyh
*/
@Component
public final class SpringContextHelper implements BeanFactoryPostProcessor {
/**
* Spring应用上下文环境
*/
private static ConfigurableListableBeanFactory beanFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringContextHelper.beanFactory = beanFactory;
}
/**
* 获取对象
*
* @param name 实例名称
* @return 一个以所给名字注册的bean的实例
* @throws BeansException
*/
public static <T> T getBean(String name, Class<T> clz) throws BeansException {
return (T) beanFactory.getBean(name, clz);
}
}
数字签名功能类
1. 数字签名接口
package cn.com.soulfox.sign;
/**
* 数字签名接口
* @create 2023/11/6 16:32
*/
public interface SignServer {
/**
* 签名
* @param signText 签名明文串
* @param dn 数字证书 DN
* @return
*/
String sign(String signText, String dn);
/**
* 核验签名
* @param singText 签名明文串
* @param certSignedText 签名结果串
* @param dn 数字证书 DN
* @return
*/
boolean verify(String singText, String certSignedText, String dn);
}
2. 数字签名默认实现
package cn.com.soulfox.sign;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* 默认实现,返回一些默认值,让程序顺利执行
* @create 2023/11/6 16:36
*/
@Component("sign-default")//这里的“sign-default”就是beanName
@Lazy//延迟加载就是在没有配置的情况,不初始化
public class DefaultSignServer implements SignServer {
/**
* 加签方法,返回原明文串
* @param signText 签名明文串
* @param dn 数字证书 DN
* @return
*/
@Override
public String sign(String signText, String dn) {
System.out.println("加签默认实现——————————————————————————");
return signText;
}
/**
* 验签方法,直接返回true
* @param singText 签名明文串
* @param certSignedText 签名结果串
* @param dn 数字证书 DN
* @return
*/
@Override
public boolean verify(String singText, String certSignedText, String dn) {
System.out.println("验签默认实现——————————————————————————");
return true;
}
}
3. 数字签名工厂类-用于初始化/提供,数字签名实现类
package cn.com.soulfox.sign;
import cn.com.soulfox.utils.SpringContextHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author jiyh
* @create 2023/11/6 16:45
*/
@Component
public class SignServerFactory {
/**
* 数字签名实现类 beanName
*/
private static String beanName;
/**
* 数字签名对象
*/
private static SignServer signServer;
/**
* 初始化数字签名对象
*/
@PostConstruct
public void initSignServer(){
if(beanName == null || "".equals(beanName)){
beanName = "sign-default";
}
signServer = SpringContextHelper.getBean(beanName, SignServer.class);
if(signServer == null){
signServer = new DefaultSignServer();
}
}
public static SignServer getSignServer(){
return SignServerFactory.signServer;
}
/**
* 获取配置的数字签名服务类参数
* @param beanName
*/
@Value("${sign-server.bean-name:sign-default}")
public void setBeanName(String beanName) {
System.out.println("数字签名配置参数[beanName->"+beanName+"]");
SignServerFactory.beanName = beanName;
}
}
4. application.yml 中参数配置
server:
port: 19002
spring:
application:
name: sign
#这里配置数字签名实例的beanName
#如果使用默认实现类,这个参数可以不配置
sign-server:
bean-name: sign-default
测试1
1. 测试类
package cn.com.soulfox.sign;
import cn.com.soulfox.FactoryModelRun;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author jiyh
* @create 2023/11/6 16:40
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FactoryModelRun.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DefaultSignServerTest {
@Test
public void Test(){
SignServer signServer = SignServerFactory.getSignServer();
String sign = signServer.sign("test", "DN");
System.out.println(sign);
boolean result = signServer.verify("test", "test","DN");
System.out.println(result);
}
}
2. 测试结果![测试结果-默认](https://i-blog.csdnimg.cn/blog_migrate/c6009c1609319f2f879851a062a69959.png#pic_center)
测试2
现在我们把实现类切换到ExceptionSignServer类,这个类的作用是返回异常信息,比如验签失败。
1. ExceptionSignServer代码
package cn.com.soulfox.sign;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* 异常实现,返回一些异常值,测试异常流程
* @create 2023/11/6 16:36
*/
@Component("sign-exception")//这里的“sign-exception”就是beanName
@Lazy//延迟加载就是在没有配置的情况,不初始化
public class ExceptionSignServer implements SignServer {
/**
* 加签方法,返回null. 标识加签异常
* @param signText 签名明文串
* @param dn 数字证书 DN
* @return
*/
@Override
public String sign(String signText, String dn) {
System.out.println("加签异常实现——————————————————————————");
return null;
}
/**
* 验签方法,直接返回fasle,标识验签失败
* @param singText 签名明文串
* @param certSignedText 签名结果串
* @param dn 数字证书 DN
* @return
*/
@Override
public boolean verify(String singText, String certSignedText, String dn) {
System.out.println("验签异常实现——————————————————————————");
return false;
}
}
2. 修改 application.yml 中参数配置
server:
port: 19002
spring:
application:
name: sign
#修改为异常实现类
sign-server:
bean-name: sign-exception
3. 测试结果
总结
以上在DefaultSignServer 和ExceptionSignServer两个实现类的切换过程中,只修改了配置文件 application.yml 中的配置参数。调用代码的地方没有修改,这样就实现了无缝切换。后续有了签名服务器,先导入供应商提供的jar包,再实现SignServer接口。就可以在不修改代码的情况下切换到对应签名服务器的加签,验签功能。这也是JAVA开发原则中,开闭原则(对新增开放对修改封闭)的体现。