赞
踩
为什么需要对前后端接口加解密?主要是甲方要求。
AES是最常见的对称加密算法,加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合,且加密长文本比较方便。缺点是密钥的传输比较麻烦,只能将一把公钥分别在前后端代码中各存放一份,还有一个遇到的问题下面会讲到,那就是美国对AES加密密钥长度的限制。优点是加密效率高,缺点是安全性低。
RSA也就是非对称加密算法,是使用不同密钥进行加密和解密的算法,也称为公私钥加密。公钥和私钥是同时生成的,公钥用来加密,私钥用来解密,加解密的密钥是成对出现。但是不推荐用RSA加密请求报文,因为RSA加密后的报文会很长,经常会出现超过请求体长度限制。优点是安全性高,缺点是RSA的加密效率低。
我们可以结合两者的优点,AES的加密效率高,那我们可以使用aes来加密报文,而RSA的灵活性和安全性来加密aes的密钥。总的思路就是利用RSA来加密传输AES的密钥,用 AES的密钥来加密请求报文。
首先这个功能我们的出发点是可以灵活配置,所以我们在需要加密的请求头添加一个加密标识代码如下:
import axios from '@common/plugins/Axios'
saveStudent: data => {
return axios.request({
url: `/demo/saveStudent`,
method: 'post',
headers: {
//需要加密的请求在头部塞入标识
isEncrypt: 1
},
data
})
}
import JSEncrypt from 'jsencrypt' import CryptoJS from 'crypto-js' // // 加密 export function rsaEncrypt(Str, afterPublicKey) { const encryptor = new JSEncrypt() encryptor.setPublicKey(afterPublicKey) // 设置公钥 return encryptor.encrypt(Str) // 对数据进行加密 } // 解密 export function rsaDecrypt(Str, frontPrivateKey) { const encryptor = new JSEncrypt() encryptor.setPrivateKey(frontPrivateKey) // 设置私钥 return encryptor.decrypt(Str) // 对数据进行解密 } export function aesEncrypt(aeskey, Str) { // 设置一个默认值,如果第二个参数为空采用默认值,不为空则采用新设置的密钥 var key = CryptoJS.enc.Utf8.parse(aeskey) var srcs = CryptoJS.enc.Utf8.parse(Str) var encrypted = CryptoJS.AES.encrypt(srcs, key, { // 切记 需要和后端算法模式一致 mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) return encrypted.toString() } export function aesDecrypt(aeskey, Str) { var key = CryptoJS.enc.Utf8.parse(aeskey) var decrypt = CryptoJS.AES.decrypt(Str, key, { // 切记 需要和后端算法模式一致 mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) return CryptoJS.enc.Utf8.stringify(decrypt).toString() } /** * 获取16位随机码AES * @returns {string} */ export function get16RandomNum() { var chars = [ '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K', 'L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z' ] var nums = '' //这个地方切记要选择16位,因为美国对密钥长度有限制,选择32位的话加解密会报错,需要根据jdk版本去修改相关jar包,有点恼火,选择16位就不用处理。 for (var i = 0; i < 16; i++) { var id = parseInt(Math.random() * 61) nums += chars[id] } return nums } //获取rsa密钥对 export function getRsaKeys() { return new Promise((resolve, reject) => { window.crypto.subtle .generateKey( { name: 'RSA-OAEP', modulusLength: 2048, //can be 1024, 2048, or 4096 publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-512' } //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" }, true, //whether the key is extractable (i.e. can be used in exportKey) ['encrypt', 'decrypt'] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"] ) .then(function(key) { window.crypto.subtle .exportKey('pkcs8', key.privateKey) .then(function(keydata1) { window.crypto.subtle .exportKey('spki', key.publicKey) .then(function(keydata2) { var privateKey = RSA2text(keydata1, 1) var publicKey = RSA2text(keydata2) resolve({ privateKey, publicKey }) }) .catch(function(err) { reject(err) }) }) .catch(function(err) { reject(err) }) }) .catch(function(err) { reject(err) }) }) } function RSA2text(buffer, isPrivate = 0) { var binary = '' var bytes = new Uint8Array(buffer) var len = bytes.byteLength for (var i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]) } var base64 = window.btoa(binary) let text = base64.replace(/[^\x00-\xff]/g, '$&\x01').replace(/.{64}\x01?/g, '$&\n') return text }
import Axios from 'axios' import { getRsaKeys, rsaEncrypt, rsaDecrypt, aesDecrypt, aesEncrypt, get32RandomNum } from '@common/util' /** * axios实例 * @type {AxiosInstance} */ const instance = Axios.create({ headers: { x_requested_with: 'XMLHttpRequest' } }) let frontPrivateKey /** * axios请求过滤器 */ instance.interceptors.request.use( async config => { if (sessionStorage.getItem('X-Access-Token')) { // 判断是否存在token,如果存在的话,则每个http header都加上token config.headers['X-Access-Token'] = sessionStorage.getItem('X-Access-Token') } if (config.headers['isEncrypt']) { config.headers['Content-Type'] = 'application/json;charset=utf-8' if (config.method === 'post' || config.method === 'put') { const { privateKey, publicKey } = await getRsaKeys() let afterPublicKey = sessionStorage.getItem('afterPublicKey') frontPrivateKey = privateKey //每次请求生成aeskey let aesKey = get16RandomNum() //用登陆后后端生成并返回给前端的的RSA密钥对的公钥将AES16位密钥进行加密 let aesKeyByRsa = rsaEncrypt(aesKey, afterPublicKey) //使用AES16位的密钥将请求报文加密(使用的是加密前的aes密钥) if (config.data) { let data = aesEncrypt(aesKey, JSON.stringify(config.data)) config.data = { data: data, aeskey: aesKeyByRsa, frontPublicKey: publicKey } } if (config.params) { let data = aesEncrypt(aesKey, JSON.stringify(config.params)) config.params = { params: data, aeskey: aesKeyByRsa, frontPublicKey: publicKey } } } } config.url ="你的后端接口请求地址" + config.url return config }, err => { return Promise.reject(err) } ) /** * axios响应过滤器 */ instance.interceptors.response.use( response => { //后端返回的通过rsa加密后的aes密钥 let aesKeyByRsa = response.data.aesKeyByRsa if (aesKeyByRsa) { //通过rsa的私钥对后端返回的加密的aeskey进行解密 let aesKey = rsaDecrypt(aesKeyByRsa, frontPrivateKey) //使用解密后的aeskey对加密的返回报文进行解密 response.data.data = JSON.parse(JSON.parse(aesDecrypt(aesKey, response.data.data))) return response.data }else{ return response } } }, error => { if (error.response.status === 500) { const { data } = error.response if (data !== null && data !== undefined) { if (error.response.data.trace.includes('账号已被冻结或注销,请联系管理员!')) doLogout('账号已被冻结或注销,请联系管理员!') } if (error.response.data.trace.includes('用户不存在!')) { doLogout('用户不存在!') } if (error.response.data.trace.includes('Token失效')) { doLogout('Token失效,请重新登录!') } } else { Notification.error({ title: '提示', message: error.response.data.error }) } } else { return Promise.reject(error.response) } } ) export default instance
import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Random; /** * @ClassName AESUtils 一定要选择16位密钥长度,也就是KEY_LENGTH=16*8,36的话就需要修改环境的jar包。 * @Description TODO * @Author L * @Date 2024/1/26 17:27 */ public class AESUtils { /** * 加密算法AES */ private static final String KEY_ALGORITHM = "AES"; /** * key的长度,Wrong key size: must be equal to 128, 192 or 256 * 传入时需要16、24、36 */ private static final int KEY_LENGTH = 16 * 8; /** * 算法名称/加密模式/数据填充方式 * 默认:AES/ECB/PKCS5Padding */ private static final String ALGORITHMS = "AES/ECB/PKCS5Padding"; /** * 后端AES的key,由静态代码块赋值 */ public static String key; static { key = getKey(); } /** * 获取key */ public static String getKey() { int length = KEY_LENGTH / 8; StringBuilder uid = new StringBuilder(length); //产生32位的强随机数 Random rd = new SecureRandom(); for (int i = 0; i < length; i++) { //产生0-2的3位随机数 switch (rd.nextInt(3)) { case 0: //0-9的随机数 uid.append(rd.nextInt(10)); break; case 1: //ASCII在65-90之间为大写,获取大写随机 uid.append((char) (rd.nextInt(26) + 65)); break; case 2: //ASCII在97-122之间为小写,获取小写随机 uid.append((char) (rd.nextInt(26) + 97)); break; default: break; } } return uid.toString(); } /** * AES 加密 * * @param content 加密的字符串 * @param encryptKey key值 */ public static String encrypt(String content, String encryptKey) throws Exception { //设置Cipher对象 Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), KEY_ALGORITHM)); //调用doFinal // 转base64 return Base64.encodeBase64String(cipher.doFinal(content.getBytes(StandardCharsets.UTF_8))); } /** * AES 解密 * * @param encryptStr 解密的字符串 * @param decryptKey 解密的key值 */ public static String decrypt(String encryptStr, String decryptKey) throws Exception { //base64格式的key字符串转byte byte[] decodeBase64 = Base64.decodeBase64(encryptStr); //设置Cipher对象 Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), KEY_ALGORITHM)); //调用doFinal解密 return new String(cipher.doFinal(decodeBase64)); } }
import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import java.io.ByteArrayOutputStream; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.LinkedHashMap; import java.util.Map; /** * @ClassName RSAUtils * @Description TODO * @Author L * @Date 2024/1/26 17:28 */ public class RSAUtils { /** * 加密算法RSA */ private static final String KEY_ALGORITHM = "RSA"; /** * 算法名称/加密模式/数据填充方式 * 默认:RSA/ECB/PKCS1Padding */ private static final String ALGORITHMS = "RSA/ECB/PKCS1Padding"; /** * RSA最大加密明文大小 */ private static final int MAX_ENCRYPT_BLOCK = 245; /** * RSA最大解密密文大小 */ private static final int MAX_DECRYPT_BLOCK = 256; /** * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写: 245 256 */ private static final int INITIALIZE_LENGTH = 2048; /** * 后端RSA的密钥对(公钥和私钥)Map,由静态代码块赋值 */ private static final Map<String, String> map = new LinkedHashMap<>(2); /** * 生成密钥对(公钥和私钥) */ public static Map<String,String> genKeyPair() throws Exception { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); keyPairGen.initialize(INITIALIZE_LENGTH); KeyPair keyPair = keyPairGen.generateKeyPair(); // 获取公钥 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 获取私钥 RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到公钥字符串 String publicKeyString = Base64.encodeBase64String(publicKey.getEncoded()); // 得到私钥字符串 String privateKeyString = Base64.encodeBase64String((privateKey.getEncoded())); map.put("publicKey",publicKeyString); map.put("privateKey",privateKeyString); return map; } public static String getPrivateKey(){ return map.get("privateKey"); } public static String getPublicKey(){ return map.get("publicKey"); } /** * RSA私钥解密 * @param data BASE64编码过的密文 * @param privateKey 私钥(BASE64编码) * @return utf-8编码的明文 */ public static byte[] decryptByPrivateKey(byte[] data, String privateKey) throws Exception { //base64格式的key字符串转Key对象 Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey))); Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.DECRYPT_MODE, privateK); //分段进行解密操作 return encryptAndDecryptOfSubsection(data, cipher, MAX_DECRYPT_BLOCK); } /** * RSA公钥加密 * @param data BASE64编码过的密文 * @param publicKey 公钥(BASE64编码) * @return utf-8编码的明文 */ public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception { //base64格式的key字符串转Key对象 Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey))); Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.ENCRYPT_MODE, publicK); //分段进行加密操作 return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK); } /** * RSA公钥解密 * @param data BASE64编码过的密文 * @param publicKey RSA公钥 * @return utf-8编码的明文 */ public static byte[] pubKeyDec(byte[] data, String publicKey) throws Exception { //base64格式的key字符串转Key对象 Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey))); Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.DECRYPT_MODE, privateK); //分段进行解密操作 return encryptAndDecryptOfSubsection(data, cipher, MAX_DECRYPT_BLOCK); } /** * RSA私钥加密 * @param data 待加密的明文 * @param privateKey RSA私钥 * @return 经BASE64编码后的密文 */ public static byte[] privKeyEnc(byte[] data, String privateKey) throws Exception { //base64格式的key字符串转Key对象 Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey))); Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.ENCRYPT_MODE, publicK); //分段进行加密操作 return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK); } /** * 分段进行加密、解密操作 */ private static byte[] encryptAndDecryptOfSubsection(byte[] data, Cipher cipher, int encryptBlock) throws Exception { int inputLen = data.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // 对数据分段加密 while (inputLen - offSet > 0) { if (inputLen - offSet > encryptBlock) { cache = cipher.doFinal(data, offSet, encryptBlock); } else { cache = cipher.doFinal(data, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * encryptBlock; } out.close(); return out.toByteArray(); } }
import org.apache.commons.codec.binary.Base64; /** * @ClassName ApiSecurityUtils * @Description TODO * @Author L * @Date 2024/1/26 17:39 */ public class ApiSecurityUtils { /** * * @param aesKeyByRsa 经过rsa加密的aeskey * @param decryptStr 经过aes加密的数据 * @return 解密后的数据 */ public static String decrypt(String aesKeyByRsa,String decryptStr,String privateKey) throws Exception { byte[] bytes = RSAUtils.decryptByPrivateKey(Base64.decodeBase64(aesKeyByRsa), privateKey); String aesKey = new String(bytes); return AESUtils.decrypt(decryptStr, aesKey); } /** * * @param encryptStr 要加密的数据 * @param frontPublicKey 前端公钥 * @return 加密后的数据 */ public static ApiEncryptRes encrypt(String encryptStr, String frontPublicKey) throws Exception { String aesKey = AESUtils.getKey(); String data = AESUtils.encrypt(encryptStr, aesKey); ApiEncryptRes apiEncryptRes = new ApiEncryptRes(); String aesKeyByRsa = Base64.encodeBase64String(RSAUtils.encryptByPublicKey(aesKey.getBytes(), frontPublicKey)); apiEncryptRes.setAesKeyByRsa(aesKeyByRsa); apiEncryptRes.setData(data); return apiEncryptRes; } }
import java.security.MessageDigest; public class MD5Util { //十六进制下数字到字符的映射数组 private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; /** * 把inputString加密 */ public static String md5(String inputString) { return encodeByMD5(inputString); } /** * 对字符串进行MD5加密 */ private static String encodeByMD5(String originString) { if (originString != null) { try { //创建具有指定算法名称的信息摘要 MessageDigest md = MessageDigest.getInstance("MD5"); //使用指定的字节数组对摘要进行最后更新,然后完成摘要计算 byte[] results = md.digest(originString.getBytes("utf-8")); //将得到的字节数组变成字符串返回 String resultString = byteArrayToHexString(results); return resultString.toUpperCase(); } catch (Exception ex) { ex.printStackTrace(); } } return null; } /** * 转换字节数组为十六进制字符串 * * @param * @return 十六进制字符串 */ private static String byteArrayToHexString(byte[] b) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) { resultSb.append(byteToHexString(b[i])); } return resultSb.toString(); } private static String byteToHexString(byte b) { int n = b; if (n < 0) { n += 256; } int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } }
import lombok.Data; /** * @ClassName ApiEncryptRes 用于返回前端解密返回体的aeskey和返回体 * @Description TODO * @Author L * @Date 2024/1/26 17:51 */ @Data public class ApiEncryptRes { private String aesKeyByRsa; private String data; private String frontPublicKey; }
import cn.hutool.core.convert.Convert; import cn.hutool.extra.spring.SpringUtil; import com.alibaba.fastjson.JSONObject; import com.l.common.encryptAPI.utils.ApiSecurityUtils; import com.l.common.encryptAPI.util.MD5Util; import com.l.common.encryptAPI.util.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import cn.hutool.core.io.FastByteArrayOutputStream; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; import cn.hutool.http.ContentType; import lombok.SneakyThrows; import org.apache.shiro.SecurityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.util.WebUtils; import java.util.TreeMap; /** * @ClassName requestWrapper * @Description TODO * @Author L * @Date 2024/1/31 17:58 */ /** * 确保request可以多次读取 */ @Slf4j public class EncryptRequestWrapper extends HttpServletRequestWrapper { //2024-02-02由于在过滤器中无法直接通过Autowired获取Bean,因此需要通过spring上下文来获取IOC管理的实体类 // @Autowired // private RedisUtil redisUtil; protected FastByteArrayOutputStream cachedContent; protected String JSPublicKey; protected EncryptRequestWrapper(HttpServletRequest request ) { super(request); RedisUtil redisUtils =getBean(RedisUtil.class, request); this.copyBody(redisUtils); } /** * * 此处主要是用于读取 RedisUtil * @param clazz * @param request * @param <T> * @return */ public <T> T getBean(Class<T> clazz, HttpServletRequest request){ WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext()); return applicationContext.getBean(clazz); } public static EncryptRequestWrapper newOrGetMultiReadHttpServletRequest(HttpServletRequest request) { EncryptRequestWrapper multiReadHttpServletRequest = getMultiReadHttpServletRequest(request); if (null != multiReadHttpServletRequest) { return multiReadHttpServletRequest; } else { String contentType = request.getContentType(); if (contentType != null && contentType.contains(ContentType.MULTIPART.getValue())) { // 将转化后的 request 放入过滤链中 request = new StandardServletMultipartResolver().resolveMultipart(request); } return new EncryptRequestWrapper(request); } } public static EncryptRequestWrapper getMultiReadHttpServletRequest(HttpServletRequest request) { EncryptRequestWrapper nativeRequest = WebUtils.getNativeRequest(request, EncryptRequestWrapper.class); if (null != nativeRequest) { return nativeRequest; } else if (request instanceof EncryptRequestWrapper) { return (EncryptRequestWrapper) request; } else { return null; } } //重新读取请求,因为request只可以被读取一次,需要重新设置为可多次读取 @SneakyThrows protected void copyBody(RedisUtil redisUtils) { int length = this.getContentLength(); if (length > 0) { cachedContent = IoUtil.read(getRequest().getInputStream()); if (StringUtils.isNotBlank(this.getHeader("Isencrypt"))&&StringUtils.isNotBlank(this.getHeader("X-Access-Token"))) { String body = new String(cachedContent.toByteArray()); log.info("------------------- body = " + body + "------------------------"); if (StringUtils.isNotBlank(body)) { JSONObject jsonBody = JSONObject.parseObject(body); if(null != jsonBody){ String dataEncrypt = jsonBody.getString("data"); String aeskey = jsonBody.getString("aeskey"); JSPublicKey = jsonBody.getString("frontPublicKey"); String data; JSONObject json = null; log.info("------------------- dataEncrypt = " + dataEncrypt + "------------------------"); log.info("------------------- aesKey = " + aeskey + "------------------------"); String token = this.getHeader("X-Access-Token"); String md5Token = MD5Util.md5(token); String privateKey = Convert.toStr(redisUtils.get(md5Token + "privateKey")); data = ApiSecurityUtils.decrypt(aeskey, dataEncrypt,privateKey); // 如果数据不为空就编译 if (StringUtils.isNotBlank(data)) { //如果参数为空前端传回undefined if ("undefined".equalsIgnoreCase(data)) { json = new JSONObject(); } else { json = JSONObject.parseObject(data); } } if (json != null) { body = json.toJSONString(); } } log.info("------------------- body = " + body + "------------------------"); cachedContent.reset(); cachedContent.write( body.getBytes(), 0, body.getBytes().length); } } } else { cachedContent = new FastByteArrayOutputStream(); } } @Override public int getContentLength() { if (null != cachedContent) { return cachedContent.size(); } return super.getContentLength(); } public byte[] getByteArrayBody() { return cachedContent.toByteArray(); } public String getBody() { String body = null; if (cachedContent != null && cachedContent.size() > 0) { body = new String(cachedContent.toByteArray()); } return body; } public String getSortBody() { return JSONObject.toJSONString(this.getBody(TreeMap.class)); } public <T> T getBody(Class<T> type) { return cn.hutool.json.JSONUtil.toBean(this.getBody(), type); } //这个方法为获取前端给后端用于加密aeskey的rsa公钥 public String getJSPublicKey(){ return JSPublicKey; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { if (this.getContentLength() < 1) { return super.getInputStream(); } return new ResettableServletInputStream(new ByteArrayInputStream(cachedContent.toByteArray())); } private class ResettableServletInputStream extends ServletInputStream { private final InputStream sourceStream; private boolean finished = false; /** * Create a DelegatingServletInputStream for the given source stream. * * @param sourceStream the source stream (never {@code null}) */ public ResettableServletInputStream(InputStream sourceStream) { Assert.notNull(sourceStream, "Source InputStream must not be null"); this.sourceStream = sourceStream; } /** * Return the underlying source stream (never {@code null}). */ public final InputStream getSourceStream() { return this.sourceStream; } @Override public int read() throws IOException { int data = this.sourceStream.read(); if (data == -1) { this.finished = true; } return data; } @Override public int available() throws IOException { return this.sourceStream.available(); } @Override public void close() throws IOException { super.close(); this.sourceStream.close(); } @Override public boolean isFinished() { return finished; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { } } }
import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; /** * 返回值输出代理类 * @ClassName ResponseWrapper * @Description TODO * @Author L * @Date 2024/1/31 18:58 */ @Slf4j public class ResponseWrapper extends HttpServletResponseWrapper { ByteArrayOutputStream _stream = new ByteArrayOutputStream(); PrintWriter _pw = new PrintWriter(_stream); public ResponseWrapper(HttpServletResponse resp) throws IOException { super(resp); } /** * 覆盖getWriter()方法,将字符流缓冲到本地 */ @Override public PrintWriter getWriter() throws IOException { return _pw; } /** * 覆盖getOutputStream()方法,将字节流缓冲到本地 */ @Override public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { @Override public boolean isReady() { return false; } @Override public void setWriteListener(WriteListener writeListener) { } @Override public void write(int b) throws IOException { _stream.write(b); } }; } /** * 把缓冲区内容写入输出流后关闭 * * @author xxj */ public void flush() { try { _pw.flush(); _pw.close(); _stream.flush(); _stream.close(); } catch (IOException e) { log.error("", e); } } /** * 获取字节流 * * @return */ public ByteArrayOutputStream getByteArrayOutputStream() { return _stream; } /** * 将换出区内容转为文本输出 * * @return */ public String getTextContent() { flush(); return _stream.toString(); } public static boolean isTextContentType(String contentType) { boolean flag = false; if (StrUtil.isBlank(contentType)) { return false; } else { flag = StrUtil.startWithIgnoreCase(contentType, "text"); if (!flag) { flag = isJsonContentType(contentType); } } return flag; } public static boolean isJsonContentType(String contentType) { return !StrUtil.isBlank(contentType) && (StrUtil.containsIgnoreCase(contentType, "application/problem+json") || StrUtil.containsIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE)); } }
import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.l.common.encryptAPI.pojo.ApiEncryptRes; import com.l.common.encryptAPI.utils.ApiSecurityUtils; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.boot.web.servlet.filter.OrderedFilter; import org.springframework.stereotype.Component; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.HashMap; @Slf4j @Component public class EncryptApiFilter extends OncePerRequestFilter implements OrderedFilter { public static final int DEFAULT_ORDER = Integer.MAX_VALUE; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String servletPath = request.getServletPath(); ResponseWrapper wrapper = new ResponseWrapper(response); String requestContent = null; if (ServletUtil.isMultipart(request)) { } else { MultiReadHttpServletRequestWrapper multiReadHttpServletRequestWrapper = MultiReadHttpServletRequestWrapper.newOrGetMultiReadHttpServletRequest(request); requestContent = multiReadHttpServletRequestWrapper.getBody(); request = multiReadHttpServletRequestWrapper; } log.debug("{}>>>{}", servletPath, requestContent); try { filterChain.doFilter(request, wrapper); } finally { try { process(request, wrapper, response); } catch (Exception e) { log.error("AesFilter error", e); } } } @SneakyThrows protected void process(HttpServletRequest request, ResponseWrapper responseWrapper, HttpServletResponse response) { String servletPath = request.getRequestURI(); String isencrypt = request.getHeader("Isencrypt"); ServletOutputStream out = response.getOutputStream(); if (ResponseWrapper.isJsonContentType(responseWrapper.getContentType())) { String responseContent = responseWrapper.getTextContent(); if (StrUtil.isNotEmpty(responseContent)&&StringUtils.isNotBlank(isencrypt)) { String JSPublicKey = ((MultiReadHttpServletRequestWrapper)request).getJSPublicKey(); if (StringUtils.isNotBlank(JSPublicKey)) { ApiEncryptRes apiEncryptRes = ApiSecurityUtils.encrypt(JSON.toJSONString(responseContent), JSPublicKey); responseContent = JSONUtil.toJsonStr(apiEncryptRes); } log.info("{}<<<{}", servletPath, responseContent); byte[] bytes = responseContent.getBytes(StandardCharsets.UTF_8); response.setContentLength(bytes.length); out.write(bytes); out.flush(); out.close(); } else { responseWrapper.getByteArrayOutputStream().writeTo(out); out.flush(); out.close(); } } else { responseWrapper.getByteArrayOutputStream().writeTo(out); out.flush(); out.close(); } } @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); } @Override public int getOrder() { return DEFAULT_ORDER; } }
import com.l.common.encryptAPI.filter.EncryptApiFilter; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @ClassName encryConfig 将加密的过滤器注册,一定要记得,2024-01-01踩坑 * @Description TODO * @Author L * @Date 2024/2/1 15:31 */ @Configuration public class EncryConfig { @Bean @ConditionalOnWebApplication public EncryptApiFilter encryptApiFilter(ApplicationContext applicationContext) { return new EncryptApiFilter(); } }
import cn.hutool.core.convert.Convert; import com.baomidou.mybatisplus.extension.api.R; import com.l.common.encryptAPI.utils.RSAUtils; import com.l.common.encryptAPI.util.MD5Util; import com.l.common.encryptAPI.util.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.apache.shiro.SecurityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.Map; /** * @ClassName EncryptController * @Description TODO * @Author L * @Date 2024/1/29 9:18 */ @RestController @RequestMapping("/encrypt") @Slf4j public class EncryptController { @Autowired private RedisUtil redisUtil; @GetMapping("/getPublicKey") public R<?> getPublicKey(HttpServletRequest request) throws Exception { //获取当前登陆账号对应的token,这行代码就不贴了。 String publicKey=""; if (StringUtils.isNotBlank(token)) { Map<String, String> stringStringMap = RSAUtils.genKeyPair(); publicKey = stringStringMap.get("publicKey"); String privateKey = stringStringMap.get("privateKey"); String md5Token = MD5Util.md5(token); //这个地方的存放时间根据你的token存放时间走 redisUtil.set(md5Token + "publicKey", publicKey); redisUtil.set(md5Token + "privateKey", privateKey); return R.ok(publicKey); } return R.ok(publicKey); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。