1. 介绍
在我们一样寻常的Java开发中,免不了和其他体系的业务交互,大概微服务之间的接口调用
如果我们想包管数据传输的安全,对接口出参加密,入参解密。
但是不想写重复代码,我们可以提供一个通用starter,提供通用加密解密功能
2. 前置知识
2.1 hutool-crypto加密解密工具
hutool-crypto提供了很多加密解密工具,包罗对称加密,非对称加密,择要加密等等,这不做详细介绍。
2.2 request流只能读取一次的题目
2.2.1 题目:
在接口调用链中,request的请求流只能调用一次,处置惩罚之后,如果之后还须要用到请求流获取数据,就会发现数据为空。
比如利用了filter大概aop在接口处置惩罚之前,获取了request中的数据,对参数举行了校验,那么之后就不能在获取request请求流了
2.2.2 办理办法
继承HttpServletRequestWrapper,将请求中的流copy一份,复写getInputStream和getReader方法供外部利用。每次调用后的getInputStream方法都是从复制出来的二进制数组中举行获取,这个二进制数组在对象存在期间划一存在。
利用Filter过滤器,在一开始,更换request为本身定义的可以多次读取流的request。
如许就实现了流的重复获取
InputStreamHttpServletRequestWrapper
package xyz.hlh.cryptotest.utils;import org.apache.commons.io.IOUtils;import javax.servlet.ReadListener;import javax.servlet.ServletInputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.io.BufferedReader;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStreamReader;/** * 请求流支持多次获取 */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); } /** * 读取缓存的请求正文的输入流 * <p> * 用于根据 缓存输入流 创建一个可返回的 */ 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(); } }}HttpServletRequestInputStreamFilter
package xyz.hlh.cryptotest.filter;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import xyz.hlh.cryptotest.utils.InputStreamHttpServletRequestWrapper;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;/** * @author HLH * @description: * 请求流转换为多次读取的请求流 过滤器 * @email 17703595860@163.com * @date : Created in 2022/2/4 9:58 */@Component@Order(HIGHEST_PRECEDENCE + 1) // 优先级最高public class HttpServletRequestInputStreamFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 转换为可以多次获取流的request HttpServletRequest httpServletRequest = (HttpServletRequest) request; InputStreamHttpServletRequestWrapper inputStreamHttpServletRequestWrapper = new InputStreamHttpServletRequestWrapper(httpServletRequest); // 放行 chain.doFilter(inputStreamHttpServletRequestWrapper, response); }}2.3 SpringBoot的参数校验validation
为了镌汰接口中,业务代码之前的大量冗余的参数校验代码
SpringBoot-validation提供了优雅的参数校验,入参都是实体类,在实体类字段上加上对应注解,就可以在进入方法之前,举行参数校验,如果参数错误,会抛堕落误BindException,是不会进入方法的。
这种方法,必须要求在接口参数上加注解@Validated大概是@Valid
但是很多清空下,我们渴望在代码中调用某个实体类的校验功能,以是须要如下工具类
ParamException
package xyz.hlh.cryptotest.exception;import lombok.Getter;import java.util.List;/** * @author HLH * @description 自定义参数异常 * @email 17703595860@163.com * @date Created in 2021/8/10 下战书10:56 */@Getterpublic class ParamException extends Exception { private final List<String> fieldList; private final List<String> msgList; public ParamException(List<String> fieldList, List<String> msgList) { this.fieldList = fieldList; this.msgList = msgList; }}ValidationUtils
package xyz.hlh.cryptotest.utils;import xyz.hlh.cryptotest.exception.CustomizeException;import xyz.hlh.cryptotest.exception.ParamException;import javax.validation.ConstraintViolation;import javax.validation.Validation;import javax.validation.Validator;import java.util.LinkedList;import java.util.List;import java.util.Set;/** * @author HLH * @description 验证工具类 * @email 17703595860@163.com * @date Created in 2021/8/10 下战书10:56 */public class ValidationUtils { private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); /** * 验证数据 * @param object 数据 */ public static void validate(Object object) throws CustomizeException { Set<ConstraintViolation<Object>> validate = VALIDATOR.validate(object); // 验证结果异常 throwParamException(validate); } /** * 验证数据(分组) * @param object 数据 * @param groups 所在组 */ public static void validate(Object object, Class<?> ... groups) throws CustomizeException { Set<ConstraintViolation<Object>> validate = VALIDATOR.validate(object, groups); // 验证结果异常 throwParamException(validate); } /** * 验证数据中的某个字段(分组) * @param object 数据 * @param propertyName 字段名称 */ public static void validate(Object object, String propertyName) throws CustomizeException { Set<ConstraintViolation<Object>> validate = VALIDATOR.validateProperty(object, propertyName); // 验证结果异常 throwParamException(validate); } /** * 验证数据中的某个字段(分组) * @param object 数据 * @param propertyName 字段名称 * @param groups 所在组 */ public static void validate(Object object, String propertyName, Class<?> ... groups) throws CustomizeException { Set<ConstraintViolation<Object>> validate = VALIDATOR.validateProperty(object, propertyName, groups); // 验证结果异常 throwParamException(validate); } /** * 验证结果异常 * @param validate 验证结果 */ private static void throwParamException(Set<ConstraintViolation<Object>> validate) throws CustomizeException { if (validate.size() > 0) { List<String> fieldList = new LinkedList<>(); List<String> msgList = new LinkedList<>(); for (ConstraintViolation<Object> next : validate) { fieldList.add(next.getPropertyPath().toString()); msgList.add(next.getMessage()); } throw new ParamException(fieldList, msgList); } }}2.4 自定义starter
自定义starter步调
- 创建工厂,编写功能代码
- 声明主动设置类,把须要对外提供的对象创建好,通过设置类同一向外袒露
- 在resource目次下准备一个名为spring/spring.factories的文件,以org.springframework.boot.autoconfigure.EnableAutoConfiguration为key,主动设置类为value列表,举行注册
2.5 RequestBodyAdvice和ResponseBodyAdvice
- RequestBodyAdvice是对请求的json串举行处置惩罚, 一样寻常利用环境是处置惩罚接口参数的主动解密
- ResponseBodyAdvice是对请求相应的jsoin传举行处置惩罚,一样寻常用于相应结果的加密
3. 功能介绍
接口相应数据的时间,返回的是加密之后的数据 接口入参的时间,接收的是解密之后的数据,但是在进入接口之前,会主动解密,取得对应的数据
4. 功能细节
加密解密利用对称加密的AES算法,利用hutool-crypto模块举行实现
全部的实体类提取一个公共父类,包罗属性时间戳,用于加密数据返回之后的实效性,如果凌驾60分钟,那么其他接口将不举行处置惩罚。
如果接口加了加密注解EncryptionAnnotation,而且返回同一的json数据Result类,则主动对数据举行加密。如果是继承了同一父类RequestBase的数据,主动注入时间戳,确保数据的时效性
如果接口加相识密注解DecryptionAnnotation,而且参数利用RequestBody注解标注,传入json利用同一格式RequestData类,而且内容是继承了包罗时间长的父类RequestBase,则主动解密,而且转为对应的数据类型
功能提供Springboot的starter,实现开箱即用
5. 代码实现
https://gitee.com/springboot-hlh/spring-boot-csdn/tree/master/09-spring-boot-interface-crypto
5.1 项目布局
5.2 crypto-common
5.2.1 布局
5.3 crypto-spring-boot-starter
5.3.1 接口
5.3.2 告急代码
crypto.properties AES须要的参数设置 |