前言
公司现在弄的这个基于java开发的管理系统,上次对接的密码机,这次对接签名验签服务器,其实流程很类似:根据厂家提供的文档,调用厂家提供的Api接口,注意参数和返回值就行。硬件设备已经在公司的机房架设好了,硬件客户端可以登录上查看一些参数。但这里也遇到了很多坑,调试花了些时间。
文件上传代码
基于ruoyi自带的FileUploadUtils工具类,有个upload通用的文件上传方法,要在这个功能基于这个方法添加上的一个附加的功能:上传一个文件同时,并且对这个文件的完整性做检验。那用什么做呢,公司领导要求调用签名验签服务器这个设备。
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws FileNameLengthLimitExceededException 文件名太长
* @throws IOException 比如读写文件出错时
* @throws InvalidExtensionException 文件校验异常
*/
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
throws Exception {
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
assertAllowed(file, allowedExtension);
String fileName = extractFilename(file);
//输入流
InputStream in = file.getInputStream();
//获取上传文件的绝对路径
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
//输出流
FileOutputStream fileOutputStream = new FileOutputStream(absPath);
int n;
byte[] bytes = new byte[4096];
ByteArrayOutputStream out = null;
//定义一个byte[],存储数据流文件
byte[] bytes1=null;
//前端传过来的输入流读取
while((n=in.read(bytes))!=-1){
out= new ByteArrayOutputStream();
out.write(bytes,0,n);
bytes1 = out.toByteArray();
}
//对数据流签名
byte[] sign = SecurityUtils.sign(bytes1);
//存入到硬盘
fileOutputStream.write(sign);
in.close();
fileOutputStream.close();
//定义文件类型,包含上传文件的地址
File file1 = new File(absPath);
int n1;
FileInputStream fileInputStream = null;
ByteArrayOutputStream out1= null;
try {
//定义一个输出流
fileInputStream = new FileInputStream(file1);
out1 = null;
//签名值
while ((n1 = fileInputStream.read(bytes)) != -1){
out1= new ByteArrayOutputStream();
//从刚上传的文件中,用流读取
out1.write(bytes,0,n1);
}
//数据类型转换:流转为byte[]
byte[] bytes2 = out.toByteArray();
System.out.println(Arrays.toString(bytes2));
//验签,返回结果如果是true,验签成功,否则失败,byte2数据原文,out1数据原文签名后的值
boolean result= SecurityUtils.verifySign(bytes2, out1.toByteArray());
System.out.println(result);
//校验失败,弹出提示
if(result==false){
throw new Exception("数据完整性校验失败");
}
} catch (Exception e) {
e.printStackTrace();
throw new Exception("数据完整性校验失败");
}finally{
fileInputStream.close();
out1.close();
}
return getPathFileName(baseDir, fileName);
}
调用签名验签的代码
以下是根据厂家提供的api方法,自己封装的工具方法,这里有一个bug,我自己单元测试的时候,方法传参很随意,用的String,结果String和byte []转换的有点冗余,把冗余的删了,代码也更清晰了,bug也解决了。这是我反复debug发现。只能说自己的技术还是很菜啊!
SecurityUtils.sign(byte[] b1)对数据流签名
SecurityUtils.verifySign(byte[] b2,byte[] b3) 对数据流验签
/**
* sm2签名,调用签名验签服务器
* @param data 代签名的原文
* @return 签名值
*/
public static byte[] sign(byte[] data){
FmApi api = new FmApi();
String ip = "xxx.xxx.xxx.xxx";
String[] ips = new String[1];
ips[0] = ip;
api.FM_DSVS_Connection(ips, 2024, 3);
System.out.println("连接成功");
byte[] sign = api.FM_DSVS_SignData(xxx, xxx,xxx, xxx);
System.out.println("签名成功");
System.out.println(Arrays.toString(sign));
return sign;
}
/**
* sm2验签,调用签名验签服务器
* @param data 代签名的原文
* @param singData 签名值
* @return
*/
public static boolean verifySign(byte[] data,byte[] singData){
// 7.2直接将证书放入验签
FmApi api = new FmApi();
String ip = "xxx.xxx.xxx.xxx";
String[] ips = new String[1];
ips[0] = ip;
api.FM_DSVS_Connection(ips, 2024, 3);
System.out.println("连接成功");
//证书
String cert = "xxxxxxxxxxxxxxx";
int state = api.FM_DSVS_VerifySignedData(1, Base64.decode(cert.getBytes()), null, data, singData, 0);
if (state == 0) {
return true;
} else {
return false;
}
}
测试验证
本地idea运行调试,在File file1 = new File(absPath);
打断点,然后点击上传文件按钮,程序会在断点处停下。然后修改上传文件的内容,如图我是随意删除了两个字符。
前端页面会弹出“数据完整性校验失败”
如果没有打断点,文件是可以直接上传成功的,这就是测试文件的完整性成功。
总结
这节运用到了java基础数据流部分的知识。解决问题首先是理清思路,然后匹配技术解决方案。对接这种物理机的代码,和以前学习连接数据库JDBC很类似。只不过现在有些连接数据库框架比如Mybatis都太成熟了,封装迭代。从以上代码中可以看出,我的基础还是太薄弱了。再有就是现在只是做到最基本对接,实际企业级大项目,信息安全的软件业务也是很复杂的,比如签名验签服务器提供很多其它功能接口我都完全看不懂。