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

一、 应用场景

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

二、示例说明

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

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

1、component 具体结构与实现

在这里插入图片描述

1.1 第三方应用配置信息
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 定义解密注解
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 切面

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 定义加密注解
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 切面

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、实际使用

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

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

        return BaseResult.buildSuccess("转换后的参数为:",requestParam);
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值