业务场景
在软件产品或者源码发布后,防止产品或者源码的外流导致核心竞争力被破解,直接影响商业价值,如何对产品进行加密,对产品的保护非常的重要。
1、办公软件-下载安装后如果没有进行授权,可以无限制的复制,导致产品被滥用,资金流失。
2、源码被窃取,通过源码解读,去除加密授权的拦截或者过滤功能,导致源码流失,资金损失,核心产品外泄。
如果去管控呢?
方法一、只对外发布产品安装包或者产品运行包,不提供核心源码,如果非要提供源码,也要控制核心工具类、验证类是jar包的形式使用,而非全部代码。
方法二、增加额外的验证机制,当发现拦截器或过滤器没有相应时候,采取代码破坏机制,动态删除一些核心的配置文件,并且将破解人的远程IP,机器码,授权源码信息发送给管理端。
方法三、代码混淆,源代码保护,代码逻辑混淆,代码加密,提高代码阅读成本
所有的加密、混淆、机制管控都是操作产品或者源码被泄露的手段,但是任何的手段都可以被破解,所以在实际过程中,除了增加手段之外还要结合其他的方式进行关联,比如签订合同、软著申请等等。
产品和源码认证授权加密
RSA非对称加密
什么是rsa非对称加密
一般场景 服务端和移动端 数据传输加密,用AES对称加密,同一个秘钥,当一端被获取后,数据都将被获取。
对称加密算法在加密和解密时使用的是同一个秘钥;而非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
服务端-公钥加密 移动端-私钥解密。
与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫做非对称加密算法。
非对称加密的优缺点
非对称加密与对称加密相比,其安全性更好:对称加密的通信双方使用相同的秘钥,如果一方的秘钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对秘钥,一个用来加密,一个用来解密,而且公钥是公开的,秘钥是自己保存的,不需要像对称加密那样在通信之前要先同步秘钥。
非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
RSA加解密工具类
package com.qingfeng.util;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
/**
* <p>
* rsa 加密工具
*/
public class RSAUtils {
/**
* 加密算法RSA
*/
public static final String KEY_ALGORITHM = "RSA";
/**
* 签名算法
*/
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
/**
* 获取公钥的key
*/
private static final String PUBLIC_KEY = "RSAPublicKey";
/**
* 获取私钥的key
*/
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* <p>
* 生成密钥对(公钥和私钥)
* </p>
*
* @return
* @throws Exception
*/
public static Map<String, Object> genKeyPair() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* <p>
* 用私钥对信息生成数字签名
* </p>
*
* @param data 已加密数据
* @param privateKey 私钥(BASE64编码)
*
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateK);
signature.update(data);
return encode(signature.sign());
}
/**
* <p>
* 校验数字签名
* </p>
*
* @param data 已加密数据
* @param publicKey 公钥(BASE64编码)
* @param sign 数字签名
*
* @return
* @throws Exception
*
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
byte[] keyBytes = decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicK);
signature.update(data);
return signature.verify(decode(sign));
}
/**
* <P>
* 私钥解密
* </p>
*
* @param encryptedData 已加密数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey)
throws Exception {
byte[] keyBytes = decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateK);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
/**
* <p>
* 公钥解密
* </p>
*
* @param encryptedData 已加密数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey)
throws Exception {
byte[] keyBytes = decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicK);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
/**
* <p>
* 公钥加密
* </p>
*
* @param data 源数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String publicKey)
throws Exception {
byte[] keyBytes = decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicK);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
/**
* <p>
* 私钥加密
* </p>
*
* @param data 源数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String privateKey)
throws Exception {
byte[] keyBytes =decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateK);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
/**
* <p>
* 获取私钥
* </p>
*
* @param keyMap 密钥对
* @return
* @throws Exception
*/
public static String getPrivateKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return encode(key.getEncoded());
}
/**
* <p>
* 获取公钥
* </p>
*
* @param keyMap 密钥对
* @return
* @throws Exception
*/
public static String getPublicKey(Map<String, Object> keyMap)
throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return encode(key.getEncoded());
}
/**
* base64 加密
* @param bytes
* @return
* @throws Exception
*/
public static String encode(byte[] bytes) throws Exception {
return new String(Base64.encodeBase64(bytes));
}
/**
* base64 解密
* @param base64
* @return
* @throws Exception
*/
public static byte[] decode(String base64) throws Exception {
return Base64.decodeBase64(base64.getBytes());
}
/**
* @Description: 解密
* @Param: [data, privateKey]
* @return: java.lang.String
* @Author: anxingtao
* @Date: 2019-3-13 11:06
*/
public static String decrypt(String data, String privateKey) throws Exception {
byte[] pubdata = decryptByPrivateKey(decode(data), privateKey);
return new String(pubdata, "UTF-8");
}
/**
* @Description: 加密
* @Param: [data, publicKey]
* @return: java.lang.String
* @Author: anxingtao
* @Date: 2019-3-13 11:10
*/
public static String encrypt(String data, String publicKey) throws Exception {
byte[] pubdata = encryptByPublicKey(data.getBytes("UTF-8"), publicKey);
String outString = new String(encode(pubdata));
return outString;
}
/**
* @title encrypriva
* @description 私钥加密
* @author Administrator
* @updateTime 2021/8/11 16:31
*/
public static String encrypk(String data, String privateKey) throws Exception {
byte[] resdata = encryptByPrivateKey(data.getBytes("UTF-8"), privateKey);
String outString = new String(encode(resdata));
return outString;
}
/**
* @title decrypk
* @description 公钥解密
* @author Administrator
* @updateTime 2021/8/11 16:32
*/
public static String decrypk(String data, String publicKey) throws Exception {
byte[] resdata = decryptByPublicKey(decode(data), publicKey);
return new String(resdata, "UTF-8");
}
public static void main(String[] args) throws Exception {
// String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALbGpPkikVf51rzHHr5rCmtIjpsq9Pked0W5xu5Khf4fQEl7utYahfC3dleFaCyLkD39Q1PpyOrQm7YQPSxkHR7Bn/w/CA93EYNKXG1cAZjDr/ZJAd6xUY2K4GkriWB9v4OeCTThkKwfharJJW270No5VT49ktKAHKkug/WnCUN7AgMBAAECgYEAlxPNK20qTFjj6biBLg5WV2VrEtFIGl7XYdf0meUZqnr0bYkLX4we6GENPby05hUaTlL4gvT8MTPrcWss1XOPKQYRrSjLBZmmCiYe+SDxx0ZPmNN1db86X0ahQsNzxAVkTyIKKVW1SxwyvIOSN5H71yyBzZj/yG0qymj8H0hBVFkCQQDijlLZ9E7pGT/2HLitxEyi02kIjlsgLtjmk8NP8if5eCspmKXaw3xH2j3BLOGDGZUjeQGZcyOOHdGnNkbUVSdtAkEAzoe6ugzr63r70CwbWAEBQLRUfX4i8fiLazlCybhVAMEKjbKTdcAUFLfKNmharPHE/dlHLja/dReaLMKB7l69hwJABcM/AkI/m5hD0zvJysm6dU3RVyFf2gK3C65ogmkTcToIRweV+GmOiLlZZseAePg2ne9fBgsytVO22Hz98jq0RQJAGOyOX0eR7RAhdYTtI9izMwDQNXjUdMke4ii946QoNfgV8vW7D/nHMpzffWNolfhzYoMnMO+QeWwIwiATGBY83wJBAN5aLGltsuW/6ILvMTgciwdQlO3dTjzFXhbXURsyKWYF6QJnaeqNMVqr9F0lWXKQyuIu+mj5JRkZszOvS0jczNA=\n";
// String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2xqT5IpFX+da8xx6+awprSI6bKvT5HndFucbuSoX+H0BJe7rWGoXwt3ZXhWgsi5A9/UNT6cjq0Ju2ED0sZB0ewZ/8PwgPdxGDSlxtXAGYw6/2SQHesVGNiuBpK4lgfb+Dngk04ZCsH4WqySVtu9DaOVU+PZLSgBypLoP1pwlDewIDAQAB";
// String msg = "{\"dev_id\":\"868989034328943\",\"user_id\":\"\",\"team_id\":\"\"}";
// String msg = DateTimeUtil.getDate();
// String sm = RSAUtils.encrypt(msg,publicKey);
// System.out.println(sm);
// String str = "IGCJsmAEdYDle/WYAS4qd3hCp9TAZRz806Sm8p4KepJ2XuDaOnKh65homSgZJ88/pQs5rEm6Qmsg+8JqMgqjixD8jZhnToVriKzkhHe2UarS+sT8oUAvXrO98FwZv7GELgt4mCuitwxrREYq4QUtwI6Fc+SGdF0BjeKNUSWHifg=";
//
//
//
// String s = RSAUtils.decrypt(sm,privateKey);
// System.out.println(s);
//生成密钥对(公钥和私钥)
Map map = RSAUtils.genKeyPair();
System.out.println("----------------公钥----------------");
System.out.println(RSAUtils.getPublicKey(map));
System.out.println("----------------私钥----------------");
System.out.println(RSAUtils.getPrivateKey(map));
// String str = RSAUtils.encrypk("我爱你中国",privateKey);
// System.out.println(str);
//
// String res = RSAUtils.decrypk(str,publicKey);
// System.out.println(res);
// String content = "F2cgqkTN1DehdikNnKtt5t0p+HrOr4hwWrbf9MGmksWUQFwWzFPbwt1at7uXYp8mt88KiIHrD/yKe8pkdzuq2VMTyvObryIbVannBHtzrhjBd2FoRQpaorMWtFuv7MZY3WmuqhzdhXMXAx1pB/hmEToncvW3ZpEWp7N5ItO89H8=";
// String priKey = "MIICYQIBAAKBgQCijkoTGTel/S62WwUpXswc8m7Fdp+SY2ZMZKV8FeRD5QUi1mGYUHqmab7w/xN2vdNWI9sR9MPL4aO2IA57HD4HTGmidyCDmxl8QCRMSNwXroIP6mWpI5lHjajPth25n2gwsnYoAvrSfTnTkUAmoKUtQSg3/AZ9Rg8mf8NFY5AjGwIDAQABAoGBAJqS8W9NyHPn2DaBQNxBD5jrE1hj34NFT+6OuinPa1sAeSzSbMV4qdh6r53dADYmdcLwn41okZLbAmDaBBscUUZo/pIUPdGgU+P7RqpMtfwbq/RoleFpU2GWBErJ7/qKe6cfcEyuBhAMG8vLqtfA70P75vFAMoKhzWsUGtD5gQVZAkUAuRjD839QS/u17Xv6jWbcw0vxTD2UN/hyObyQORlX+LjxC5RCuYwkkWoXIGe94hzsyCoURc6RhakRM/LyqhCliTQ+PvcCPQDg0xUHm2qr8jfo1PTYf6Ks8Id9FZiqxD1VJh2OoRMSTCPo8O4PskbzictNu2siFLt0r3EKjCDP1jP0H/0CRFSsO5d8OiNINmU5PdjJoVvFtdCGqvMfuEEpPWChc1jYYYxGem+e6GuM+J9eVcLGMJswhK2aXX+jY7c8AD5D9zXYrFDpAj0Ap5OOXEIyy4FavRhmfCz+wyrxwoFjbv2gvaQQaeyTu5K3PXy/5UE783Ek8Yad/yQ26W2Ps43pMyF1TiS9AkQrfddTmrz7OZEfFtgaHuSj/8gbkGEdr7H8YIML/wZ1ozDkfxwsnzgclJXir/Qb2VjGOSzfUrlj2SaxgqIGfKI7DyEWeg==";
// System.out.println(priKey);
//
// String priKey2 = "MIICewIBADANBgkqhkiG9w0BAQEFAASCAmUwggJhAgEAAoGBAKKOShMZN6X9LrZbBSlezBzybsV2n5JjZkxkpXwV5EPlBSLWYZhQeqZpvvD/E3a901Yj2xH0w8vho7YgDnscPgdMaaJ3IIObGXxAJExI3Beugg/qZakjmUeNqM+2HbmfaDCydigC+tJ9OdORQCagpS1BKDf8Bn1GDyZ/w0VjkCMbAgMBAAECgYEAmpLxb03Ic+fYNoFA3EEPmOsTWGPfg0VP7o66Kc9rWwB5LNJsxXip2Hqvnd0ANiZ1wvCfjWiRktsCYNoEGxxRRmj+khQ90aBT4/tGqky1/Bur9GiV4WlTYZYESsnv+op7px9wTK4GEAwby8uq18DvQ/vm8UAygqHNaxQa0PmBBVkCRQC5GMPzf1BL+7Xte/qNZtzDS/FMPZQ3+HI5vJA5GVf4uPELlEK5jCSRahcgZ73iHOzIKhRFzpGFqREz8vKqEKWJND4+9wI9AODTFQebaqvyN+jU9Nh/oqzwh30VmKrEPVUmHY6hExJMI+jw7g+yRvOJy027ayIUu3SvcQqMIM/WM/Qf/QJEVKw7l3w6I0g2ZTk92MmhW8W10Iaq8x+4QSk9YKFzWNhhjEZ6b57oa4z4n15VwsYwmzCErZpdf6NjtzwAPkP3NdisUOkCPQCnk45cQjLLgVq9GGZ8LP7DKvHCgWNu/aC9pBBp7JO7krc9fL/lQTvzcSTxhp3/JDbpbY+zjekzIXVOJL0CRCt911OavPs5kR8W2Boe5KP/yBuQYR2vsfxggwv/BnWjMOR/HCyfOByUleKv9BvZWMY5LN9SuWPZJrGCogZ8ojsPIRZ6";
//
// System.out.println(RSAUtils.decrypt(content,priKey2));
}
}
新增证书颁发模块
证书模块功能效果
核心代码解析
/**
* @title exportCert
* @description 导出证书
* @author Administrator
* @updateTime 2021/8/11 15:46
*/
@RequiresPermissions("authCert:exportCert")
@RequestMapping(value = "/exportCert", method = RequestMethod.GET)
public void exportCert(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws Exception {
PageData pd = new PageData(request);
PageData p = authCertService.findInfo(pd);
String content = p.get("title")+"#"+p.get("start_time")+"#"+p.get("end_time")+"#"+p.get("ip_address")+"#"+p.get("mac_address")+"#"+p.get("other_limit");
System.out.println(content);
System.out.println(commonConfig.getPublicKey());
String license = RSAUtils.encrypt(content,commonConfig.getPublicKey());
// 创建根节点
Element root = new Element("QingFeng");
// 为根节点设置属性
root.setAttribute("version", p.get("version_num").toString());
// 创建Document对象,并为其设置根节点
Document document = new Document(root);
Element title = new Element("Title");
Element sdate = new Element("SDate");
Element edate = new Element("EDate");
Element ip = new Element("IP");
Element mac = new Element("MAC");
Element olimit = new Element("OLimit");
Element licenseKeys = new Element("LicenseKeys");
title.addContent(p.get("title").toString());
sdate.addContent(p.get("start_time").toString());
edate.addContent(p.get("end_time").toString());
ip.addContent(p.get("ip_address")+"");
mac.addContent(p.get("mac_address")+"");
olimit.addContent(p.get("other_limit")+"");
licenseKeys.addContent(license);
root.addContent(title);
root.addContent(sdate);
root.addContent(edate);
root.addContent(ip);
root.addContent(mac);
root.addContent(olimit);
root.addContent(licenseKeys);
// 创建XMLOutputter对象
XMLOutputter outputter = new XMLOutputter();
//创建Format对象(自动缩进、换行)
Format format = Format.getPrettyFormat();
//为XMLOutputter设置Format对象
outputter.setFormat(format);
// 将Document转换成XML
String tempPath = "D:\\home\\template\\QingfengLicense.xml";
// String tempPath = session.getServletContext().getRealPath("/") + "/template/QingfengLicense.xml";
outputter.output(document, new FileOutputStream(new File(tempPath)));
// String toPath = session.getServletContext().getRealPath("/") + "/template/QingfengLicense.zip";
/** 测试压缩方法2 */
// List<File> fileList = new ArrayList<>();
// fileList.add(new File(tempPath));
// FileOutputStream fos2 = new FileOutputStream(new File(toPath));
// ZipUtils.toZip(fileList, fos2);
FileUtil.downFile(response, tempPath, "QingfengLicense.xml");
// File file = new File(toPath);
// file.delete();
// file.deleteOnExit();
}
证书下载
public static void downFile(HttpServletResponse response, String filePath,
String filename) throws Exception {
File tempFile = new File(filePath);
System.out.println(tempFile.exists());
if (tempFile.exists()) {
response.reset();
response.setContentType("bin");//
filename = new String(filename.getBytes(), "ISO-8859-1");
response.addHeader("Content-Disposition", "attachment;filename="
+ filename);
FileInputStream fis = new FileInputStream(tempFile);
OutputStream os = response.getOutputStream();
byte[] bb = new byte[1024*8];
int i = 0;
while ((i = fis.read(bb)) > 0) {
os.write(bb, 0, i);
}
os.close();
os.flush();
fis.close();
}
}
证书认证校验-项目启动
证书校验源码
/**
* 继承Application接口后项目启动时会按照执行顺序执行run方法
* 通过设置Order的value来指定执行的顺序
*/
@Component
@Order(value = 1)
public class StartService implements ApplicationRunner {
@Autowired
private CommonConfig commonConfig;
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
ClassPathResource classPathResource = new ClassPathResource("config/QingfengLicense.xml");
// InputStream inputStream =classPathResource.getInputStream();
// 创建SAXReader对象
SAXReader reader = new SAXReader();
// 加载xml文件
Document dc= reader.read(classPathResource.getFile());
// 获取根节点
Element e = dc.getRootElement();
System.out.println(e);
// 获取迭代器
Iterator it = e.elementIterator();
// 遍历迭代器,获取根节点信息
PageData pd = new PageData();
while(it.hasNext()){
Element book = (Element) it.next();
pd.put(book.getName(),book.getStringValue());
}
String LicenseKeys = pd.get("LicenseKeys").toString();
String license = RSAUtils.decrypt(LicenseKeys,commonConfig.getPrivateKey());
if(Verify.verifyIsNull(license)){
throw new RuntimeException("正式认证失败。");
}else{
System.out.println(license);
String[] params = license.split("#", -1);
System.out.println(params.length);
String title = params[0];
String start_time = params[1];
String end_time = params[2];
String ip_address = params[3];
String mac_address = params[4];
String other_limit = params[5];
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date startDate = sdf.parse(start_time);
Date endDate = sdf.parse(end_time);
if(startDate.after(DateTimeUtil.getDDate()) || endDate.before(DateTimeUtil.getDDate())){
throw new RuntimeException("认证正式已失效,请重新授权。");
}
System.out.println(IpUtils.getIpAddress());
System.out.println(IpUtils.getMacAddress());
if(Verify.verifyIsNotNull(ip_address)){
if(!ip_address.equals(IpUtils.getIpAddress())){
throw new RuntimeException("授权IP无效,请重新授权。");
}
}
if(Verify.verifyIsNotNull(mac_address)){
if(!mac_address.equals(IpUtils.getMacAddress())){
throw new RuntimeException("授权MAC无效,请重新授权。");
}
}
}
// System.out.println("###############################项目启动成功###############################");
}
}
ApplicationRunner接口功能说明
1、ApplicationRunner
是一个接口,常用于项目启动后,(也就是ApringApplication.run()执行结束),立马执行某些逻辑。
可用于项目的准备工作,比如加载配置文件,加载执行流,定时任务等等。
2、如何使用ApplicationRunner
可以有多个实例实现该接口,但是一般需要增加注解@Order来指定加载顺序
@Order(-1)优先于@Order(0)
@Order(1)优先于@Order(2)
@Component
@Order(2)
public class QingfengRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(args);
System.out.println("这个是测试ApplicationRunner接口");
}
}
3、还有个接口,也可以实现和ApplicationRunner一样的功能
CommandLineRunner
CommandLineRunner接口的run方法接收的参数为String数组
@Component
@Order(value = 2)
public class MyStartupRunner1 implements CommandLineRunner{
@Override
public void run(String... strings) throws Exception {
System.out.println(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作 MyStartupRunner1 order 2 <<<<<<<<<<<<<");
}
}
@Component
@Order(value = 1)
public class MyStartupRunner2 implements CommandLineRunner {
@Override
public void run(String... strings) throws Exception {
System.out.println(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作 MyStartupRunner2 order 1 <<<<<<<<<<<<<");
}
}
证书认证校验-过滤器
过滤器证书校验源码
@Configuration
public class WebFilterConfig {
@Bean
public FilterRegistrationBean filterDemo3Registration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(WebUrlFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("filterDemo3");
registration.setOrder(1);
return registration;
}
@Bean
public Filter WebUrlFilter() {
return new WebUrlFilter();
}
}
public class WebUrlFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private CommonConfig commonConfig;
@Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("doFilter 1");
}
@SneakyThrows
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
// request.setAttribute("menuAuthParams","add,edit,del");
System.out.println("doFilter 2");
ClassPathResource classPathResource = new ClassPathResource("config/QingfengLicense.xml");
// InputStream inputStream =classPathResource.getInputStream();
// 创建SAXReader对象
SAXReader reader = new SAXReader();
// 加载xml文件
Document dc= reader.read(classPathResource.getFile());
// 获取根节点
Element e = dc.getRootElement();
System.out.println(e);
// 获取迭代器
Iterator it = e.elementIterator();
// 遍历迭代器,获取根节点信息
PageData pd = new PageData();
while(it.hasNext()){
Element book = (Element) it.next();
pd.put(book.getName(),book.getStringValue());
}
String LicenseKeys = pd.get("LicenseKeys").toString();
String license = RSAUtils.decrypt(LicenseKeys,commonConfig.getPrivateKey());
if(Verify.verifyIsNull(license)){
throw new RuntimeException("正式认证失败。");
}else{
System.out.println(license);
String[] params = license.split("#", -1);
System.out.println(params.length);
String title = params[0];
String start_time = params[1];
String end_time = params[2];
String ip_address = params[3];
String mac_address = params[4];
String other_limit = params[5];
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date startDate = sdf.parse(start_time);
Date endDate = sdf.parse(end_time);
if(startDate.after(DateTimeUtil.getDDate()) || endDate.before(DateTimeUtil.getDDate())){
throw new RuntimeException("认证正式已失效,请重新授权。");
}
System.out.println(IpUtils.getIpAddress());
System.out.println(IpUtils.getMacAddress());
if(Verify.verifyIsNotNull(ip_address)){
if(!ip_address.equals(IpUtils.getIpAddress())){
throw new RuntimeException("授权IP无效,请重新授权。");
}
}
if(Verify.verifyIsNotNull(mac_address)){
if(!mac_address.equals(IpUtils.getMacAddress())){
throw new RuntimeException("授权MAC无效,请重新授权。");
}
}
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("doFilter 3");
}
}
什么是过滤器
过滤器的概念
在讲Spring boot之前,我们先了解一下过滤器和拦截器。这两者在功能方面很类似,但是在具体技术实现方面,差距还是比较大的。在分析两者的区别之前,我们先理解一下AOP的概念,AOP不是一种具体的技术,而是一种编程思想。在面向对象编程的过程中,我们很容易通过继承、多态来解决纵向扩展。 但是对于横向的功能,比如,在所有的service方法中开启事务,或者统一记录日志等功能,面向对象的是无法解决的。所以AOP——面向切面编程其实是面向对象编程思想的一个补充。而我们今天讲的过滤器和拦截器都属于面向切面编程的具体实现。而两者的主要区别包括以下几个方面:
1、Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。
2、Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理的方式来执行。
3、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。
过滤器的使用场景
Java过滤器是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器可以对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改;也可以对响应进行过滤,拦截或修改响应。
如图,浏览器发出的请求先递交给第一个filter进行过滤,符合规则则放行,递交给filter链中的下一个过滤器进行过滤。过滤器在链中的顺序与它在web.xml中配置的顺序有关,配置在前的则位于链的前端。当请求通过了链中所有过滤器后就可以访问资源文件了,如果不能通过,则可能在中间某个过滤器中被处理掉。
在doFilter()方法中,chain.doFilter()前的一般是对request执行的过滤操作,chain.doFilter后面的代码一般是对response执行的操作。过滤链代码的执行顺序如下:
过滤器一般用于登录权限验证、资源访问权限控制、敏感词汇过滤、字符编码转换等等操作,便于代码重用,不必每个servlet中还要进行相应的操作。
代码混淆ProGuard
ProGuard简介
ProGuard是一个混淆代码的开源项目,它的主要作用是混淆代码,ProGuard还包括以下4个功能。
- 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)。
- 优化(Optimize):对字节码进行优化,移除无用的指令。
- 混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名。
- 预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。
Proguard是一个Java类文件压缩器、优化器、混淆器、预校验器。压缩环节会检测以及移除没有用到的类、字段、方法以及属性。优化环节会分析以及优化方法的字节码。混淆环节会用无意义的短变量去重命名类、变量、方法。这些步骤让代码更精简,更高效,也更难被逆向(破解)。
Springboot整合ProGuard代码混淆
<!-- proguard混淆插件-->
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.2.0</version>
<executions>
<execution>
<!-- 打包的时候开始混淆-->
<phase>package</phase>
<goals>
<goal>proguard</goal>
</goals>
</execution>
</executions>
<configuration>
<injar>${project.build.finalName}.jar</injar>
<!--输出的jar-->
<outjar>${project.build.finalName}.jar</outjar>
<!-- 是否混淆-->
<obfuscate>true</obfuscate>
<options>
<option>-target 1.8</option> <!--指定java版本号-->
<option>-dontshrink</option> <!--默认开启,不做收缩(删除注释、未被引用代码)-->
<option>-dontoptimize</option><!--默认是开启的,这里关闭字节码级别的优化-->
<!-- 不路过非公用类文件及成员-->
<option>-dontskipnonpubliclibraryclasses</option>
<option>-dontskipnonpubliclibraryclassmembers</option>
<!--不用大小写混合类名机制-->
<option>-dontusemixedcaseclassnames</option>
<!-- 优化时允许访问并修改有修饰符的类和类的成员 -->
<option>-allowaccessmodification</option>
<!-- 确定统一的混淆类的成员名称来增加混淆-->
<option>-useuniqueclassmembernames</option>
<!-- 不混淆所有包名-->
<!--<option>-keeppackagenames</option>-->
<option>-adaptclassstrings</option><!--混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代-->
<option>-ignorewarnings
</option><!-- 忽略warn消息,如果提示org.apache.http.* 这个包里的类有问题,那么就加入下述代码:-keep class org.apache.http.** { *; } -dontwarn org.apache.http.**-->
<option>-keep class org.apache.logging.log4j.util.* { *; }</option>
<option>-dontwarn org.apache.logging.log4j.util.**</option>
<option>-keepattributes
Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
</option><!--对异常、注解信息在runtime予以保留,不然影响springboot启动-->
<!--不混淆所有interface接口-->
<!-- <option>-keepnames interface **</option>-->
<option>-keepclassmembers enum * { *; }</option><!--保留枚举成员及方法-->
<option>-keepparameternames</option>
<option>-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);}
</option> <!--保留main方法的类及其方法名-->
<!--忽略note消息,如果提示javax.annotation有问题,那麽就加入以下代码-->
<option>-dontnote javax.annotation.**</option>
<option>-dontnote sun.applet.**</option>
<option>-dontnote sun.tools.jar.**</option>
<option>-dontnote org.apache.commons.logging.**</option>
<option>-dontnote javax.inject.**</option>
<option>-dontnote org.aopalliance.intercept.**</option>
<option>-dontnote org.aopalliance.aop.**</option>
<option>-dontnote org.apache.logging.log4j.**</option>
<!-- ##### 以下为需要根据项目情况修改 comment by 青锋 ##### -->
<!--入口程序类不能混淆,混淆会导致springboot启动不了-->
<option>-keep class com.qingfeng.Application</option>
<option>-keep class com.qingfeng.framework.**.* {*;}</option>
<option>-keep class com.qingfeng.quartz.**.* {*;}</option>
<!-- <option>-keep class com.qingfeng.system.entity.* {*;}</option>-->
<!-- <option>-keep class com.qingfeng.system.model.* {*;}</option>-->
<!-- <option>-keep class com.qingfeng.system.controller.* {*;}</option>-->
<!-- ##### 以上为需要根据项目情况修改 comment by qingfeng ##### -->
<option>-keep interface * extends * { *; }</option>
<!--不混淆所有类,保存原始定义的注释-->
<option>-keepclassmembers class * {
@org.springframework.beans.factory.annotation.Autowired *;
@org.springframework.beans.factory.annotation.Value *;
}
</option>
</options>
<libs>
<!-- 添加依赖 java8-->
<lib>${java.home}/lib/rt.jar</lib>
<lib>${java.home}/lib/jce.jar</lib>
</libs>
</configuration>
<dependencies>
<!-- https://mvnrepository.com/artifact/net.sf.proguard/proguard-base -->
<dependency>
<groupId>net.sf.proguard</groupId>
<artifactId>proguard-base</artifactId>
<version>6.1.1</version>
</dependency>
</dependencies>
</plugin>
<!--Springboot repackage 打包-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<!-- spingboot 打包需要repackage否则不是可执行jar -->
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<mainClass>com.qingfeng.Application</mainClass>
</configuration>
</execution>
</executions>
</plugin>
问题:
1.由于在混淆后代码a.class,b.class,c.class等方式,会导致springboot注入bean的时候报bean冲突,需要对生成bean策略进行修改,添加类:
package com.qingfeng;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
/**
* @author Administrator
* @version 1.0.0
* @ProjectName qingfengThymeleaf
* @Description sping下代码混淆后,不同包下的bean名相同会报bean冲突
* @createTime 2022年04月25日 22:59:00
*/
public class UniqueNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
//全限定类名
String beanName = definition.getBeanClassName();
return beanName;
}
}
@ComponentScan(nameGenerator = UniqueNameGenerator.class)
执行maven打包
jd-gui查看编译后的class文件
打包之后,查看打包后的代码