一.结构:
加密方法,通过@ControllerAdvice扫描所有接口,对含有@EncryptResponse注解的类或者方法进行加密
import com.alibaba.fastjson.JSONObject;
import com.example.util.AESOperator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* 请求响应处理类
* 对加了@Encrypt的方法的数据进行加密操作
*/
@ControllerAdvice
@Slf4j
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
//在这里调用needEncrypet方法判断是否需要加密
return new NeedCrypto().needEncrypt(returnType);
}
//这个方法截取了接口中返回的对象,在对对象加密后返回
@Override
public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
// 通过 ServerHttpRequest的实现类ServletServerHttpRequest 获得HttpServletRequest
ServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;
// 此处获取到request 是为了取到在拦截器里面设置的一个对象 是我项目需要,可以忽略
HttpServletRequest request = sshr.getServletRequest();
String returnData;
String realData = "";
JSONObject data = new JSONObject();
try {
data = (JSONObject) JSONObject.toJSON(obj);
if(!data.get("data").toString().isEmpty()){
realData = data.get("data").toString();
}
// 添加encry header,告诉前端数据已加密
serverHttpResponse.getHeaders().add("encrypt", "true");
// 加密
returnData = AESOperator.replace(AESUtil.encrypt(realData));
log.debug("接口={},原始数据={},加密后数据={}", request.getRequestURI(), realData, returnData);
data.put("data", returnData);
} catch (Exception e) {
log.error("异常!", e);
}
return data;
}
}
解密方法,通过@ControllerAdvice扫描所有接口,对含有@DecryptRequest注解的类或者方法进行解密
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* 请求数据接收处理类
* 对加了@Decrypt的方法的数据进行解密操作
*/
@ControllerAdvice
@Slf4j
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
//needDecrypt判断是否需要解密
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return new NeedCrypto().needDecrypt(methodParameter);
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
return inputMessage;
}
//拦截接口中的入参,对入参进行解密后返回
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
JSONObject dealData = new JSONObject();
try {
// 解密操作
JSONObject encryptObject = (JSONObject) JSONObject.toJSON(body);
String srcData = String.valueOf(encryptObject.get("encryptData"));
String decryptData = AESUtil.decrypt(srcData);
dealData = JSON.parseObject(decryptData);
} catch (Exception e) {
log.error("请求body参数格式解析异常!", e);
}
return dealData;
}
}
NeedCrypto类,判断是否需要进行加解密,类中是否需要加解密的逻辑可以根据需要进行修改,
目前代码中的逻辑为:注解在类上加密解密整个类,在方法是加密解密单个方法
import com.example.annotation.DecryptRequest;
import com.example.annotation.EncryptResponse;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import javax.annotation.PostConstruct;
/**
* 判断是否需要加解密
*/
@Configuration
@Log4j2
public class NeedCrypto {
//开关变量,在配置文件中配置是否开启加解密功能,不需要可以去掉
@Value("${api.encrypt}")
private boolean encryptApiFlag;
private static boolean flag = false;
@PostConstruct
public void NeedCrypto() {
flag = encryptApiFlag;
}
/**
* 是否需要对结果加密
* 1.类上标注或者方法上标注,并且都为true
* 2.有一个标注为false就不需要加密
*/
public boolean needEncrypt(MethodParameter returnType) {
boolean encrypt = false;
if (flag) {
boolean classPresentAnno = returnType.getContainingClass().isAnnotationPresent(EncryptResponse.class);
boolean methodPresentAnno = returnType.getMethod().isAnnotationPresent(EncryptResponse.class);
if (classPresentAnno) {
//类上标注的是否需要加密
encrypt = returnType.getContainingClass().getAnnotation(EncryptResponse.class).value();
if (encrypt) {
return encrypt;
}
//类不加密,所有都不加密
/**if(!encrypt){
return false;
}*/
}
if (methodPresentAnno) {
//方法上标注的是否需要加密
encrypt = returnType.getMethod().getAnnotation(EncryptResponse.class).value();
}
}
return encrypt;
}
/**
* 是否需要参数解密
* 1.类上标注或者方法上标注,并且都为true
* 2.有一个标注为false就不需要解密
*/
public boolean needDecrypt(MethodParameter parameter) {
boolean encrypt = false;
if (flag) {
boolean classPresentAnno = parameter.getContainingClass().isAnnotationPresent(DecryptRequest.class);
boolean methodPresentAnno = parameter.getMethod().isAnnotationPresent(DecryptRequest.class);
if (classPresentAnno) {
//类上标注的是否需要解密
encrypt = parameter.getContainingClass().getAnnotation(DecryptRequest.class).value();
if (encrypt) {
return encrypt;
}
//类不加密,所有都不加密
/**if (!encrypt) {
return false;
}*/
}
if (methodPresentAnno) {
//方法上标注的是否需要解密
encrypt = parameter.getMethod().getAnnotation(DecryptRequest.class).value();
}
}
return encrypt;
}
}
encryptApiFlag变量为开关变量,在配置文件中配置加解密是否生效
yml配置文件:
api:
encrypt: true
AESUtil 加解密工具类,这里可以换成自己需要的加密方法
public class AESUtil {
/**
* 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。
*/
public static final String skey = "smkldospd121daaa";
/**
* 加密偏移量
*/
private static final String ivParameter = "1016449182184177";
public static String encrypt(String srcData) throws Exception {
String enString = AESOperator.getInstance().Encrypt(srcData, skey, ivParameter);
return enString;
}
public static String decrypt(String srcData) throws Exception {
String DeString = AESOperator.getInstance().Decrypt(srcData, skey, ivParameter);
return DeString;
}
}
二.自定义注解:
添加在类或方法中判断接口是否需要加解密
@DecryptRequest (解密)
@EncryptResponse (加密)
import java.lang.annotation.*;
/**
* 解密注解
*
* 加了此注解的接口(true)将进行数据解密操作(post的body)
*
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptRequest {
/**
* 是否对body进行解密
*/
boolean value() default true;
}
import java.lang.annotation.*;
/**
* 加密注解
*
* 加了此注解的接口(true)将进行数据加密操作
*
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptResponse {
/**
* 是否对结果加密
*/
boolean value() default true;
}
三.AES加密
加解密操作类,本文使用的是AES,可以换成自己需要的加解密方式、
import org.springframework.util.StringUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* AES加解密操作类
*/
public class AESOperator {
/**
* 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。
*/
public static final String skey = "smkldospd121daaa";
/**
* 偏移量,可自行修改
*/
private static String ivParameter = "1016449182184177";
private static AESOperator instance = null;
private AESOperator() {
}
public static AESOperator getInstance() {
if (instance == null) {
instance = new AESOperator();
}
return instance;
}
public String Encrypt(String encData, String secretKey, String vector) throws Exception {
if (secretKey == null) {
return null;
}
if (secretKey.length() != 16) {
return null;
}
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] raw = secretKey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
IvParameterSpec iv = new IvParameterSpec(vector.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(encData.getBytes("utf-8"));
// 此处使用BASE64做转码。
return new BASE64Encoder().encode(encrypted);
}
/**
* 加密
*
* @param sSrc
* @param sKey
* @return
* @throws Exception
*/
public String encrypt(String sSrc, String sKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] raw = sKey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
// 此处使用BASE64做转码。
return replace(new BASE64Encoder().encode(encrypted));
}
/**
* 解密
*
* @param sSrc
* @param sKey
* @return
*/
public String decrypt(String sSrc, String sKey) {
try {
byte[] raw = sKey.getBytes("ASCII");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
// 先用base64解密
byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original, "utf-8");
return originalString;
} catch (Exception ex) {
return null;
}
}
public String Decrypt(String sSrc, String key, String ivs) {
try {
byte[] raw = key.getBytes("ASCII");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(ivs.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
// 先用base64解密
byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original, "utf-8");
return originalString;
} catch (Exception ex) {
return null;
}
}
public static String encodeBytes(byte[] bytes) {
StringBuffer strBuf = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
strBuf.append((char) (((bytes[i] >> 4) & 0xF) + ((int) 'a')));
strBuf.append((char) (((bytes[i]) & 0xF) + ((int) 'a')));
}
return strBuf.toString();
}
/**
* 去除 换行符、制表符
*
* @param str
* @return
*/
public static String replace(String str) {
if (!StringUtils.isEmpty(str)) {
return str.replaceAll("\r|\n", "");
}
return str;
}
//测试
public static void main(String[] args) throws Exception {
// 需要加密的字串
String cSrc = "{\"loginName\":\"master\",\"secret\":\"123456\"}";
System.out.println(cSrc);
// 加密
String enString = AESOperator.getInstance().Encrypt(cSrc, skey, ivParameter);
System.out.println("加密后的字串是:" + replace(enString));
String test = replace(enString);
// 解密
String DeString = AESOperator.getInstance().Decrypt(test, skey, ivParameter);
System.out.println("解密后的字串是:" + DeString);
}
}
四.实战
注解加在类或者方法上就可以使用啦(类的优先级高于方法)
解密使用时入参不可以直接写实体类,需要先用json接收,使用实体类无法存放加密后的数据
@RestController
public class TestController {
@DecryptRequest
@EncryptResponse
@PostMapping("/test")
public ApiResult test(@RequestBody JSONObject params) {
Menu menu = params.toJavaObject(Menu.class);
return menuService.addMenu(menu);
}
}