SpringBoot請(qǐng)求參數(shù)加密、響應(yīng)參數(shù)解密的實(shí)現(xiàn)
1.說明
在項(xiàng)目開發(fā)工程中,有的項(xiàng)目可能對(duì)參數(shù)安全要求比較高,在整個(gè)http數(shù)據(jù)傳輸?shù)倪^程中都需要對(duì)請(qǐng)求參數(shù)、響應(yīng)參數(shù)進(jìn)行加密,也就是說整個(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.測試
1.測試類
@Slf4j @RestController @Api(tags = "測試加密解密") public class TestController { /** * 請(qǐng)求示例: * { * "requestData":"5tAayXF5ZcPC9yoNvBIT0fw2Li2uoxUhGyMq4JKUvCttOFnU7iKovyB9pm/ZV+2qU8h2htdk5s6ht9kCpTGG9WZAGTdMUgIJkD/Tf6IQ3gw=" * } * * @return */ @PostMapping(value = "/postEncrypt") @ApiOperation("測試post加密") @EncryptionAnnotation @DecryptionAnnotation public CommonResult<String> postEncrypt(@RequestBody UserReqVO userReqVO) { System.out.println("userReqVO: ============>" + userReqVO); return CommonResult.success("成功"); } @GetMapping(value = "/getEncrypt") @ApiOperation("測試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; }
測試結(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-04Spring Data JPA實(shí)現(xiàn)分頁P(yáng)ageable的實(shí)例代碼
本篇文章主要介紹了Spring Data JPA實(shí)現(xiàn)分頁P(yáng)ageable的實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07Java中使用HashMap時(shí)指定初始化容量性能解析
這篇文章主要為大家介紹了Java中使用HashMap時(shí)指定初始化容量性能解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02MyBatis Mapper XML中比較操作符轉(zhuǎn)義問題解決
在使用MyBatis編寫Mapper XML時(shí),有時(shí)會(huì)遇到比較操作符需要進(jìn)行轉(zhuǎn)義的情況,本文主要介紹了MyBatis Mapper XML中比較操作符轉(zhuǎn)義問題解決,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01Spring如何消除代碼中的if-else/switch-case
這篇文章主要給大家介紹了關(guān)于Spring如何消除代碼中if-else/switch-case的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04SpringMVC中的SimpleUrlHandlerMapping用法詳解
這篇文章主要介紹了SpringMVC中的SimpleUrlHandlerMapping用法詳解,SimpleUrlHandlerMapping是Spring MVC中適用性最強(qiáng)的Handler Mapping類,允許明確指定URL模式和Handler的映射關(guān)系,有兩種方式聲明SimpleUrlHandlerMapping,需要的朋友可以參考下2023-10-10基于Java語言MD5加密Base64轉(zhuǎn)換方法
這篇文章主要為大家詳細(xì)介紹了基于Java語言的MD5加密Base64轉(zhuǎn)換方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09