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

news2024/12/28 5:40:26

一、 应用场景

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

二、示例说明

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

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

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);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/568099.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

feat:使用企业微信企业微信JS-SDK的onMenuShareAppMessage()实现点击转发自定义分享内容(TypeScript)

背景&#xff1a;企业微信应用使用企业微信JS-SDK的分享接口实现分享样式自定义 原生&#xff1a; 需要实现成&#xff1a; 企业微信JS-SDK 是企业微信面向网页开发者提供的 基于企业微信内 的网页开发工具包。 通过使用企业微信JS-SDK&#xff0c;网页开发者 可借助企业微信…

单片机的UART升级固件流程

文章目录 单片机的烧录方式ISP In-Systen Programming 系统编程ICP In-Circuit Programming 电路编程IAP In-Application Programming 应用编程BootLoader 引导程序 串口下载的重难点启动流程基于IAP设计的STM32程序启动流程 随着技术的发展&#xff0c;单片机的功能和性能得到…

AWS-S3通用存储操作,操作minio、oss、cos等所有兼容s3协议的云存储(含有大文件分片上传实现)

一、介绍 通用存储操作common包&#xff0c;支持所有兼容amazon-s3协议的云存储&#xff0c;如minio、oss、cos等&#xff0c;以后客户用啥云储存一套代码都能搞定了&#xff0c;真棒~ 二、代码结构 三、代码实现 3.1 pom.xml <?xml version"1.0" encoding&q…

平安银行广州分行立足地域文化,增强差异化权益服务软实力

立足地域文化&#xff0c;拓展差异化权益服务 瓦屋纸窗之下&#xff0c;一盏清茶&#xff0c;三五好友&#xff0c;怡然自若。中国人对茶的喜爱由来已久&#xff0c;茶文化已成为中华传统文化中一张亮丽的名片&#xff0c;而广东茶文化则是中国四大茶文化系列之一。平安银行广州…

链式哈希,一致性哈希,倒排表

在普通的查询中&#xff0c;通过关键码的比较进行查找&#xff0c;而哈希是根据关键码直接定位到数据项 哈希冲突&#xff1a;同一个关键码经过哈希函数后指向同一个记录集 链式哈希 using namespace std; #define M 13 typedef int KeyType; //typedef struct //{ // KeyTyp…

开放式耳机和封闭式耳机的区别有哪些?开放式耳机有哪些推荐?

开放式耳机和封闭式耳机的区别主要在以下几个方面&#xff1a; 设计结构&#xff1a;开放式耳机通常有一个开放的设计&#xff0c;不需要塞入耳即可收听音乐&#xff0c;同时与外部环境进行交互。封闭式耳机则是封闭的设计&#xff0c;耳机驱动单元之间是封闭和隔离的&#xf…

电子科技大学编译原理复习笔记(一):绪论

目录 前言 重点一览 语言的分类 冯诺依曼体系结构 绑定的概念 变量 虚拟机 程序单元 本章小结 前言 本复习笔记基于张老师的课堂PPT&#xff0c;供自己期末复习与学弟学妹参考用。 重点一览 语言的分类 命令式语言&#xff08;强制式语言&#xff09;&#xff1a;冯…

Activity的预览窗口StartingWindow添加

Activity的预览窗口StartingWindow添加 1、Activity组件启动2、ActivityStarter.java#startActivityInner() > 主要查看Task.java#startActivityLocked3、ActivityRecord.java#addStartingWindow到WindowManagerService.java#addWindow3.1 ActivityRecord.java#addStartingW…

一文搞定验证码(下部分)

文章目录 1.背景2.验证3.valid接口具体实现类SimpleImageCaptchaValidator 1.背景 上一篇文章讲了验证码生成的逻辑. 验证码-上篇. 大概来说就是: 服务端保存一些默认的验证码图片. 然后需要生成时创建一个包含随机字符的验证码字符图片根据随机字符和一些参数&#xff08;如…

新入职一个00后卷王,每天加班到2点,太让人崩溃了····

在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的&#xff0c;是技术平平&…

chatgpt赋能python:PythonScheme:为什么你应该考虑在你的下一个项目中使用它

Python Scheme&#xff1a;为什么你应该考虑在你的下一个项目中使用它 介绍 Python是一种常用的编程语言&#xff0c;被广泛应用于各种应用程序&#xff0c;包括Web开发、数据分析和人工智能。今天&#xff0c;我们要讨论的是Python编程语言的一种方言&#xff0c;名为“Pyth…

HR不会告诉你!Java程序员月薪8K和20K的区别!

昨天有同学问好程序员&#xff0c;为啥都是干Java程序员&#xff0c;别人可以拿20k&#xff0c;我才拿8k呢&#xff1f;为啥人家能提前转正我就得晚俩月&#xff1f;小源一听大事不妙&#xff0c;赶紧连夜整理了以下清单供大家check&#xff01; 对于刚入职场还有跳槽成功的同学…

【2023 · CANN训练营第一季】昇腾AI入门课(TensorFlow)微认证考试

1、下面哪个AI框架开发模型可以不用适配&#xff0c;直接在昇腾AI处理器上进行训练&#xff1f; A. PyTorch B. Caffe C. Mindspore D. Tensorflow C 2、使用Estimator进行脚本训练开发的一般步骤为&#xff08;A&#xff09; A. 数据预处理 --> 模型构建 --> 运行配置 -…

苏诗:医疗器械企业增长秘籍之CRM系统的 4 大能力建设

近些年&#xff0c;在国家医疗产业政策支持的推动下&#xff0c;医疗器械产业已进入蓬勃发展的“黄金时期”&#xff0c;医疗器械产品丰富度增加&#xff0c;配套服务体系逐渐完善&#xff0c;国产品牌效应进一步凸显。 医疗健康行业是纷享的战略行业之一&#xff0c;自2017年…

chatgpt如何自动生成角色prompt模板

chatgpt如何自动生成角色prompt模板 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、角色prompt模板 下面是套图的chatgpt模板&#xff0c;你可以手动的进行填写。将角色的描述填…

如何把数据从 TDengine 2.x 迁移到 3.x ?

一.迁移背景&#xff1a; 随着时序数据库&#xff08;Time Series Database&#xff09; TDengine 3.0 的发布至今&#xff0c;我们除了在持续地优化产品质量的本身&#xff0c;也一直在努力地提升用户体验。但由于 3.0 底层有大量的重构优化&#xff0c;导致开源版的 2.0 用户…

集权攻击-无权限条件下AD域凭据获取与利用分析

前言 对AD域攻击的前期&#xff0c;在没有任何域内据点或域用户凭据时&#xff0c;攻击者往往会使用用户名枚举、密码爆破、密码喷洒、Roasting等手段进行域用户凭据的窃取&#xff0c;本篇文章将针对AD域攻击时无权限环境下对域用户的信息收集及凭据窃取进行分析。 用户名枚举…

四、CNNs网络架构-深度可分离卷积

《A review of convolutional neural network architectures and their optimizations》论文指出一些高性能的卷积神经网络方法不可避免地带来巨大的计算成本&#xff0c;往往需要高性能GPU或高度优化的分布式CPU架构的支持。尽管CNNs应用向移动终端扩展&#xff0c;但大多数移…

如何在Spring Boot服务端实现公网远程调试并进行HTTP服务监听

文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…

Go语言 -- Web开发基础学习 net/http包

前言 Go 是一个开源的编程语言&#xff0c;它能让构造简单、可靠且高效的软件变得容易。 Go语言最擅长的领域就是Web开发&#xff0c;此贴是本人入门完go语法基础后学习Web开发的学习笔记。 第一个Go Web 新建go文件hello_world.go 写入&#xff1a; package mainimport (&…