小工具知识网

当前位置: 首页 > 行业知识 > SpringBoot请求参数加密、响应参数解密

行业知识

SpringBoot请求参数加密、响应参数解密

2026-11-02 16:22:48 来源:互联网转载

SpringBoot请求参数加密、响应参数解密

1.说明

在项目开发工程中,有的项目可能对参数安全要求比较高,在整个http数据传输的过程中都需要对请求参数、响应参数进行加密,也就是说整个请求响应的过程都是加密处理的,不在浏览器上暴露请求参数、响应参数的真实数据。

补充:也可以用于单点登录,在请求参数中添加时间戳,后台解析请求参数对时间戳进行校验,比如当前时间和请求参数中的时间戳相差多少秒、分钟才能进行放行,返回token。这样做的好处在于请求端每次加密之后的密文都是变化的,也能够避免携带相同的报文可以重复的登录。

2.准备工作

1.引入依赖, 创建SpringBoot工程

org.springframework.bootspring-boot-starter-weborg.projectlomboklombokcn.hutoolhutool-allcommons-iocommons-io2.11.0junitjunitcommons-codeccommons-codecorg.springframework.bootspring-boot-starter-aopio.springfoxspringfox-swagger23.0.0io.springfoxspringfox-swagger-ui3.0.0io.swaggerswagger-annotations1.5.22com.github.xiaoyminswagger-bootstrap-ui1.8.7org.apache.commonscommons-lang3

3.代码实现

1.定义两个注解

/** * @description: : 请求参数解密 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DecryptionAnnotation {}/** * @description: 响应参数加密 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @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@ControllerAdvicepublic class DecryptRequestBodyAdvice implements RequestBodyAdvice {    /**     * 方法上有DecryptionAnnotation注解的,进入此拦截器     *     * @param methodParameter 方法参数对象     * @param targetType      参数的类型     * @param converterType   消息转换器     * @return true,进入,false,跳过     */    @Override    public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) {        return methodParameter.hasMethodAnnotation(DecryptionAnnotation.class);    }    @Override    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> 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 真实的参数     */    @SneakyThrows    @Override    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) {        log.info("解密后的请求报文:{}", body);        return body;    }    /**     * 如果body为空,转为空对象     *     * @param body          spring解析完的参数     * @param inputMessage  输入参数     * @param parameter     参数对象     * @param targetType    参数类型     * @param converterType 消息转换类型     * @return 真实的参数     */    @Override    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> 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();            }        }        @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("参数【requestData】缺失异常!");            }        }        return "";    }}

GetDeleteDecryptAspect:针对get、delete请求参数进行解密

@Aspect//值越小优先级越高@Order(-1)@Component@Slf4jpublic 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?data=xxx这样的方式进行传递     * 后端后去到 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@ControllerAdvicepublic 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("对方法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.统一返回结果

@Datapublic class CommonResult {    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后指定泛型表示 static     *     * @param data     * @return     */    public static  CommonResult success(T data) {        CommonResult 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      * @return     */    public static  CommonResult error(String code, String msg, T data) {        CommonResult 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";    // 与前端统一好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函数加密密文    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.请求流支持多次获取

/** * 请求流支持多次获取 */public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper {    /**     * 用于缓存输入流     */    private ByteArrayOutputStream cachedBytes;    public InputStreamHttpServletRequestWrapper(HttpServletRequest request) {        super(request);    }    @Override    public ServletInputStream getInputStream() throws IOException {        if (cachedBytes == null) {            // 首次获取流时,将流放入 缓存输入流 中            cacheInputStream();        }        // 从 缓存输入流 中获取流并返回        return new CachedServletInputStream(cachedBytes.toByteArray());    }    @Override    public BufferedReader getReader() throws IOException {        return new BufferedReader(new InputStreamReader(getInputStream()));    }    /**     * 首次获取流时,将流放入 缓存输入流 中     */    private void cacheInputStream() throws IOException {        // 缓存输入流以便多次读取。为了方便, 我使用 org.apache.commons IOUtils        cachedBytes = new ByteArrayOutputStream();        IOUtils.copy(super.getInputStream(), cachedBytes);    }    /**     * 读取缓存的请求正文的输入流     * 

* 用于根据 缓存输入流 创建一个可返回的 */ public static class CachedServletInputStream extends ServletInputStream { private final ByteArrayInputStream input; public CachedServletInputStream(byte[] buf) { // 从缓存的请求正文创建一个新的输入流 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 {    /**     * 请求示例:     * {     * "requestData":"5tAayXF5ZcPC9yoNvBIT0fw2Li2uoxUhGyMq4JKUvCttOFnU7iKovyB9pm/ZV+2qU8h2htdk5s6ht9kCpTGG9WZAGTdMUgIJkD/Tf6IQ3gw="     * }     *     * @return     */    @PostMapping(value = "/postEncrypt")    @ApiOperation("测试post加密")    @EncryptionAnnotation    @DecryptionAnnotation    public CommonResult postEncrypt(@RequestBody UserReqVO userReqVO) {        System.out.println("userReqVO: ============>" + userReqVO);        return CommonResult.success("成功");    }    @GetMapping(value = "/getEncrypt")    @ApiOperation("测试get加密")    @DecryptionAnnotation // requestBody 自动解密    public CommonResult getEncrypt(String data) {        log.info("解密后的数据:{}",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 = "用户请求vo")@Datapublic 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;}

测试结果

上一篇:怎么选择多ip服务器?

下一篇:宝塔面板IP授权问题深度解析与解决方案探讨