为了减少项目代码的变动,采用自定义注解的方案。 逻辑如下
(1) 选定加解密的方式,这里采用AES的加密方式,加密算法是AES/CBC/PKCS5Padding
,偏移量是0000000000000000
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AESUtils {
// 设置采用的加密算法
private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
// 设置加解密偏移量
private static final byte[] IV_PARAMETER = "0000000000000000".getBytes();
// 获取 cipher
private static Cipher getCipher(byte[] key, int model) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(model, secretKeySpec, new IvParameterSpec(IV_PARAMETER));
return cipher;
}
// AES加密
public static String encrypt(byte[] data, byte[] key) throws Exception {
Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE);
return Base64.getEncoder().encodeToString(cipher.doFinal(data));
}
// AES解密
public static byte[] decrypt(byte[] data, byte[] key) throws Exception {
String str = new String(data);
String trim = str.trim();
Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
return cipher.doFinal(Base64.getDecoder().decode(trim.getBytes()));
}
public static void main(String[] args) throws Exception {
String enStr = "";
String encrypt = encrypt(enStr.getBytes(), "1a2d3m4i5n6.far.".getBytes());
System.out.println("encrypt = " + encrypt);
}
}
(2) 设定加密采用的密钥,这里采用yml配置方式
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.encrypt")
public class EncryptProperties {
private final static String DEFAULT_KEY = "uniwinnnL$$linkcom.";
private String key = DEFAULT_KEY;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
(3) 定义用于加密和解密的注解,之后将注解与对应的加解密类进行对应
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface Decrypt {
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Encrypt {
}
(4) 定义具体的加密类和解密类
RequestBodyAdviceAdapter
类,重写beforeBodyRead
方法来拦截响应体,从而实现对数据的加密,重写supports
方法,实现加密类与注解的绑定。import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
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.io.InputStream;
import java.lang.reflect.Type;
@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class DecryptRequest extends RequestBodyAdviceAdapter {
@Autowired
EncryptProperties encryptProperties;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);
}
@Override
public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
byte[] body = StreamUtils.copyToByteArray(inputMessage.getBody());
inputMessage.getBody().read(body);
try {
byte[] decrypt = AESUtils.decrypt(body, encryptProperties.getKey().getBytes());
final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt); // 再将字节转为输入流
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
return bais; // 再将输入流返回
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
} catch (Exception e) {
e.printStackTrace();
}
return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
}
}
ResponseBodyAdvice
接口,重写beforeBodyWrite
方法,编写对应的加密逻辑import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class EncryptResponse implements ResponseBodyAdvice<HashMap<String, Object>> {
private ObjectMapper om = new ObjectMapper();
@Autowired
EncryptProperties encryptProperties;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.hasMethodAnnotation(Encrypt.class);
}
// 这里可以改造成对整个响应体都进行加密吗? 而不是只对其中的某些变量进行加密
@Override
public HashMap<String, Object> beforeBodyWrite(HashMap<String, Object> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 获取加密密钥
byte[] keyBytes = encryptProperties.getKey().getBytes();
try {
// 对parameterInfo字段对应的值加密
if (body.get("parameterInfo")!=null) {
body.put("parameterInfo",AESUtils.encrypt(om.writeValueAsBytes(body.get("parameterInfo")), keyBytes));
}
if (body.get("passInfo")!=null) {
body.put("passInfo",AESUtils.encrypt(om.writeValueAsBytes(body.get("passInfo")), keyBytes));
}
if (body.get("deleteInfo")!=null) {
body.put("deleteInfo",AESUtils.encrypt(om.writeValueAsBytes(body.get("deleteInfo")), keyBytes));
}
if (body.get("severPath")!=null) {
body.put("severPath",AESUtils.encrypt(body.get("severPath").toString().getBytes(), keyBytes));
}
} catch (Exception e) {
e.printStackTrace();
}
return body;
}
}
(5) 使用时,在对应的接口上添加@Encrypt
或者@Decrypt
注解即可
@Encrypt
@Decrypt
@ResponseBody
@RequestMapping(value = "/insertNoteFace", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public HashMap<String, Object> insertNote(@RequestBody JSONObject jsonParam,HttpServletRequest req) {
}