模擬項目中使用RequestBodyAdvice對前端傳入的數據進行解密(入參),請求成功之後使用ResponseBodyAdvice對返回值進行加密處理
注意點:分別需要實現接口
RequestBodyAdvice 和 ResponseBodyAdvice,需要配合註解@ControllerAdvice使用
特別需要注意的是,針對RequestBodyAdvice僅作用在請求參數有註解@RequestBody的,同樣的ResponseBodyAdvice僅對有註解@ResponseBody生效
下面的代碼演示:
新建requestBody和responseBody攔截類:
package com.wm.mi.config;
import com.wm.mi.exception.MyException;
import com.wm.mi.util.HttpInputMessageUtil;
import com.wm.mi.util.Md5Tool;
import com.wm.mi.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.List;
/***
* @ClassName: DecodeRequestBody
* @Description: 請求解密 注意:RequestBodyAdvice僅針對打上@RequestBody的註解生效
* @Author: wm_yu
* @Create_time: 14:43 2019-11-11
*/
@Slf4j
@Component
@ControllerAdvice("com.wm.mi.controller")
public class DecodeRequestBody implements RequestBodyAdvice {
@Value("${enctry.secret}")
private String SECRET;
private static final String SECRET_KEY = "SECRET_KEY";
/**
* 設置條件,這個條件爲true纔會執行下面的beforeBodyRead方法
* @param methodParameter
* @param type
* @param aClass
* @return
*/
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
List<String> headerList = request.getHeaders().get(SECRET_KEY);
if(CollectionUtils.isEmpty(headerList) || StringUtils.isEmpty(headerList.get(0)) || !SECRET.equals(headerList.get(0))){
throw new MyException("request header no access key....");
}
InputStream inputStream = request.getBody();
String requestBodyStr = RequestUtil.getRequestBodyStr(inputStream);
log.info("start decode request body:{}",requestBodyStr);
if(StringUtils.isEmpty(requestBodyStr)){
return request;
}
String decodeStr = null;
try {
decodeStr = Md5Tool.md5Decode(requestBodyStr, SECRET);
log.info("end decode request body:{}",decodeStr);
} catch (Exception e) {
throw new MyException("decode request body exception....");
}
return HttpInputMessageUtil.builder().headers(request.getHeaders()).body(new ByteArrayInputStream(decodeStr.getBytes("UTF-8"))).build();
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
}
/**
* 傳入的json是空值的時候,進入這個方法
* @param o
* @param httpInputMessage
* @param methodParameter
* @param type
* @param aClass
* @return
*/
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
log.info("input request body be null......");
return o;
}
}
package com.wm.mi.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.wm.mi.exception.MyException;
import com.wm.mi.util.Md5Tool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/***
* @ClassName: EncodeResponseBody
* @Description: 返回加密 注意ResponseBodyAdvice僅針對打上@ResponseBody註解的返回值生效
* @Author: wm_yu
* @Create_time: 14:43 2019-11-11
*/
@Slf4j
@Component
@ControllerAdvice("com.wm.mi.controller")
public class EncodeResponseBody implements ResponseBodyAdvice {
@Value("${enctry.secret}")
private String SECRET;
/**
* 設置條件,這個條件爲true纔會執行下面的beforeBodyWrite方法
* @param methodParameter
* @param aClass
* @return
*/
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
/**
* 返回值加密處理
* @param body
* @param methodParameter
* @param mediaType
* @param aClass
* @param serverHttpRequest
* @param serverHttpResponse
* @return
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
//禁止fastjson轉換循環引用
String bodyStr = JSON.toJSONString(body, SerializerFeature.DisableCircularReferenceDetect);
log.info("start encode response:{}", JSON.toJSONString(body));
String encodeStr = null;
try {
encodeStr = Md5Tool.md5Encode(bodyStr, SECRET);
log.info("end encode response:{}",encodeStr);
} catch (Exception e) {
throw new MyException("encode response body exception...");
}
return encodeStr;
}
}
Yml中自定義的一個 常量:
enctry:
secret: d5416a341766390368ab75d220a6c051
使用到的MD加密解密工具類:
package com.wm.mi.util;
/***
* @ClassName: Md5Tool
* @Description: 簡單MD5加密解密工具類
* @Author: wm_yu
* @Create_time: 14:53 2019-11-11
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
/**
* @class_name: MD5Tool
* @description:
* @author: wm_yu
* @create: 2019/09/12
**/
public class Md5Tool {
private final static Logger log = LoggerFactory.getLogger(Md5Tool.class);
/**
* 向量(同時擁有向量和密匙才能解密),此向量必須是8byte,多少都報錯
*/
private final static byte[] DESIV = new byte[] { 0x22, 0x54, 0x36, 110, 0x40, (byte) 0xac, (byte) 0xad, (byte) 0xdf };
/**
* 加密算法的參數接口
*/
private static AlgorithmParameterSpec iv = null;
private static Key key = null;
private static String charset = "utf-8";
private static void init(String SECRET_KEY){
try {
// 設置密鑰參數
DESKeySpec keySpec = new DESKeySpec(SECRET_KEY.getBytes(charset));
// 設置向量
iv = new IvParameterSpec(DESIV);
// 獲得密鑰工廠
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
// 得到密鑰對象
key = keyFactory.generateSecret(keySpec);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 加密
* @param data
* @return
* @throws Exception
*/
public static String md5Encode(String data,String SECRET_KEY) throws Exception {
init(SECRET_KEY);
// 得到加密對象Cipher
Cipher enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
// 設置工作模式爲加密模式,給出密鑰和向量
enCipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] pasByte = enCipher.doFinal(data.getBytes(charset));
BASE64Encoder base64Encoder = new BASE64Encoder();
return base64Encoder.encode(pasByte);
}
/**
* 解密
* @param data
* @return
* @throws Exception
*/
public static String md5Decode(String data,String SECRET_KEY) throws Exception {
init(SECRET_KEY);
Cipher deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
deCipher.init(Cipher.DECRYPT_MODE, key, iv);
BASE64Decoder base64Decoder = new BASE64Decoder();
//此處注意doFinal()的參數的位數必須是8的倍數,否則會報錯(通過encode加密的字符串讀出來都是8的倍數位,但寫入文件再讀出來,就可能因爲讀取的方式的問題,導致最後此處的doFinal()的參數的位數不是8的倍數)
//此處必須用base64Decoder,若用data。getBytes()則獲取的字符串的byte數組的個數極可能不是8的倍數,而且不與上面的BASE64Encoder對應(即使解密不報錯也不會得到正確結果)
byte[] pasByte = deCipher.doFinal(base64Decoder.decodeBuffer(data));
return new String(pasByte, charset);
}
/**
* 獲取MD5的值,可用於對比校驗
* @param sourceStr
* @return
*/
private static String getMD5Value(String sourceStr) {
String result = "";
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(sourceStr.getBytes());
byte b[] = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0){ i += 256;}
if (i < 16){buf.append("0");}
buf.append(Integer.toHexString(i));
}
result = buf.toString();
} catch (NoSuchAlgorithmException e) {
}
return result;
}
public static void main(String[] args) {
try {
String secret_key = "d5416a341766390368ab75d220a6c051";
String source = "name:yu";
String value = md5Encode(source,secret_key);
String decode = md5Decode(value,secret_key);
System.out.println("原始數據:" + source + "----加密後的數據:" + value + "-----解密後的數據:" + decode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
構建request處理之後的返回數據類:
package com.wm.mi.util;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import java.io.IOException;
import java.io.InputStream;
/***
* @ClassName: HttpInputMessageUtil
* @Description:
* @Author: wm_yu
* @Create_time: 15:32 2019-11-11
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Accessors(fluent = true)
public class HttpInputMessageUtil implements HttpInputMessage {
private HttpHeaders headers;
private InputStream body;
@Override
public InputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
獲取request中的數據工具類:
package com.wm.mi.util;
import org.springframework.util.ObjectUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/***
* @ClassName: RequestUtil
* @Description:
* @Author: wm_yu
* @Create_time: 15:16 2019-11-11
*/
public class RequestUtil {
/**
* reuqest body流數據轉換爲String
* @param inputStream
* @return
* @throws IOException
*/
public static String getRequestBodyStr(InputStream inputStream) throws IOException {
StringBuilder builder = new StringBuilder();
if (!ObjectUtils.isEmpty(inputStream)) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
builder.append(charBuffer, 0, bytesRead);
}
}else {
builder.append("");
}
return builder.toString();
}
}
自定義一個異常類:
package com.wm.mi.exception;
import lombok.AllArgsConstructor;
/***
* @ClassName: MyException
* @Description: 自定義異常
* @Author: wm_yu
* @Create_time: 15:14 2019-11-11
*/
@AllArgsConstructor
public class MyException extends RuntimeException{
private Integer errorCode;
public MyException() {
}
public MyException(String message) {
super(message);
}
public MyException(Integer errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public Integer getErrorCode() {
return this.errorCode;
}
public void setErrorCode(Integer errorCode) {
this.errorCode = errorCode;
}
}
新建cotroller測試類:
package com.wm.mi.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/***
* @ClassName: HelloController
* @Description:
* @Author: wm_yu
* @Create_time: 16:35 2019-11-11
*/
@RestController
@RequestMapping("/yu")
public class HelloController {
@GetMapping("/hello")
public String hello(@RequestBody String name){
return String.format("hell0,%s",name);
}
}
先使用工具類,構建一個加密之後的請求參數,如下
模擬請求:
結果如下: