使用注解+RequestBodyAdvice實(shí)現(xiàn)http請(qǐng)求內(nèi)容加解密方式
注解主要用來指定那些需要加解密的controller方法
實(shí)現(xiàn)比較簡(jiǎn)單
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SecretAnnotation {
boolean encode() default false;
boolean decode() default false;
}
使用時(shí)添加注解在controller的方法上
@PostMapping("/preview")
@SecretAnnotation(decode = true)
public ResponseVO<ContractSignVO> previewContract(@RequestBody FillContractDTO fillContractDTO) {
return contractSignService.previewContract(fillContractDTO);
}
請(qǐng)求數(shù)據(jù)由二進(jìn)制流轉(zhuǎn)為類對(duì)象數(shù)據(jù),對(duì)于加密過的數(shù)據(jù),需要在二進(jìn)制流被處理之前進(jìn)行解密,否則在轉(zhuǎn)為類對(duì)象時(shí)會(huì)因?yàn)閿?shù)據(jù)格式不匹配而報(bào)錯(cuò)。
因此使用RequestBodyAdvice的beforeBodyRead方法來處理。
@Slf4j
@RestControllerAdvice
public class MyRequestControllerAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return methodParameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
@Autowired
private MySecretUtil mySecretUtil;
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
if (methodParameter.getMethod().isAnnotationPresent(SecretAnnotation.class)) {
SecretAnnotation secretAnnotation = methodParameter.getMethod().getAnnotation(SecretAnnotation.class);
if (secretAnnotation.decode()) {
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
List<String> appIdList = httpInputMessage.getHeaders().get("appId");
if (appIdList.isEmpty()){
throw new RuntimeException("請(qǐng)求頭缺少appID");
}
String appId = appIdList.get(0);
String bodyStr = IOUtils.toString(httpInputMessage.getBody(),"utf-8");
bodyStr = mySecretUtil.decode(bodyStr,appId);
return IOUtils.toInputStream(bodyStr,"utf-8");
}
@Override
public HttpHeaders getHeaders() {
return httpInputMessage.getHeaders();
}
};
}
}
return httpInputMessage;
}
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
}
mySecretUtil.decode(bodyStr,appId)的內(nèi)容是,通過請(qǐng)求頭中的AppID去數(shù)據(jù)庫(kù)中查找對(duì)于的秘鑰,之后進(jìn)行解密,返回解密后的字符串。
再通過common.io包中提供的工具類IOUtils將字符串轉(zhuǎn)為inputstream流,替換HttpInputMessage,返回一個(gè)body數(shù)據(jù)為解密后的二進(jìn)制流的HttpInputMessage。
Stringboot RequestBodyAdvice接口如何實(shí)現(xiàn)請(qǐng)求響應(yīng)加解密
在實(shí)際項(xiàng)目中,我們常常需要在請(qǐng)求前后進(jìn)行一些操作,比如:參數(shù)解密/返回結(jié)果加密,打印請(qǐng)求參數(shù)和返回結(jié)果的日志等。這些與業(yè)務(wù)無關(guān)的東西,我們不希望寫在controller方法中,造成代碼重復(fù)可讀性變差。這里,我們講講使用@ControllerAdvice和RequestBodyAdvice、ResponseBodyAdvice來對(duì)請(qǐng)求前后進(jìn)行處理(本質(zhì)上就是AOP),來實(shí)現(xiàn)日志記錄每一個(gè)請(qǐng)求的參數(shù)和返回結(jié)果。
1.加解密工具類
package com.linkus.common.utils;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Aes {
/**
*
* @author ngh
* AES128 算法
*
* CBC 模式
*
* PKCS7Padding 填充模式
*
* CBC模式需要添加偏移量參數(shù)iv,必須16位
* 密鑰 sessionKey,必須16位
*
* 介于java 不支持PKCS7Padding,只支持PKCS5Padding 但是PKCS7Padding 和 PKCS5Padding 沒有什么區(qū)別
* 要實(shí)現(xiàn)在java端用PKCS7Padding填充,需要用到bouncycastle組件來實(shí)現(xiàn)
*/
private String sessionKey="加解密密鑰";
// 偏移量 16位
private static String iv="偏移量";
// 算法名稱
final String KEY_ALGORITHM = "AES";
// 加解密算法/模式/填充方式
final String algorithmStr = "AES/CBC/PKCS7Padding";
// 加解密 密鑰 16位
byte[] ivByte;
byte[] keybytes;
private Key key;
private Cipher cipher;
boolean isInited = false;
public void init() {
// 如果密鑰不足16位,那么就補(bǔ)足. 這個(gè)if 中的內(nèi)容很重要
keybytes = iv.getBytes();
ivByte = iv.getBytes();
Security.addProvider(new BouncyCastleProvider());
// 轉(zhuǎn)化成JAVA的密鑰格式
key = new SecretKeySpec(keybytes, KEY_ALGORITHM);
try {
// 初始化cipher
cipher = Cipher.getInstance(algorithmStr, "BC");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchProviderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 加密方法
*
* @param content
* 要加密的字符串
* 加密密鑰
* @return
*/
public String encrypt(String content) {
byte[] encryptedText = null;
byte[] contentByte = content.getBytes();
init();
try {
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivByte));
encryptedText = cipher.doFinal(contentByte);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new String(Hex.encode(encryptedText));
}
/**
* 解密方法
*
* @param encryptedData
* 要解密的字符串
* 解密密鑰
* @return
*/
public String decrypt(String encryptedData) {
byte[] encryptedText = null;
byte[] encryptedDataByte = Hex.decode(encryptedData);
init();
try {
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivByte));
encryptedText = cipher.doFinal(encryptedDataByte);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new String(encryptedText);
}
public static void main(String[] args) {
Aes aes = new Aes();
String a="{\n" +
"\"distance\":\"1000\",\n" +
"\"longitude\":\"28.206471\",\n" +
"\"latitude\":\"112.941301\"\n" +
"}";
//加密字符串
//String content = "孟飛快跑";
// System.out.println("加密前的:" + content);
// System.out.println("加密密鑰:" + new String(keybytes));
// 加密方法
String enc = aes.encrypt(a);
System.out.println("加密后的內(nèi)容:" + enc);
String dec="";
// 解密方法
try {
dec = aes.decrypt(enc);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("解密后的內(nèi)容:" + dec);
}
}
2.請(qǐng)求解密
前端頁(yè)面?zhèn)鬟^來的是密文,我們需要在Controller獲取請(qǐng)求之前對(duì)密文解密然后傳給Controller
package com.linkus.common.filter;
import com.alibaba.fastjson.JSON;
import com.linkus.common.constant.KPlatResponseCode;
import com.linkus.common.exception.CustomException;
import com.linkus.common.exception.JTransException;
import com.linkus.common.service.util.MyHttpInputMessage;
import com.linkus.common.utils.Aes;
import com.linkus.common.utils.http.HttpHelper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.lang.reflect.Type;
/**
* 請(qǐng)求參數(shù) 解密操作
*
* @Author: Java碎碎念
* @Date: 2019/10/24 21:31
*
*/
@Component
//可以配置指定需要解密的包,支持多個(gè)
@ControllerAdvice(basePackages = {"com.linkus.project"})
@Slf4j
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
Logger log = LoggerFactory.getLogger(getClass());
Aes aes=new Aes();
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
//true開啟功能,false關(guān)閉這個(gè)功能
return true;
}
//在讀取請(qǐng)求之前做處理
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {
//獲取請(qǐng)求數(shù)據(jù)
String string = "";
BufferedReader bufferedReader = null;
InputStream inputStream = inputMessage.getBody();
//這個(gè)request其實(shí)就是入?yún)?可以從這里獲取流
//入?yún)⒎旁贖ttpInputMessage里面 這個(gè)方法的返回值也是HttpInputMessage
try {
string=getRequestBodyStr(inputStream,bufferedReader);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
/*****************進(jìn)行解密start*******************/
String decode = null;
if(HttpHelper.isEncrypted(inputMessage.getHeaders())){
try {
// //解密操作
//Map<String,String> dataMap = (Map)body;
//log.info("接收到原始請(qǐng)求數(shù)據(jù)={}", string);
// inputData 為待加解密的數(shù)據(jù)源
//解密
decode= aes.decrypt(string);
//log.info("解密后數(shù)據(jù)={}",decode);
} catch (Exception e ) {
log.error("加解密錯(cuò)誤:",e);
throw new CustomException(KPlatResponseCode.MSG_DECRYPT_TIMEOUT,KPlatResponseCode.CD_DECRYPT_TIMEOUT);
}
//把數(shù)據(jù)放到我們封裝的對(duì)象中
}else{
decode = string;
}
// log.info("接收到請(qǐng)求數(shù)據(jù)={}", decode);
// log.info("接口請(qǐng)求地址{}",((HttpServletRequest)inputMessage).getRequestURI());
return new MyHttpInputMessage(inputMessage.getHeaders(), new ByteArrayInputStream(decode.getBytes("UTF-8")));
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public Object handleEmptyBody(@Nullable Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class<? extends HttpMessageConverter<?>> var5) {
return var1;
}
//自己寫的方法,不是接口的方法,處理密文
public String getRequestBodyStr( InputStream inputStream,BufferedReader bufferedReader) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
String string = stringBuilder.toString();
return string;
}
}
3.響應(yīng)加密
將返給前端的響應(yīng)加密,保證數(shù)據(jù)的安全性
package com.linkus.common.filter;
import com.alibaba.fastjson.JSON;
import com.linkus.common.utils.Aes;
import com.linkus.common.utils.DesUtil;
import com.linkus.common.utils.http.HttpHelper;
import io.swagger.models.auth.In;
import lombok.experimental.Helper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 請(qǐng)求參數(shù) 加密操作
*
* @Author: Java碎碎念
* @Date: 2019/10/24 21:31
*
*/
@Component
@ControllerAdvice(basePackages = {"com.linkus.project"})
@Slf4j
public class EncryResponseBodyAdvice implements ResponseBodyAdvice<Object> {
Logger log = LoggerFactory.getLogger(getClass());
Aes aes=new Aes();
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
String returnStr = "";
Object retObj = null;
log.info("接口請(qǐng)求地址{}",serverHttpRequest.getURI());
//日志過濾
//retObj=infofilter.getInfoFilter(returnType,obj);
if(HttpHelper.isEncrypted(serverHttpRequest.getHeaders())) {
try {
//添加encry header,告訴前端數(shù)據(jù)已加密
//serverHttpResponse.getHeaders().add("infoe", "e=a");
//獲取請(qǐng)求數(shù)據(jù)
String srcData = JSON.toJSONString(obj);
//加密
returnStr = aes.encrypt(srcData).replace("\r\n", "");
//log.info("原始數(shù)據(jù)={},加密后數(shù)據(jù)={}", obj, returnStr);
return returnStr;
} catch (Exception e) {
log.error("異常!", e);
}
}
log.info("原始數(shù)據(jù)={}",JSON.toJSONString(obj));
return obj;
}
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中==運(yùn)算符與equals方法的區(qū)別及intern方法詳解
這篇文章主要介紹了Java中==運(yùn)算符與equals方法的區(qū)別及intern方法詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04
JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過程解析
這篇文章主要介紹了JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
Java使用openssl檢測(cè)網(wǎng)站是否支持ocsp
OCSP在線證書狀態(tài)協(xié)議是為了替換CRL而提出來的。對(duì)于現(xiàn)代web服務(wù)器來說一般都是支持OCSP的,OCSP也是現(xiàn)代web服務(wù)器的標(biāo)配,這篇文章主要介紹了Java使用openssl檢測(cè)網(wǎng)站是否支持ocsp,需要的朋友可以參考下2022-07-07
詳解在Spring3中使用注解(@Scheduled)創(chuàng)建計(jì)劃任務(wù)
本篇文章主要介紹了詳解在Spring3中使用注解(@Scheduled)創(chuàng)建計(jì)劃任務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
Springboot使用pdfbox提取PDF圖片的代碼示例
PDFBox是一個(gè)用于創(chuàng)建和處理PDF文檔的Java庫(kù),它可以使用Java代碼創(chuàng)建、讀取、修改和提取PDF文檔中的內(nèi)容,本文就給大家介紹Springboot如何使用pdfbox提取PDF圖片,感興趣的同學(xué)可以借鑒參考2023-06-06

