SpringBoot Controller层传入的参数进行解密

news/2024/6/18 21:22:16 标签: spring boot, java, Advice

一、 应用场景

当和第三方应用对接系统的时候, 可能别人的参数加密方式和我们的不相同,那就需要和对方沟通好他们的接口参数是如何加密的,达成一致后才方便后续的工作开展。

二、示例说明

采用Springboot 项目开发,先在component 中封装好切面,然后在具体的服务中依赖此项目。为啥要采用切面,主要是因为使用注解 @ControllerAdvice 比较方便实现

三、 SpringMVC 请求和响应相关组件结构在这里插入图片描述

component 具体结构与实现

在这里插入图片描述

1.1 第三方应用配置信息

java">import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.Serializable;
import java.security.MessageDigest;


/**
 * 第三方应用配置
 */
@Component
public class ThirdPartyAppConfig implements Serializable {

    @Value("${third.party.app.config.secret}")
    private String appSecret;

    @Value("${third.party.app.config.id}")
    private String appId;

	/**
	具体的加密方式:
	1. 将 dict 进行 json 序列号,之后使用 AES/CBC/PKCS5PADDING 进行对称加密,加密的 为 app_secret
		的 sha256 的 digest 哈希值的前 16 字节, 加密的块长度为 16
	2. 加密之后,生成的是标准的 base64 编码,为了 url 传输安全, 做两部转换:
		a) 将 base64 结果中的+/分别用-和_替代,例:a+/b ===> a-_b
		b) 将 base64 尾部用于 padding 的=去除,例: abc= 变成 abc 其中的时间戳为 UTC 时间
	*/

  /**
     * 加密
     * @param data 需要加密的json 数据
     * @return
     * @throws Exception
     */
    public String encrypt(String data) throws Exception {
        if (StringUtils.isEmpty(data)) {
            return null;
        }
        MessageDigest sha = MessageDigest.getInstance("SHA-256");
        sha.update(appSecret.getBytes());
        byte[] raw = sha.digest();

        byte[] raw_half = new byte[16];
        System.arraycopy(raw, 0, raw_half, 0, 16);

        MessageDigest iv_init = MessageDigest.getInstance("MD5");
        iv_init.update(data.getBytes("UTF-8"));
        byte[] iv_raw = iv_init.digest();


        SecretKeySpec skeySpec = new SecretKeySpec(raw_half, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");//"算法/模式/补码方式"
        IvParameterSpec iv = new IvParameterSpec(iv_raw);//使用CBC模式,需要一个向量iv,可增加加密算法的强度
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8"));

        byte[] encrypted_sum = new byte[encrypted.length + 16];
        System.arraycopy(iv_raw, 0, encrypted_sum, 0, 16);
        System.arraycopy(encrypted, 0, encrypted_sum, 16, encrypted.length);


        String result = new BASE64Encoder().encode(encrypted_sum).toString().trim().replace("+", "-")
                .replace("/", "_").replace("\r", "").replace("\n", "");//此处使用BASE64做转码功能,同时能起到2次加密的作用。
        while (result.endsWith("=")) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    /**
     * 解密
     *
     * @param decodeData 需要解密的数据
     * @return
     */
    public String decrypt(String decodeData) {
        try {
            int trim_number = (4 - decodeData.length() % 4) % 4;
            String trim_str = "";
            for (int i = 0; i < trim_number; i++) {
                trim_str += "=";
            }
            decodeData = (decodeData + trim_str).replace("_", "/").replace("-", "+");

            MessageDigest sha = MessageDigest.getInstance("SHA-256");
            sha.update(appSecret.getBytes());
            byte[] raw = sha.digest();

            byte[] raw_half = new byte[16];
            System.arraycopy(raw, 0, raw_half, 0, 16);

            byte[] encrypted_init = new BASE64Decoder().decodeBuffer(decodeData);//先用base64解密

            byte[] iv_raw = new byte[16];
            System.arraycopy(encrypted_init, 0, iv_raw, 0, 16);

            byte[] encrypted = new byte[encrypted_init.length - 16];
            System.arraycopy(encrypted_init, 16, encrypted, 0, encrypted_init.length - 16);

            SecretKeySpec skeySpec = new SecretKeySpec(raw_half, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec iv = new IvParameterSpec(iv_raw);
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(encrypted);
            return  new String(original, "UTF-8");
        } catch (Exception ex) {
            System.out.println(ex);
            return null;
        }
    }
}

1.2 将配置类作为自动配置的类

resources/META-INF/spring.factories 中配置,这样就将系统配置文件的值: third.party.app.config.secretthird.party.app.config.id 自动设置到 ThirdPartyAppConfig 属性中

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.thirdparty.ThirdPartyAppConfig

2、 对于请求体入参的RequestBody 进行解密

2.1 定义解密注解

java">import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * 解密请求
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptRequestBody {
}

2.2 解密的请求体Controller 切面

java">
import com.thirdparty.ThirdPartyAppConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

@ControllerAdvice
@Component
@Aspect
@Slf4j
@Order(1)
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

    @Resource
    private ThirdPartyAppConfig thirdPartyAppConfig;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.hasMethodAnnotation(DecryptRequestBody.class) || methodParameter.hasParameterAnnotation(DecryptRequestBody.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        //先判断有没有使用该注解
        boolean isAnnotationPresent = parameter.getMethod().isAnnotationPresent(DecryptRequestBody.class);
        if (isAnnotationPresent) {
            return new DecryptHttpInputMessage(inputMessage, "UTF-8");
        }
        return inputMessage;

    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return null;
    }


    public class DecryptHttpInputMessage implements HttpInputMessage {

        private HttpInputMessage httpInputMessage;

        private String charset;

        @Override
        public HttpHeaders getHeaders() {
            return httpInputMessage.getHeaders();
        }

        public DecryptHttpInputMessage(HttpInputMessage httpInputMessage, String charset) {
            this.httpInputMessage = httpInputMessage;
            this.charset = charset;
        }

        @Override
        public InputStream getBody() throws IOException {
            //读取body的数据
            StringWriter writer = new StringWriter();
            IOUtils.copy(httpInputMessage.getBody(), writer, StandardCharsets.UTF_8.name());
            String decrypt = writer.toString();

            log.info("前端传进来的数据:{}", decrypt);
            //把数据解密,
            decrypt = thirdPartyAppConfig.decrypt(decrypt);
            log.info("解密后的数据为:{}", decrypt);
            return IOUtils.toInputStream(decrypt, charset);
        }
    }
}

3、 对于响应体入参的ResponseBody 进行加密

3.1 定义加密注解

java">import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * 加密响应体
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptResponseBody {
}

3.2 加密的响应体Controller 切面

java">
import com.alibaba.fastjson.JSONObject;
import com.component.common.base.BaseResult;
import com.thirdparty.ThirdPartyAppConfig;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
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 javax.annotation.Resource;


@ControllerAdvice
@Component
@Aspect
@Slf4j
@Order(1)
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<BaseResult> {

    @Resource
    private ThirdPartyAppConfig thirdPartyAppConfig;


    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return  returnType.hasMethodAnnotation(EncryptResponseBody.class);
    }


    @Override
    public BaseResult beforeBodyWrite(BaseResult body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        try {
            Object content = body.getContent();
            if (content != null) {
                String jsonString = JSONObject.toJSONString(content);
                body.setContent(thirdPartyAppConfig.encrypt(jsonString));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return body;
    }
}

4、实际使用

java"> 	@PostMapping("/testParam")
    @DecryptRequest
    public BaseResult<?> testParam(@RequestBody String requestParam){

		//这里是解密后的数据
        log.info(requestParam);

        return BaseResult.buildSuccess("转换后的参数为:",requestParam);
    }

http://www.niftyadmin.cn/n/359378.html

相关文章

打家劫舍 III——力扣337

文章目录 题目描述法一&#xff1a;动态规划 题目描述 法一&#xff1a;动态规划 问题简化&#xff1a;一棵二叉树&#xff0c;树上的每个点都有对应的权值&#xff0c;每个点有两种状态&#xff08;选中和不选中&#xff09;&#xff0c;问在不能同时选中有父子关系的点的情况…

00_JS基础_ES6

js的标准ECMAScript(ES),现在使用的版本为ES6 js编写的位置 1.写在HTML中的scrip标签 <script>//内嵌式console.log("hello world") </script> <!--引入外部的js文件,script不能使用单标签-->2.引用中使用 <script src"../js/01_index…

vs中计算代码行数

在vs中依次点击以下几个菜单按钮&#xff1a;”编辑“&#xff0c;”查找和替换“&#xff0c;”在文件中查找“&#xff0c;然后输入如下表达式&#xff0c; b*[^:b#/].*$并点击”使用正则表达式“复选框后&#xff0c;然后再”查找范围“选项卡中选择解决方案或者工程或者本…

drf-----认证组件

认证组件 认证组件使用步骤&#xff08;固定用法&#xff09; 1 写一个类&#xff0c;继承BaseAuthentication 2 在类中写&#xff1a;authenticate 3 在方法中&#xff0c;完成登录认证&#xff0c;如果 不是登录的&#xff0c;抛异常 4 如果是登录的&#xff0c…

Java的4种内部类的使用方式及适用场景

Java中有四种形式的内部类&#xff0c;在开发的过程中需要理清楚何时使用合适的内部类&#xff0c;内部类用好了可以提高编码效率、更好的实现封装、甚至可以巧妙实现多继承。当然&#xff0c;某些内部类用多了会削弱面向对象的设计思想&#xff0c;所以内部类不可滥用&#xf…

由于过多的连接错误而被 MySQL服务器 阻止

Caused by: com.mysql.cj.exceptions.CJException: null, message from server: "Host 10.105.***.** is blocked because of many connection errors; unblock with mysqladmin flush-hosts" 这个错误可能表示当您尝试使用 IP 地址为 "10.105.***.**" 的…

YOLOv5【训练train.py逐行源码及参数调参解析】超详细解读!!!建议收藏✨✨!

之前的文章介绍了YOLOv5的网络结构&#x1f680;与目录结构源码&#x1f680;以及detect.py&#x1f680;的详细解读&#xff0c;今天带来的是YOLOv5的 train.py 代码参数逐行解读以及注释&#xff0c;废话不多说&#xff0c;让我们一起学习YOLOv5的 train.py 源码吧&#xff0…

低代码平台名声臭,用起来却真香——90%重复工作给你完成喽

一、前言 开发过程中&#xff0c;只是觉得前端后端合起来&#xff0c;有很多冗余信息&#xff0c;被代码一遍遍重复表达&#xff0c;是一件很枯燥、无聊的事情。 这些枯燥的重复工作&#xff0c;完全可以由机器来做&#xff0c;以便解放出我们的时间&#xff0c;来做更有价值的…