SpringBoot請(qǐng)求參數(shù)加密、響應(yīng)參數(shù)解密的實(shí)現(xiàn)
1.說(shuō)明
在項(xiàng)目開發(fā)工程中,有的項(xiàng)目可能對(duì)參數(shù)安全要求比較高,在整個(gè)http數(shù)據(jù)傳輸?shù)倪^程中都需要對(duì)請(qǐng)求參數(shù)、響應(yīng)參數(shù)進(jìn)行加密,也就是說(shuō)整個(gè)請(qǐng)求響應(yīng)的過程都是加密處理的,不在瀏覽器上暴露請(qǐng)求參數(shù)、響應(yīng)參數(shù)的真實(shí)數(shù)據(jù)。
補(bǔ)充:也可以用于單點(diǎn)登錄,在請(qǐng)求參數(shù)中添加時(shí)間戳,后臺(tái)解析請(qǐng)求參數(shù)對(duì)時(shí)間戳進(jìn)行校驗(yàn),比如當(dāng)前時(shí)間和請(qǐng)求參數(shù)中的時(shí)間戳相差多少秒、分鐘才能進(jìn)行放行,返回token。這樣做的好處在于請(qǐng)求端每次加密之后的密文都是變化的,也能夠避免攜帶相同的報(bào)文可以重復(fù)的登錄。
2.準(zhǔn)備工作
1.引入依賴, 創(chuàng)建SpringBoot工程
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.22</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.8.7</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> </dependencies>
3.代碼實(shí)現(xiàn)
1.定義兩個(gè)注解
/**
* @description: : 請(qǐng)求參數(shù)解密
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptionAnnotation {
}
/**
* @description: 響應(yīng)參數(shù)加密
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptionAnnotation {
}
2.加密解密實(shí)現(xiàn)核心代碼
DecryptRequestBodyAdvice:請(qǐng)求參數(shù)解密,針對(duì)post請(qǐng)求
package com.llp.crypto.advice;
import cn.hutool.json.JSONUtil;
import com.llp.crypto.annotation.DecryptionAnnotation;
import com.llp.crypto.utils.AESUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
/**
* @description: 請(qǐng)求參數(shù)解密,針對(duì)post請(qǐng)求
*/
@Slf4j
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
/**
* 方法上有DecryptionAnnotation注解的,進(jìn)入此攔截器
*
* @param methodParameter 方法參數(shù)對(duì)象
* @param targetType 參數(shù)的類型
* @param converterType 消息轉(zhuǎn)換器
* @return true,進(jìn)入,false,跳過
*/
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.hasMethodAnnotation(DecryptionAnnotation.class);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
try {
return new MyHttpInputMessage(inputMessage, parameter);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 轉(zhuǎn)換之后,執(zhí)行此方法,解密,賦值
*
* @param body spring解析完的參數(shù)
* @param inputMessage 輸入?yún)?shù)
* @param parameter 參數(shù)對(duì)象
* @param targetType 參數(shù)類型
* @param converterType 消息轉(zhuǎn)換類型
* @return 真實(shí)的參數(shù)
*/
@SneakyThrows
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
log.info("解密后的請(qǐng)求報(bào)文:{}", body);
return body;
}
/**
* 如果body為空,轉(zhuǎn)為空對(duì)象
*
* @param body spring解析完的參數(shù)
* @param inputMessage 輸入?yún)?shù)
* @param parameter 參數(shù)對(duì)象
* @param targetType 參數(shù)類型
* @param converterType 消息轉(zhuǎn)換類型
* @return 真實(shí)的參數(shù)
*/
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
class MyHttpInputMessage implements HttpInputMessage {
private HttpHeaders headers;
private InputStream body;
private MethodParameter parameter;
public MyHttpInputMessage(HttpInputMessage inputMessage, MethodParameter parameter) throws Exception {
this.headers = inputMessage.getHeaders();
//只對(duì)post請(qǐng)求進(jìn)行加密
if (parameter.hasMethodAnnotation(PostMapping.class)) {
/*
*請(qǐng)求報(bào)文示例:
* {
* "requestData":"JF7kvl9Wd/vgdmAS8JijsQ=="
* }
*/
String decrypt = AESUtil.decrypt(easpData(IOUtils.toString(inputMessage.getBody(), "UTF-8")));
log.info("解密后的請(qǐng)求參數(shù):{}", decrypt);
this.body = IOUtils.toInputStream(decrypt, "UTF-8");
} else {
this.body = inputMessage.getBody();
}
}
@Override
public InputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
public String easpData(String requestData) {
if (requestData != null && !requestData.equals("")) {
String start = "requestData";
if (requestData.contains(start)) {
return JSONUtil.parseObj(requestData).getStr(start);
} else {
throw new RuntimeException("參數(shù)【requestData】缺失異常!");
}
}
return "";
}
}
GetDeleteDecryptAspect:針對(duì)get、delete請(qǐng)求參數(shù)進(jìn)行解密
@Aspect
//值越小優(yōu)先級(jí)越高
@Order(-1)
@Component
@Slf4j
public class GetDeleteDecryptAspect {
/**
* 對(duì)get、delete方法進(jìn)行解密
* @param point
* @return
* @throws Throwable
*/
@Around("@annotation(com.llp.crypto.annotation.DecryptionAnnotation) && " + "(@annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.DeleteMapping))")
public Object aroundMethod(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 獲取到請(qǐng)求的參數(shù)列表
Object[] args = point.getArgs();
// 判斷方法請(qǐng)求參數(shù)是否需要解密
if (method.isAnnotationPresent(DecryptionAnnotation.class)) {
try {
this.decrypt(args, point);
log.info("返回解密結(jié)果=" + args);
} catch (Exception e) {
e.printStackTrace();
log.error("對(duì)方法method :【" + method.getName() + "】入?yún)?shù)據(jù)進(jìn)行解密出現(xiàn)異常:" + e.getMessage());
}
}
// 執(zhí)行將解密的結(jié)果交給控制器進(jìn)行處理,并返回處理結(jié)果
return point.proceed(args);
}
/**
* 前端對(duì)請(qǐng)求參數(shù)進(jìn)行加密,最終將這個(gè)加密的字符串已 localhost:48080?data=xxx這樣的方式進(jìn)行傳遞
* 后端后去到 data的數(shù)據(jù)進(jìn)行解密最終得到解密后的數(shù)據(jù)
* @param args
* @param point
* @throws Exception
*/
// 解密方法
@SuppressWarnings("unchecked")
public void decrypt(Object[] args, ProceedingJoinPoint point) throws Exception {
ServletRequestAttributes sc = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sc.getRequest();
String data = request.getParameter("data");
log.info("data: " + data);
// 將密文解密為JSON字符串
Class<?> aClass = args[0].getClass();
log.info("數(shù)據(jù)類型:{}",aClass.getClass());
if (StringUtils.isNotEmpty(data)) {
// 將JSON字符串轉(zhuǎn)換為Map集合,并替換原本的參數(shù)
args[0] = JSONUtil.toBean(AESUtil.decrypt(data), args[0].getClass());
}
}
}
EncryptResponseBodyAdvice:響應(yīng)參數(shù)解密,針對(duì)統(tǒng)一返回結(jié)果類的裝配
/**
* @description: 響應(yīng)加密
*/
@Slf4j
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return methodParameter.hasMethodAnnotation(EncryptionAnnotation.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
log.info("對(duì)方法method :【" + methodParameter.getMethod().getName() + "】返回?cái)?shù)據(jù)進(jìn)行加密");
// 只針對(duì)回參類型為CommonResult的對(duì)象,進(jìn)行加密
if (body instanceof CommonResult) {
CommonResult commonResult = (CommonResult) body;
Object data = commonResult.getData();
if (Objects.nonNull(data)) {
// 將響應(yīng)結(jié)果轉(zhuǎn)換為json格式
String result = JSONUtil.toJsonStr(data);
log.info("返回結(jié)果:{}", result);
try {
String encrypt = AESUtil.encrypt(result);
commonResult.setData(encrypt);
log.info("返回結(jié)果加密=" + commonResult);
} catch (Exception e) {
log.error("對(duì)方法method :【" + methodParameter.getMethod().getName() + "】返回?cái)?shù)據(jù)進(jìn)行解密出現(xiàn)異常:" + e.getMessage());
}
return commonResult;
}
}
return body;
}
}
3.統(tǒng)一返回結(jié)果
@Data
public class CommonResult<T> {
private String code;
private String msg;
private T data;
public CommonResult() {
}
public CommonResult(T data) {
this.data = data;
}
/**
* 表示成功的Result,不攜帶返回?cái)?shù)據(jù)
*
* @return
*/
public static CommonResult success() {
CommonResult result = new CommonResult();
result.setCode("200");
result.setMsg("success");
return result;
}
/**
* 便是成功的Result,攜帶返回?cái)?shù)據(jù)
* 如果需要在static方法使用泛型,需要在static后指定泛型表示 static<T>
*
* @param data
* @return
*/
public static <T> CommonResult<T> success(T data) {
CommonResult<T> result = new CommonResult<>(data);
result.setCode("200");
result.setMsg("success");
return result;
}
/**
* 失敗不攜帶數(shù)據(jù)
* 將錯(cuò)誤的code、msg作為形參,靈活傳入
*
* @param code
* @param msg
* @return
*/
public static CommonResult error(String code, String msg) {
CommonResult result = new CommonResult();
result.setCode(code);
result.setMsg(msg);
return result;
}
/**
* 失敗攜帶數(shù)據(jù)
* 將錯(cuò)誤的code、msg、data作為形參,靈活傳入
* @param code
* @param msg
* @param data
* @param <T>
* @return
*/
public static <T> CommonResult<T> error(String code, String msg, T data) {
CommonResult<T> result = new CommonResult<>(data);
result.setCode(code);
result.setMsg(msg);
return result;
}
}
4.加密工具類
public class AESUtil {
// 加解密方式
private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";
// 與前端統(tǒng)一好KEY
private static final String KEY = "abcdsxyzhkj12345";
// 獲取 cipher
private static Cipher getCipher(byte[] key, int model) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(model, secretKeySpec);
return cipher;
}
// AES加密
public static String encrypt(String data) throws Exception {
Cipher cipher = getCipher(KEY.getBytes(), Cipher.ENCRYPT_MODE);
return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes("UTF-8")));
}
// AES解密
public static String decrypt(String data) throws Exception {
Cipher cipher = getCipher(KEY.getBytes(), Cipher.DECRYPT_MODE);
return new String(cipher.doFinal(Base64.getDecoder().decode(data.getBytes("UTF-8"))),"UTF-8");
}
public static byte[] decryptUrl(String url) throws Exception {
Cipher cipher = getCipher(KEY.getBytes(), Cipher.DECRYPT_MODE);
return cipher.doFinal(Base64.getDecoder().decode(url.replaceAll(" +", "+")));
}
// AES解密MySQL AES_ENCRYPT函數(shù)加密密文
public static String aesDecryptMySQL(String key, String content){
try {
SecretKey secretKey = generateMySQLAESKey(key,"ASCII");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] cleartext = Hex.decodeHex(content.toCharArray());
byte[] ciphertextBytes = cipher.doFinal(cleartext);
return new String(ciphertextBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//加密
public static String aesEncryptMySQL(String key2, String content) {
try {
SecretKey key = generateMySQLAESKey(key2,"ASCII");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cleartext = content.getBytes("UTF-8");
byte[] ciphertextBytes = cipher.doFinal(cleartext);
return new String(Hex.encodeHex(ciphertextBytes));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static SecretKeySpec generateMySQLAESKey(final String key, final String encoding) {
try {
final byte[] finalKey = new byte[16];
int i = 0;
for(byte b : key.getBytes(encoding)) {
finalKey[i++%16] ^= b;
}
return new SecretKeySpec(finalKey, "AES");
} catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
@Test
public void decodeTest() {
try {
String a = "{\"username\":\"admin\",\"deptId\":\"1250500000\",\"userId\":\"1\",\"phone\":\"15195928695\"}";
String encrypt = AESUtil.encrypt(a);
System.out.println("加密后的字符串: "+encrypt);
System.out.println("解密后的字符串:" +AESUtil.decrypt(encrypt));
String str = "5tAayXF5ZcPC9yoNvBIT0fw2Li2uoxUhGyMq4JKUvCttOFnU7iKovyB9pm/ZV+2qU8h2htdk5s6ht9kCpTGG9WZAGTdMUgIJkD/Tf6IQ3gw=";
String decrypt = AESUtil.decrypt(IOUtils.toString(str.getBytes(), "UTF-8"));
System.out.println(decrypt);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.請(qǐng)求流支持多次獲取
/**
* 請(qǐng)求流支持多次獲取
*/
public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 用于緩存輸入流
*/
private ByteArrayOutputStream cachedBytes;
public InputStreamHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null) {
// 首次獲取流時(shí),將流放入 緩存輸入流 中
cacheInputStream();
}
// 從 緩存輸入流 中獲取流并返回
return new CachedServletInputStream(cachedBytes.toByteArray());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* 首次獲取流時(shí),將流放入 緩存輸入流 中
*/
private void cacheInputStream() throws IOException {
// 緩存輸入流以便多次讀取。為了方便, 我使用 org.apache.commons IOUtils
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
/**
* 讀取緩存的請(qǐng)求正文的輸入流
* <p>
* 用于根據(jù) 緩存輸入流 創(chuàng)建一個(gè)可返回的
*/
public static class CachedServletInputStream extends ServletInputStream {
private final ByteArrayInputStream input;
public CachedServletInputStream(byte[] buf) {
// 從緩存的請(qǐng)求正文創(chuàng)建一個(gè)新的輸入流
input = new ByteArrayInputStream(buf);
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return input.read();
}
}
}
4.測(cè)試
1.測(cè)試類
@Slf4j
@RestController
@Api(tags = "測(cè)試加密解密")
public class TestController {
/**
* 請(qǐng)求示例:
* {
* "requestData":"5tAayXF5ZcPC9yoNvBIT0fw2Li2uoxUhGyMq4JKUvCttOFnU7iKovyB9pm/ZV+2qU8h2htdk5s6ht9kCpTGG9WZAGTdMUgIJkD/Tf6IQ3gw="
* }
*
* @return
*/
@PostMapping(value = "/postEncrypt")
@ApiOperation("測(cè)試post加密")
@EncryptionAnnotation
@DecryptionAnnotation
public CommonResult<String> postEncrypt(@RequestBody UserReqVO userReqVO) {
System.out.println("userReqVO: ============>" + userReqVO);
return CommonResult.success("成功");
}
@GetMapping(value = "/getEncrypt")
@ApiOperation("測(cè)試get加密")
@DecryptionAnnotation // requestBody 自動(dòng)解密
public CommonResult<UserReqVO> getEncrypt(String data) {
log.info("解密后的數(shù)據(jù):{}",data);
UserReqVO userReqVO = JSONUtil.toBean(data, UserReqVO.class);
//UserReqVO(username=admin, deptId=1250500000, userId=1, phone=15195928695)
log.info("用戶信息:{}",userReqVO);
return CommonResult.success(userReqVO);
}
}
@ApiModel(description = "用戶請(qǐng)求vo")
@Data
public class UserReqVO {
@ApiModelProperty(value = "用戶名", required = true)
private String username;
@ApiModelProperty(value = "部門id",required = true)
private Long deptId;
@ApiModelProperty(value = "用戶id",required = true)
private Long userId;
@ApiModelProperty(value = "電話號(hào)碼",required = true)
private String phone;
}
測(cè)試結(jié)果


到此這篇關(guān)于SpringBoot請(qǐng)求參數(shù)加密、響應(yīng)參數(shù)解密的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot請(qǐng)求參數(shù)加密、響應(yīng)參數(shù)解密內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot添加Email發(fā)送功能及常見異常詳解
本篇文章主要介紹了SpringBoot添加Email發(fā)送功能及常見異常詳解,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-04-04
Spring Data JPA實(shí)現(xiàn)分頁(yè)P(yáng)ageable的實(shí)例代碼
本篇文章主要介紹了Spring Data JPA實(shí)現(xiàn)分頁(yè)P(yáng)ageable的實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07
Java中使用HashMap時(shí)指定初始化容量性能解析
這篇文章主要為大家介紹了Java中使用HashMap時(shí)指定初始化容量性能解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
MyBatis Mapper XML中比較操作符轉(zhuǎn)義問題解決
在使用MyBatis編寫Mapper XML時(shí),有時(shí)會(huì)遇到比較操作符需要進(jìn)行轉(zhuǎn)義的情況,本文主要介紹了MyBatis Mapper XML中比較操作符轉(zhuǎn)義問題解決,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
Spring如何消除代碼中的if-else/switch-case
這篇文章主要給大家介紹了關(guān)于Spring如何消除代碼中if-else/switch-case的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
SpringMVC中的SimpleUrlHandlerMapping用法詳解
這篇文章主要介紹了SpringMVC中的SimpleUrlHandlerMapping用法詳解,SimpleUrlHandlerMapping是Spring MVC中適用性最強(qiáng)的Handler Mapping類,允許明確指定URL模式和Handler的映射關(guān)系,有兩種方式聲明SimpleUrlHandlerMapping,需要的朋友可以參考下2023-10-10
基于Java語(yǔ)言MD5加密Base64轉(zhuǎn)換方法
這篇文章主要為大家詳細(xì)介紹了基于Java語(yǔ)言的MD5加密Base64轉(zhuǎn)換方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09

