参考:
链接: https://www.cnblogs.com/throwable/p/9471938.html.
链接: https://www.cnblogs.com/whcghost/p/5657594.html.
链接: https://blog.csdn.net/charry0110/article/details/109495035.
链接: https://www.cnblogs.com/elaron/archive/2013/04/09/3010375.html.
链接: https://blog.csdn.net/qq_35164962/article/details/102704880.
主要是为了实现对app的请求的参数和响应进行加密,本次只做了json格式参数的post请求的加解密。主要考虑到get参数加密后太长会超过url长度限制。上传下载的请求也没有加解密,主要担心加解密时间太长。
除了对参数和响应内容进行加密外,还进行了时间戳的过期检查操作和签名验证的操作,来防止伪造请求访问。但是只对时间戳拼接一个头的字符串进行签名,并没有使用参数内容进行签名,想想只要破解不了我的加密方式,我用时间戳来签名应该就够了。
json请求接收器
package com.wx.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wx.exception.BadRequestException;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonInputMessage;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Date;
/**
*
*/
//@ControllerAdvice(basePackages = "com.wx.*.controller")
@ControllerAdvice
public class CustomRequestBodyAdvice extends RequestBodyAdviceAdapter {
private final ObjectMapper objectMapper;
public CustomRequestBodyAdvice(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class extends HttpMessageConverter>> converterType) {
return RsaProperties.enSwitch;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class extends HttpMessageConverter>> converterType) throws IOException {
String content = StreamUtils.copyToString(inputMessage.getBody(), Charset.forName("UTF-8"));
EncryptModel in = objectMapper.readValue(content, EncryptModel.class);
//先校验一下时间戳是否过期,如果时间戳和后台当前时间相差超过5分钟,则请求判定为失效
if (Math.abs(in.getTimestamp() - new Date().getTime()) > RsaProperties.deadlineMins * 1000 * 60) {
throw new BadRequestException("请求已过期!");
}
String inRawSign = in.getTimestamp() + "";
String inSign = null;
try {
inSign = ShaUtils.SINGLETON.sha(inRawSign);
} catch (Exception e) {
throw new IllegalArgumentException("验证参数签名失败!");
}
if (!inSign.equals(in.getSign())) {
throw new IllegalArgumentException("验证参数签名失败!");
}
String decryptedData = null;
try {
decryptedData = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, in.getData());
} catch (Exception e) {
throw new IllegalArgumentException("参数解密失败:" + in.getData(), e);
}
ByteArrayInputStream inputStream = new ByteArrayInputStream(URLDecoder.decode(decryptedData, "UTF-8").getBytes(Charset.forName("UTF-8")));
return new MappingJacksonInputMessage(inputStream, inputMessage.getHeaders());
}
}
响应解析器
package com.wx.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
import java.net.URLEncoder;
/**
*
*/
@ControllerAdvice
public class CustomResponseBodyAdvice extends JsonViewResponseBodyAdvice {
private final ObjectMapper objectMapper;
public CustomResponseBodyAdvice(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {
return RsaProperties.enSwitch;
}
@Override
protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request,
ServerHttpResponse response) {
if (request.getMethod() == HttpMethod.POST) {
EncryptModel out = new EncryptModel();
out.setTimestamp(System.currentTimeMillis());
String decryptedData = null;
try {
final String jsonStr = objectMapper.writeValueAsString(bodyContainer.getValue());
decryptedData = RsaUtils.encryptByPublicKey(RsaProperties.publicKey, URLEncoder.encode(jsonStr, "UTF-8"));
out.setData(decryptedData);
} catch (Exception e) {
throw new IllegalArgumentException("返回对象加密失败:" + bodyContainer.toString(), e);
}
String rawSign = out.getTimestamp() + "";
try {
out.setSign(ShaUtils.SINGLETON.sha(rawSign));
} catch (Exception e) {
throw new IllegalArgumentException("返回对象签名失败:" + rawSign);
}
bodyContainer.setValue(out);
}
}
}
加密后接收对象
package com.wx.security;
/**
* @Author: 遛猫达人
* @Description: TODO
* @DateTime: 2021/8/9 11:44
**/
public class EncryptModel {
private String data;
private Long timestamp;
private String sign;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
}
签名工具:
package com.wx.security;
import org.apache.commons.codec.binary.Hex;
import java.security.MessageDigest;
public enum ShaUtils {
/**
* SINGLETON
*/
SINGLETON;
private static final String SECRET = "xxx";
private static final String CHARSET = "UTF-8";
/**
* @param raw
* @return
* @throws Exception
*/
public String sha(String raw) throws Exception {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update((SECRET + raw).getBytes(CHARSET));
return Hex.encodeHexString(messageDigest.digest());
}
}
前端加密
post(api, data, options) {
var newData = {};
if(store.state.enSwitch){
let jsonData = JSON.stringify(data);
// console.log(jsonData)
newData.timestamp = new Date().getTime();
let shaContent = sha_prefix + newData.timestamp;
newData.sign =sha256(shaContent);
newData.data = encrypt(encodeURIComponent(jsonData));
}else{
newData = data
}
return this.getRequest(api, newData, options, 'POST')
},
前端解密
//解密post请求响应
if(store.state.enSwitch && method === 'POST'){
let sign =sha256(sha_prefix + res.data.timestamp);
if(sign !== res.data.sign){
reject(res)
uni.showToast({
icon: 'none',
title: "签名校验不通过"
})
return false
}
let resData = decrypt(res.data.data);
// console.log(api+":"+decodeURIComponent(resData))
res.data = JSON.parse(decodeURIComponent(resData))
}
注意:使用前端加解密工具前先要执行以下命令安装相关依赖包:
npm install jsencrypt
npm install js-sha256
npm i encryptlong -S
前端加解密工具:
import JSEncrypt from 'encryptlong'
const publicKey = 'xxx'
const privateKey = 'yyyy'
// 加密
export function encrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey) // 设置公钥,注意此处不要再设置私钥
return encryptor.encryptLong(txt) // 对需要加密的数据进行加密
}
// 解密
export function decrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(privateKey)// 设置私钥,注意此处不要再设置公钥
return encryptor.decryptLong(txt)
}
后端加解密工具:
package com.wx.security;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @author
* @description Rsa 工具类,公钥私钥生成,加解密
* @date
**/
public class RsaUtils {
private static final String SRC = "1123456";
public static final String RSA = "RSA";
/** */
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/** */
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
public static final String CHARSET_NAME = "UTF-8";
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
System.out.println("\n");
RsaKeyPair keyPair = generateKeyPair();
System.out.println("公钥:" + keyPair.getPublicKey());
System.out.println("私钥:" + keyPair.getPrivateKey());
System.out.println("\n");
test1(keyPair);
System.out.println("\n");
}
/**
* 公钥加密私钥解密
*/
private static void test1(RsaKeyPair keyPair) throws Exception {
System.out.println("***************** 公钥加密私钥解密开始 *****************");
String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
System.out.println("加密前:" + RsaUtils.SRC);
System.out.println("加密后:" + text1);
System.out.println("解密后:" + text2);
if (RsaUtils.SRC.equals(text2)) {
System.out.println("解密字符串和原始字符串一致,解密成功");
} else {
System.out.println("解密字符串和原始字符串不一致,解密失败");
}
System.out.println("***************** 公钥加密私钥解密结束 *****************");
}
/**
* 私钥解密
*
* @param privateKeyText 私钥
* @param text 待解密的文本
* @return /
* @throws Exception /
*/
public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
byte[] keyBytes = Base64.decodeBase64(privateKeyText);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateK);
byte[] encryptedData = Base64.decodeBase64(text);
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 new String(decryptedData, CHARSET_NAME);
}
/**
*
* 公钥加密
*
*
* @param str 源数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
public static String encryptByPublicKey(String publicKey, String str)
throws Exception {
byte[] data = str.getBytes(CHARSET_NAME);
byte[] keyBytes = Base64.decodeBase64(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
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 Base64.encodeBase64String(encryptedData);
}
/**
* 构建RSA密钥对
*
* @return /
* @throws NoSuchAlgorithmException /
*/
public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
return new RsaKeyPair(publicKeyString, privateKeyString);
}
/**
* RSA密钥对对象
*/
public static class RsaKeyPair {
private final String publicKey;
private final String privateKey;
public RsaKeyPair(String publicKey, String privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public String getPublicKey() {
return publicKey;
}
public String getPrivateKey() {
return privateKey;
}
}
}