Spring Boot 请求统一的加密与解密

背景

之前项目中遇到,因为接口比较敏感,设计隐私数据通过参数传递,所以需要对传递的每个参数进行分别加密。之前做法是继承HttpServletRequestWrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
private class ModifyParametersWrapper extends HttpServletRequestWrapper {

private final Map<String, Object> parameterMap = Maps.newHashMap();

public ModifyParametersWrapper(final HttpServletRequest request) {
super(request);

for (final Entry<String, String[]> e : request.getParameterMap().entrySet()) {
this.parameterMap.put(e.getKey(), e.getValue()[0]);
}
//获取解密方式
final String storeKey = ParamFilter.keyCache.getAESKey("xxx");
ParamFilter.coder.decode(this.parameterMap, storeKey);
}

// 重写几个HttpServletRequestWrapper中的方法

/**
* 获取所有参数名
*
* @return 返回所有参数名
*/
@Override
public Enumeration<String> getParameterNames() {
final Vector<String> vector = new Vector<>(this.parameterMap.keySet());
return vector.elements();
}

@Override
public String[] getParameterValues(final String name) {
final Object result = this.parameterMap.get(name);
if (result == null) {
return null;
}
return new String[] {result.toString()};
}

@Override
public String getParameter(final String name) {
final Object value = this.parameterMap.get(name);
return value.toString();
}

@Override
public Map<String, String[]> getParameterMap() {

final Map<String, String[]> result = Maps.newHashMap();
for (final Entry<String, Object> e : this.parameterMap.entrySet()) {
Object value = e.getValue();
if(value == null){
continue;
}
result.put(e.getKey(), new String[] {e.getValue().toString()});
}
return result;
}

}

然后通过Filter拦截请求,并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain)
throws IOException, ServletException {

final HttpServletRequest request = (HttpServletRequest)servletRequest;
final String url = request.getRequestURI().substring(request.getContextPath().length());

final String storeKey = ParamFilter.keyCache.getAESKey("xxxx");
if (isExclude(url) || StringUtils.isBlank(storeKey)) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
final ModifyParametersWrapper mParametersWrapper = new ModifyParametersWrapper(request);
filterChain.doFilter(mParametersWrapper, servletResponse);
}

}

另外,再请求发起的时候用相同的加密方式对每个参数分别加密,当然这种情况比较特殊,要求对每个参数加密,可以动态修改或者添加参数,校验签名,但是,如果我们请求过了,requestbody就是json,然后我们加密一次,这样更优雅。

RequestBodyAdvice和ResponseBodyAdvice

需要适配器类上加上@ControllerAdvice注解,才能起作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface RequestBodyAdvice {

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

Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,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);

}


public interface ResponseBodyAdvice<T> {

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

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

}
  • supports:判断当前参数是否需要使用自定义的适配器来处理;
  • beforeBodyRead:HTTP内容被转化为对象前执行的方法;
  • afterBodyRead:HTTP内容被消息转换器转换为对象后执行的方法;

好,接下来我们写一个Starter,能很容易引入SpringBoot,我们的目标是能通过注解来实现加密和解密。

实现

算法分类

根据加密结果是否可以被解密,算法可以分为可逆加密和不可逆加密(单向加密),从这个意义上来说,单向加密只能称之为加密算法而不是加解密算法。对于可逆加密,又可以根据密钥的的对称性分为对称加密和非对称加密。具体的分类结构如下:

  • 可逆加密
    • 对称加密:DES,3DES,AES,PBE
    • 非对称加密:RSA,DSA,ECC(椭圆曲线加密算法)
  • 不可逆加密(单向加密):MD5,SHA,HMAC

很显然,如果是签名,我们可以用不可逆加密,如果是body体的话,我们需要用可逆算法,去解出相应数据才能进行业务处理。

Codes:https://github.com/liuhailin/encrypt-body-spring-boot-starter

坚持技术分享,您的支持将鼓励我继续创作!