1、参数加密采用的是RSA加密方式
(1)前期准备
自定义RSA加密解密注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Decrypt{
/**
* 请求参数一定要是加密内容
*/
boolean required() default false;
/**
* 请求数据时间戳校验时间差
* 超过(当前时间-指定时间)的数据认定为伪造
* 注意应用程序需要捕获 {@link EncryptRequestException} 异常
*/
long timeout() default 300000;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypt{
}
(2)在接口上分别加上注解
package cn.shuibo.controller;
import cn.shuibo.annotation.Decrypt;
import cn.shuibo.annotation.Encrypt;
import cn.shuibo.domain.TestBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class DemoController {
@Encrypt
@GetMapping("/encryption")
public TestBean encryption(){
TestBean testBean = new TestBean();
testBean.setName("shuibo.cn");
testBean.setAge(18);
long time = System.currentTimeMillis();
testBean.setTimestamp(time);
return testBean;
}
@Decrypt
@PostMapping("/decryption")
public String Decryption(@RequestBody TestBean testBean){
return testBean.toString();
}
}
(3)其次需要认识3个类
- ResponseBodyAdvice、RequestBodyAdvice、HttpInputMessage
ResponseBodyAdvice
- 主要作用进行:出参进行加密操作
代码如下:
package cn.shuibo.advice;
import cn.shuibo.annotation.Encrypt;
import cn.shuibo.config.SecretKeyConfig;
import cn.shuibo.util.Base64Util;
import cn.shuibo.util.JsonUtils;
import cn.shuibo.util.RSAUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* Author:Bobby
* DateTime:2019/4/9
* 出参进行加密操作
**/
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private Logger log = LoggerFactory.getLogger(this.getClass());
private boolean encrypt;
@Autowired
private SecretKeyConfig secretKeyConfig;
private static ThreadLocal<Boolean> encryptLocal = new ThreadLocal<>();
/**判断该控制类接口是否注入加密解密注解*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
Method method = returnType.getMethod();
if (Objects.isNull(method)) {
return encrypt;
}
encrypt = method.isAnnotationPresent(Encrypt.class) && secretKeyConfig.isOpen();
return encrypt;
}
/**进行出参加密操作*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// EncryptResponseBodyAdvice.setEncryptStatus(false);
// Dynamic Settings Not Encrypted
Boolean status = encryptLocal.get();
if (null != status && !status) {
encryptLocal.remove();
return body;
}
if (encrypt) {
String publicKey = secretKeyConfig.getPublicKey();
try {
String content = JsonUtils.writeValueAsString(body);
if (!StringUtils.hasText(publicKey)) {
throw new NullPointerException("Please configure rsa.encrypt.privatekeyc parameter!");
}
byte[] data = content.getBytes();
byte[] encodedData = RSAUtil.encrypt(data, publicKey);
String result = Base64Util.encode(encodedData);
if(secretKeyConfig.isShowLog()) {
log.info("Pre-encrypted data:{},After encryption:{}", content, result);
}
return result;
} catch (Exception e) {
log.error("Encrypted data exception", e);
}
}
return body;
}
}
RequestBodyAdvice
- 主要作用进行入参是解密操作
代码如下:
package cn.shuibo.advice;
import cn.shuibo.annotation.Decrypt;
import cn.shuibo.config.SecretKeyConfig;
import cn.shuibo.exception.EncryptRequestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Objects;
/**
* DateTime:2019/4/9
* 拦截入参 解密
**/
@ControllerAdvice
public class EncryptRequestBodyAdvice implements RequestBodyAdvice {
private Logger log = LoggerFactory.getLogger(this.getClass());
private boolean encrypt;
private Decrypt decryptAnnotation;
@Autowired
private SecretKeyConfig secretKeyConfig;
//判断解密方法是否有解密注解
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
Method method = methodParameter.getMethod();
if (Objects.isNull(method)) {
encrypt = false;
return false;
}
if (method.isAnnotationPresent(Decrypt.class) && secretKeyConfig.isOpen()) {
encrypt = true;
decryptAnnotation = methodParameter.getMethodAnnotation(Decrypt.class);
return true;
}
// 此处如果按照原逻辑直接返回encrypt, 会造成一次修改为true之后, 后续请求都会变成true, 在不支持时, 需要做修正
encrypt = false;
return false;
}
@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){
if (encrypt) {
try {
//这一步很重要
return new DecryptHttpInputMessage(inputMessage, secretKeyConfig, decryptAnnotation);
} catch (EncryptRequestException e) {
throw e;
} catch (Exception e) {
log.error("Decryption failed", e);
}
}
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
HttpInputMessage
打完断点知道,由RequestBodyAdvice 初始化后进入HttpInputMessage进行解密操作
package cn.shuibo.advice;
import cn.shuibo.annotation.Decrypt;
import cn.shuibo.config.SecretKeyConfig;
import cn.shuibo.exception.EncryptRequestException;
import cn.shuibo.util.Base64Util;
import cn.shuibo.util.JsonUtils;
import cn.shuibo.util.RSAUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
/**
* DateTime:2019/4/9
**/
public class DecryptHttpInputMessage implements HttpInputMessage {
private Logger log = LoggerFactory.getLogger(this.getClass());
private HttpHeaders headers;
private InputStream body;
public DecryptHttpInputMessage(HttpInputMessage inputMessage, SecretKeyConfig secretKeyConfig, Decrypt decrypt) throws Exception {
String privateKey = secretKeyConfig.getPrivateKey();
String charset = secretKeyConfig.getCharset();
boolean showLog = secretKeyConfig.isShowLog();
boolean timestampCheck = secretKeyConfig.isTimestampCheck();
if (StringUtils.isEmpty(privateKey)) {
throw new IllegalArgumentException("privateKey is null");
}
this.headers = inputMessage.getHeaders();
String content = new BufferedReader(new InputStreamReader(inputMessage.getBody()))
.lines().collect(Collectors.joining(System.lineSeparator()));
String decryptBody;
// 未加密内容
if (content.startsWith("{")) {
// 必须加密
if (decrypt.required()) {
log.error("not support unencrypted content:{}", content);
throw new EncryptRequestException("not support unencrypted content");
}
log.info("Unencrypted without decryption:{}", content);
decryptBody = content;
}
//加密的内容
else {
StringBuilder json = new StringBuilder();
content = content.replaceAll(" ", "+");
if (!StringUtils.isEmpty(content)) {
String[] contents = content.split("\\|");
for (String value : contents) {
value = new String(RSAUtil.decrypt(Base64Util.decode(value), privateKey), charset);
json.append(value);
}
}
decryptBody = json.toString();
if(showLog) {
log.info("Encrypted data received:{},After decryption:{}", content, decryptBody);
}
}
// 开启时间戳检查
if (timestampCheck) {
// 容忍最小请求时间 decrypt.timeout()可在解密的注释里面设置 可以防止dos攻击
long toleranceTime = System.currentTimeMillis() - decrypt.timeout();
long requestTime = JsonUtils.getNode(decryptBody, "timestamp").asLong();
// 如果请求时间小于最小容忍请求时间, 判定为超时
if (requestTime < toleranceTime) {
log.error("Encryption request has timed out, toleranceTime:{}, requestTime:{}, After decryption:{}", toleranceTime, requestTime, decryptBody);
throw new EncryptRequestException("request timeout");
}
}
this.body = new ByteArrayInputStream(decryptBody.getBytes());
}
@Override
public InputStream getBody(){
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
配置类如下:
运行
加密操作
解密操作
码云地址:https://gitee.com/yuisuiWork/spring_project_demo/
欢迎交流,一起进步!
代码可私信找我要,互相学习,别忘记三连哦