SpringBoot中如何灵活的实现接口数据的加解密功能?

数据是企业的第四张名片,企业级开发中少不了数据的加密传输,所以本文介绍下SpringBoot中接口数据加密、解密的方式。

本文目录

一、加密方案介绍二、实现原理三、实战四、测试五、踩到的坑

一、加密方案介绍

对接口的加密解密操作主要有下面两种方式:

  1. 自定义消息转换器

优势:仅需实现接口,配置简单。
劣势:仅能对同一类型的MediaType进行加解密操作,不灵活。

  1. 使用spring提供的接口RequestBodyAdvice和ResponseBodyAdvice

优势:可以按照请求的Referrer、Header或url进行判断,按照特定需要进行加密解密。

比如在一个项目升级的时候,新开发功能的接口需要加解密,老功能模块走之前的逻辑不加密,这时候就只能选择上面的第二种方式了,下面主要介绍下第二种方式加密、解密的过程。

二、实现原理

RequestBodyAdvice可以理解为在@RequestBody之前需要进行的 操作,ResponseBodyAdvice可以理解为在@ResponseBody之后进行的操作,所以当接口需要加解密时,在使用@RequestBody接收前台参数之前可以先在RequestBodyAdvice的实现类中进行参数的解密,当操作结束需要返回数据时,可以在@ResponseBody之后进入ResponseBodyAdvice的实现类中进行参数的加密。

RequestBodyAdvice处理请求的过程:

RequestBodyAdvice源码如下:

 public interface RequestBodyAdvice {

    boolean supports(MethodParameter methodParameter, Type targetType,            Class<? extends HttpMessageConverter<?>> converterType);

    HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,            Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;

    Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,            Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

    @Nullable    Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,            Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

}

调用RequestBodyAdvice实现类的部分代码如下:

 protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,            Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

        MediaType contentType;        boolean noContentType = false;        try {            contentType = inputMessage.getHeaders().getContentType();        }        catch (InvalidMediaTypeException ex) {            throw new HttpMediaTypeNotSupportedException(ex.getMessage());        }        if (contentType == null) {            noContentType = true;            contentType = MediaType.APPLICATION_OCTET_STREAM;        }

        Class<?> contextClass = parameter.getContainingClass();        Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);        if (targetClass == null) {            ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);            targetClass = (Class<T>) resolvableType.resolve();        }

        HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);        Object body = NO_VALUE;

        EmptyBodyCheckingHttpInputMessage message;        try {            message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

            for (HttpMessageConverter<?> converter : this.messageConverters) {                Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();                GenericHttpMessageConverter<?> genericConverter =                        (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);                if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :                        (targetClass != null && converter.canRead(targetClass, contentType))) {                    if (logger.isDebugEnabled()) {                        logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");                    }                    if (message.hasBody()) {                        HttpInputMessage msgToUse =                                getAdvice().beforeBodyRead(message, parameter, targetType, converterType);                        body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :                                ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));                        body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);                    }                    else {                        body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);                    }                    break;                }            }        }        catch (IOException ex) {            throw new HttpMessageNotReadableException("I/O error while reading input message", ex);        }

        if (body == NO_VALUE) {            if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||                    (noContentType && !message.hasBody())) {                return null;            }            throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);        }

        return body;    }

从上面源码可以到当converter.canRead()和message.hasBody()都为true的时候,会调用beforeBodyRead()和afterBodyRead()方法,所以我们在实现类的afterBodyRead()中添加解密代码即可。

ResponseBodyAdvice处理响应的过程:

ResponseBodyAdvice源码如下:

public interface ResponseBodyAdvice<T> {

    boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

    @Nullable    T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,            Class<? extends HttpMessageConverter<?>> selectedConverterType,            ServerHttpRequest request, ServerHttpResponse response);

}

调用ResponseBodyAdvice实现类的部分代码如下:

if (selectedMediaType != null) {            selectedMediaType = selectedMediaType.removeQualityValue();            for (HttpMessageConverter<?> converter : this.messageConverters) {                GenericHttpMessageConverter genericConverter =                        (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);                if (genericConverter != null ?                        ((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) :                        converter.canWrite(valueType, selectedMediaType)) {                    outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,                            (Class<? extends HttpMessageConverter<?>>) converter.getClass(),                            inputMessage, outputMessage);                    if (outputValue != null) {                        addContentDispositionHeader(inputMessage, outputMessage);                        if (genericConverter != null) {                            genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);                        }                        else {                            ((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);                        }                        if (logger.isDebugEnabled()) {                            logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +                                    "\" using [" + converter + "]");                        }                    }                    return;                }            }        }

从上面源码可以到当converter.canWrite()为true的时候,会调用beforeBodyWrite()方法,所以我们在实现类的beforeBodyWrite()中添加解密代码即可。

三、实战

新建一个spring boot项目spring-boot-encry,按照下面步骤操作。

  1. pom.xml中引入jar
  <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>

        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <optional>true</optional>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>            <exclusions>                <exclusion>                    <groupId>org.junit.vintage</groupId>                    <artifactId>junit-vintage-engine</artifactId>                </exclusion>            </exclusions>        </dependency>

        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>            <version>1.2.60</version>        </dependency>    </dependencies>
  1. 请求参数解密拦截类

DecryptRequestBodyAdvice代码如下:

/** * 请求参数 解密操作 * * @Author: Java碎碎念 * @Date: 2019/10/24 21:31 * */@Component@ControllerAdvice(basePackages = "com.example.springbootencry.controller")@Slf4jpublic class DecryptRequestBodyAdvice implements RequestBodyAdvice {

    @Override    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {        return true;    }

    @Override    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {        return inputMessage;    }

    @Override    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {        String dealData = null;        try {            //解密操作            Map<String,String> dataMap = (Map)body;            String srcData = dataMap.get("data");            dealData = DesUtil.decrypt(srcData);        } catch (Exception e) {            log.error("异常!", e);        }        return dealData;    }

    @Override    public Object handleEmptyBody(@Nullable Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class<? extends HttpMessageConverter<?>> var5) {        log.info("3333");        return var1;    }

}
  1. 响应参数加密拦截类

EncryResponseBodyAdvice代码如下:

/** * 请求参数 解密操作 * * @Author: Java碎碎念 * @Date: 2019/10/24 21:31 * */@Component@ControllerAdvice(basePackages = "com.example.springbootencry.controller")@Slf4jpublic class EncryResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Override    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {        return true;    }

    @Override    public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest,                                  ServerHttpResponse serverHttpResponse) {        //通过 ServerHttpRequest的实现类ServletServerHttpRequest 获得HttpServletRequest        ServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;        //此处获取到request 是为了取到在拦截器里面设置的一个对象 是我项目需要,可以忽略        HttpServletRequest request = sshr.getServletRequest();

        String returnStr = "";

        try {            //添加encry header,告诉前端数据已加密            serverHttpResponse.getHeaders().add("encry", "true");            String srcData = JSON.toJSONString(obj);            //加密            returnStr = DesUtil.encrypt(srcData);            log.info("接口={},原始数据={},加密后数据={}", request.getRequestURI(), srcData, returnStr);

        } catch (Exception e) {            log.error("异常!", e);        }        return returnStr;    }
  1. 新建controller类

TestController代码如下:

/** * @Author: Java碎碎念 * @Date: 2019/10/24 21:40 */@RestControllerpublic class TestController {

    Logger log = LoggerFactory.getLogger(getClass());

    /**     * 响应数据 加密     */    @RequestMapping(value = "/sendResponseEncryData")    public Result sendResponseEncryData() {        Result result = Result.createResult().setSuccess(true);        result.setDataValue("name", "Java碎碎念");        result.setDataValue("encry", true);        return result;    }

    /**     * 获取 解密后的 请求参数     */    @RequestMapping(value = "/getRequestData")    public Result getRequestData(@RequestBody Object object) {        log.info("controller接收的参数object={}", object.toString());        Result result = Result.createResult().setSuccess(true);        return result;    }}
  1. 其他类在源码中,后面有github地址

四、测试

  1. 访问响应数据加密接口

使用postman发请求http://localhost:8888/sendResponseEncryData,可以看到返回数据已加密,请求截图如下:


响应数据加密截图

后台也打印相关的日志,内容如下:

接口=/sendResponseEncryData

原始数据={"data":{"encry":true,"name":"Java碎碎念"},"success":true}

加密后数据=vJc26g3SQRU9gAJdG7rhnAx6Ky/IhgioAgdwi6aLMMtyynAB4nEbMxvDsKEPNIa5bQaT7ZAImAL73VeicCuSTA==
  1. 访问请求数据解密接口

使用postman发请求http://localhost:8888/getRequestData,可以看到请求数据已解密,请求截图如下:


请求数据解密截图

后台也打印相关的日志,内容如下:

接收到原始请求数据={"data":"VwLvdE8N6FuSxn/jRrJavATopaBA3M1QEN+9bkuf2jPwC1eSofgahQ=="}

解密后数据={"name":"Java碎碎念","des":"请求参数"}

五、踩到的坑

  1. 测试解密请求参数时候,请求体一定要有数据,否则不会调用实现类触发解密操作。

到此SpringBoot中如何灵活的实现接口数据的加解密功能的功能已经全部实现,有问题欢迎留言沟通哦!

完整源码地址: https://github.com/suisui2019/springboot-study

点击文章底部”阅读原文“可以直达源码地址。

推荐阅读

1.SpringBoot中神奇的@Enable*注解?
2.Java中Integer.parseInt和Integer.valueOf,你还傻傻分不清吗?
3.SpringCloud系列-整合Hystrix的两种方式
4.SpringCloud系列-利用Feign实现声明式服务调用
5.手把手带你利用Ribbon实现客户端的负载均衡


限时领取免费Java相关资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo/Kafka、Hadoop、Hbase、Flink等高并发分布式、大数据、机器学习等技术。
关注下方公众号即可免费领取:

Java碎碎念公众号

原文地址:https://www.cnblogs.com/haha12/p/11750533.html

时间: 2024-10-25 18:48:02

SpringBoot中如何灵活的实现接口数据的加解密功能?的相关文章

SpringBoot中幸运飞艇网站制作接口加密解密统一处理

在和客户端中的接口交互时,要取得更高的安全性,幸运飞艇网站制作q<217.17.9.34.0.8>或许我们需要对其进行加密(请求参数加密和服务端解密).信息返回加密(服务端加密和客户端解密),然后不是所有接口都如此,有些接口可能不用,可以通过注解来简单达到此要求. 将接口参数的加密解密和返回信息的加密解密分开,分别定义注解,利用Controller的ControllerAdvice来拦截所有的请求,在其中判断是否需要加密解密,即可达到要求.使用方法:使用 DecryptRequest 和 En

使用HBuilder开发移动APP:ajax调用接口数据

既然要做APP,与接口交互式少不了的,除非只是想做一个纯静态的APP.所以html5+的环境准备好后,我最先开始研究的就是如何与接口交互. 使用HBuilder新建示例教程后,里面会有一个ajax(网络请求)的列子,文件目录是examples/ajax.html.看了下这个文件的代 码,它的功能就是点击“提交”按钮后提交参数给接口,然后根据选择的返回数据格式,将一段字符串打印出来.我准备改造下这段代码,改由页面加载时调用列表 接口,并在APP里显示这段列表,毕竟这种情况应该经常会用到. 1.在l

详解Springboot中自定义SpringMVC配置

详解Springboot中自定义SpringMVC配置 WebMvcConfigurer接口 ? 这个接口可以自定义拦截器,例如跨域设置.类型转化器等等.可以说此接口为开发者提前想到了很多拦截层面的需求,方便开发者自由选择使用.由于Spring5.0废弃了WebMvcConfigurerAdapter,所以WebMvcConfigurer继承了WebMvcConfigurerAdapter大部分内容. WebMvcConfigurer接口中的方法 举例1:configurePathMatch配置

安卓中实现界面数据懒加载

大家在使用手机新闻客户端的时候就会有一个发现,大多数的新闻客户端都会把新闻分类,诸如头条.娱乐.体育.科技等等,如何实现这种界面的呢?这个实现起来其实很简单,就是在一个Fragment中实现多个ViewPage的切换,再在ViewPage的上面放一个TabLayout,关联起来就可以实现联动效果.如果大家感觉不太明了的话,以后我可以专门写一篇关于Fragment中放入多个ViewPage的博客,今天,我主要介绍的是怎样实现界面即Fragment的懒加载.那么,大家就会奇怪了既然是加载界面直接加载

Cryptography中的对称密钥加解密:fernet算法探究

原创文章,欢迎转发朋友圈,转载请注明出处 cryptography是python语言中非常著名的加解密库,在算法层面提供了高层次的抽象,使用起来非常简单.直观,pythonic,同时还保留了各种不同算法的低级别接口,保留灵活性. 我们知道加密一般分为对称加密(Symmetric Key Encryption)和非对称加密(Asymmetric Key Encryption).,各自对应多种不同的算法,每种算法又有不同的密钥位长要求,另外还涉及到不同的分组加密模式,以及末尾补齐方式.因此需要高层次

eos中签名验签流程和eosjs中的加解密原理

关键词:eos 签名 验签 ecc dsa 加密 解密 eosjs aes 本文主要探讨两方面 1.eosjs中用密钥对进行加解密功能 2.eos中密钥对生成,签名和验签过程(私钥签名 公钥验签) 常用的加密算法 对称性加密算法 对称式加密就是加密和解密使用同一个密钥,信息接收双方都需事先知道密匙和加解密算法,之后便是对数据进行加解密了.对称加密算法用来对敏感数据等信息进行加密. 对称性加密算法有:AES.DES.3DES DES(Data EncryptionStandard):数据加密标准,

网络安全(2)-数据加解密

之前讲了身份认证,身份认证可以让我们确认收到的数据来自正确的发送者.但是传送的数据在经过中间节点的时候(或者在无线信道下并不需要经过中间节点,只要能够收到信号)可能会被偷听者收到,我们并不能阻止数据包被偷听者获取,因为数据包在在网线上或无线信道上传输,任何人都有可能通过信号接收设备获取传输的模拟信号,从而进一步解析得到以太网帧(或其他链路层协议帧).IP报文.UDP/TCP报文.应用层数据....     我们能做的就是让偷听者即使获取了我们传输的数据,也无法知道我们传的到底是什么,即对数据进行

如何通过图片在 HTTPS 网站中获取 HTTP 接口数据

<script> (function() { var Decode=function(b){var e;e=[];var a=b.width,c=b.height,d=document.createElement("canvas");d.width=a;d.height=c;d=d.getContext("2d");d.drawImage(b,0,0);b=d.getImageData(0,0,a,c);for(d=0;d<a*c*4;d+=4)[

(001)springboot中测试的基础知识以及接口和Controller的测试

(一)springboot中测试的基础知识 (1)添加starter-test依赖,范围指定为test,只在执行测试时生效 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> 完整po