网站是什么样子的,企业展示网站模板免费下载,白城网站seo,导航网站模板SpringBoot请求参数加密、响应参数解密
1.说明
在项目开发工程中#xff0c;有的项目可能对参数安全要求比较高#xff0c;在整个http数据传输的过程中都需要对请求参数、响应参数进行加密#xff0c;也就是说整个请求响应的过程都是加密处理的#xff0c;不在浏览器上暴…SpringBoot请求参数加密、响应参数解密
1.说明
在项目开发工程中有的项目可能对参数安全要求比较高在整个http数据传输的过程中都需要对请求参数、响应参数进行加密也就是说整个请求响应的过程都是加密处理的不在浏览器上暴露请求参数、响应参数的真实数据。
补充:也可以用于单点登录在请求参数中添加时间戳后台解析请求参数对时间戳进行校验比如当前时间和请求参数中的时间戳相差多少秒、分钟才能进行放行返回token。这样做的好处在于请求端每次加密之后的密文都是变化的也能够避免携带相同的报文可以重复的登录。
2.准备工作
1.引入依赖 创建SpringBoot工程 dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependencydependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactId/dependencydependencygroupIdcommons-io/groupIdartifactIdcommons-io/artifactIdversion2.11.0/version/dependencydependencygroupIdjunit/groupIdartifactIdjunit/artifactId/dependencydependencygroupIdcommons-codec/groupIdartifactIdcommons-codec/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency!--swagger--dependencygroupIdio.springfox/groupIdartifactIdspringfox-swagger2/artifactIdversion3.0.0/version/dependencydependencygroupIdio.springfox/groupIdartifactIdspringfox-swagger-ui/artifactIdversion3.0.0/version/dependencydependencygroupIdio.swagger/groupIdartifactIdswagger-annotations/artifactIdversion1.5.22/version/dependencydependencygroupIdcom.github.xiaoymin/groupIdartifactIdswagger-bootstrap-ui/artifactIdversion1.8.7/version/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactId/dependency/dependencies3.代码实现
1.定义两个注解
/*** description: : 请求参数解密*/
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface DecryptionAnnotation {
}/*** description: 响应参数加密*/
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface EncryptionAnnotation {
}2.加密解密实现核心代码
DecryptRequestBodyAdvice:请求参数解密针对post请求
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: 请求参数解密针对post请求*/Slf4j
ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {/*** 方法上有DecryptionAnnotation注解的进入此拦截器** param methodParameter 方法参数对象* param targetType 参数的类型* param converterType 消息转换器* return true进入false跳过*/Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class? extends HttpMessageConverter? converterType) {return methodParameter.hasMethodAnnotation(DecryptionAnnotation.class);}Overridepublic 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);}}/*** 转换之后执行此方法解密赋值** param body spring解析完的参数* param inputMessage 输入参数* param parameter 参数对象* param targetType 参数类型* param converterType 消息转换类型* return 真实的参数*/SneakyThrowsOverridepublic Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class? extends HttpMessageConverter? converterType) {log.info(解密后的请求报文:{}, body);return body;}/*** 如果body为空转为空对象** param body spring解析完的参数* param inputMessage 输入参数* param parameter 参数对象* param targetType 参数类型* param converterType 消息转换类型* return 真实的参数*/Overridepublic 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();//只对post请求进行加密if (parameter.hasMethodAnnotation(PostMapping.class)) {/**请求报文示例* {* requestData:JF7kvl9Wd/vgdmAS8JijsQ* }*/String decrypt AESUtil.decrypt(easpData(IOUtils.toString(inputMessage.getBody(), UTF-8)));log.info(解密后的请求参数:{}, decrypt);this.body IOUtils.toInputStream(decrypt, UTF-8);} else {this.body inputMessage.getBody();}}Overridepublic InputStream getBody() throws IOException {return body;}Overridepublic 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(参数【requestData】缺失异常);}}return ;}
}GetDeleteDecryptAspect:针对get、delete请求参数进行解密
Aspect
//值越小优先级越高
Order(-1)
Component
Slf4j
public class GetDeleteDecryptAspect {/*** 对get、delete方法进行解密* 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();// 获取到请求的参数列表Object[] args point.getArgs();// 判断方法请求参数是否需要解密if (method.isAnnotationPresent(DecryptionAnnotation.class)) {try {this.decrypt(args, point);log.info(返回解密结果 args);} catch (Exception e) {e.printStackTrace();log.error(对方法method :【 method.getName() 】入参数据进行解密出现异常 e.getMessage());}}// 执行将解密的结果交给控制器进行处理并返回处理结果return point.proceed(args);}/*** 前端对请求参数进行加密最终将这个加密的字符串已 localhost:48080?dataxxx这样的方式进行传递* 后端后去到 data的数据进行解密最终得到解密后的数据* 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(数据类型{},aClass.getClass());if (StringUtils.isNotEmpty(data)) {// 将JSON字符串转换为Map集合并替换原本的参数args[0] JSONUtil.toBean(AESUtil.decrypt(data), args[0].getClass());}}
}EncryptResponseBodyAdvice:响应参数解密针对统一返回结果类的装配
/*** description: 响应加密*/Slf4j
ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice {Overridepublic boolean supports(MethodParameter methodParameter, Class aClass) {return methodParameter.hasMethodAnnotation(EncryptionAnnotation.class);}Overridepublic Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {log.info(对方法method :【 methodParameter.getMethod().getName() 】返回数据进行加密);// 只针对回参类型为CommonResult的对象进行加密if (body instanceof CommonResult) {CommonResult commonResult (CommonResult) body;Object data commonResult.getData();if (Objects.nonNull(data)) {// 将响应结果转换为json格式String result JSONUtil.toJsonStr(data);log.info(返回结果:{}, result);try {String encrypt AESUtil.encrypt(result);commonResult.setData(encrypt);log.info(返回结果加密 commonResult);} catch (Exception e) {log.error(对方法method :【 methodParameter.getMethod().getName() 】返回数据进行解密出现异常 e.getMessage());}return commonResult;}}return body;}}3.统一返回结果
Data
public class CommonResultT {private String code;private String msg;private T data;public CommonResult() {}public CommonResult(T data) {this.data data;}/*** 表示成功的Result,不携带返回数据** return*/public static CommonResult success() {CommonResult result new CommonResult();result.setCode(200);result.setMsg(success);return result;}/*** 便是成功的Result,携带返回数据* 如果需要在static方法使用泛型,需要在static后指定泛型表示 staticT** param data* return*/public static T CommonResultT success(T data) {CommonResultT result new CommonResult(data);result.setCode(200);result.setMsg(success);return result;}/*** 失败不携带数据* 将错误的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;}/*** 失败携带数据* 将错误的code、msg、data作为形参灵活传入* param code* param msg* param data* param T* return*/public static T CommonResultT error(String code, String msg, T data) {CommonResultT 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;// 与前端统一好KEYprivate static final String KEY abcdsxyzhkj12345;// 获取 cipherprivate 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函数加密密文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);}}Testpublic 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/ZV2qU8h2htdk5s6ht9kCpTGG9WZAGTdMUgIJkD/Tf6IQ3gw;String decrypt AESUtil.decrypt(IOUtils.toString(str.getBytes(), UTF-8));System.out.println(decrypt);} catch (Exception e) {e.printStackTrace();}}
}5.请求流支持多次获取
/*** 请求流支持多次获取*/
public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper {/*** 用于缓存输入流*/private ByteArrayOutputStream cachedBytes;public InputStreamHttpServletRequestWrapper(HttpServletRequest request) {super(request);}Overridepublic ServletInputStream getInputStream() throws IOException {if (cachedBytes null) {// 首次获取流时将流放入 缓存输入流 中cacheInputStream();}// 从 缓存输入流 中获取流并返回return new CachedServletInputStream(cachedBytes.toByteArray());}Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}/*** 首次获取流时将流放入 缓存输入流 中*/private void cacheInputStream() throws IOException {// 缓存输入流以便多次读取。为了方便, 我使用 org.apache.commons IOUtilscachedBytes new ByteArrayOutputStream();IOUtils.copy(super.getInputStream(), cachedBytes);}/*** 读取缓存的请求正文的输入流* p* 用于根据 缓存输入流 创建一个可返回的*/public static class CachedServletInputStream extends ServletInputStream {private final ByteArrayInputStream input;public CachedServletInputStream(byte[] buf) {// 从缓存的请求正文创建一个新的输入流input new ByteArrayInputStream(buf);}Overridepublic boolean isFinished() {return false;}Overridepublic boolean isReady() {return false;}Overridepublic void setReadListener(ReadListener listener) {}Overridepublic int read() throws IOException {return input.read();}}}4.测试
1.测试类
Slf4j
RestController
Api(tags 测试加密解密)
public class TestController {/*** 请求示例* {* requestData:5tAayXF5ZcPC9yoNvBIT0fw2Li2uoxUhGyMq4JKUvCttOFnU7iKovyB9pm/ZV2qU8h2htdk5s6ht9kCpTGG9WZAGTdMUgIJkD/Tf6IQ3gw* }** return*/PostMapping(value /postEncrypt)ApiOperation(测试post加密)EncryptionAnnotationDecryptionAnnotationpublic CommonResultString postEncrypt(RequestBody UserReqVO userReqVO) {System.out.println(userReqVO: userReqVO);return CommonResult.success(成功);}GetMapping(value /getEncrypt)ApiOperation(测试get加密)DecryptionAnnotation // requestBody 自动解密public CommonResultUserReqVO getEncrypt(String data) {log.info(解密后的数据{},data);UserReqVO userReqVO JSONUtil.toBean(data, UserReqVO.class);//UserReqVO(usernameadmin, deptId1250500000, userId1, phone15195928695)log.info(用户信息:{},userReqVO);return CommonResult.success(userReqVO);}
}ApiModel(description 用户请求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 电话号码,required true)private String phone;}测试结果