Spring Boot 参数统一加解密
Wenhao Wang 2020-07-24 Spring BootAES
在 Spring Boot 中使用更优雅的方式对参数进行加解密
# 1、相关接口
# RequestBodyAdvice
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* <p>对使用了 @RequestBody 注解的 参数进行统一解密</p>
*/
public interface RequestBodyAdvice {
/**
* 该方法用于判断当前请求,是否要执行beforeBodyRead方法
*
* @param methodParameter 方法的参数对象
* @param targetType 方法的参数类型
* @param converterType 将会使用到的Http消息转换器类类型
* @return 返回true则会执行beforeBodyRead
*/
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return false;
}
/**
* 在Http消息转换器执转换,之前执行
*
* @param inputMessage 客户端的请求数据
* @param parameter 方法的参数对象
* @param targetType 方法的参数类型
* @param converterType 将会使用到的Http消息转换器类类型
* @return 返回 一个自定义的HttpInputMessage
*/
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
return null;
}
/**
* 在Http消息转换器执转换,之后执行
*
* @param body 转换后的对象
* @param inputMessage 客户端的请求数据
* @param parameter handler方法的参数类型
* @param targetType 方法的参数类型
* @param converterType 使用的Http消息转换器类类型
* @return 返回一个新的对象
*/
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return null;
}
/**
* 同上,不过这个方法处理的是,body为空的情况
*/
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return null;
}
}
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
59
60
61
62
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
59
60
61
62
# ResponseBodyAdvice
public interface ResponseBodyAdvice<T> {
/**
* @param returnType 响应的数据类型
* @param converterType 最终将会使用的消息转换器
* @return 返回boolean,表示是否要在响应之前执行“beforeBodyWrite” 方法
*/
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
/**
* @param body 响应的数据,也就是响应体
* @param returnType 响应的数据类型
* @param selectedContentType 响应的ContentType
* @param selectedConverterType 最终将会使用的消息转换器
* @param request
* @param response
* @return 被修改后的响应体,可以为null,表示没有任何响应
*/
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2、实现
# 1、解密
# 实现RequestBodyAdvice
或继承RequestBodyAdviceAdapter
import com.leng.urlconversion.common.AesUtil;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.security.GeneralSecurityException;
/**
* <p>对使用了 @RequestBody 注解的 参数进行统一解密</p>
*
* @author Wenhao Wang
* @version 1.0
* @date 2020-07-24
*/
@RestControllerAdvice // 可以通过指定 basePackages指定要作用的范围,默认情况下全局有效
public class RequestBodyDecryptAdvice extends RequestBodyAdviceAdapter {
/**
* 该方法用于判断当前请求,是否要执行beforeBodyRead方法
*
* @param methodParameter 方法的参数对象
* @param targetType 方法的参数类型
* @param converterType 将会使用到的Http消息转换器类类型
* @return 返回true则会执行beforeBodyRead
*/
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// StringHttpMessageConverter 数据与String类型的相互转换
// MappingJackson2HttpMessageConverter 使用Jackson的ObjectMapper转换Json数据
return StringHttpMessageConverter.class.isAssignableFrom(converterType) || MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
/**
* 在Http消息转换器执转换,之前执行
*
* @param inputMessage 客户端的请求数据
* @param parameter 方法的参数对象
* @param targetType 方法的参数类型
* @param converterType 将会使用到的Http消息转换器类类型
* @return 返回 一个自定义的HttpInputMessage
*/
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
// 读取加密的请求体
byte[] body = new byte[inputMessage.getBody().available()];
int read = inputMessage.getBody().read(body);
// 使用 AES 解密
try {
body = AesUtil.decrypt(body, AesUtil.AES_KEY, AesUtil.AES_IV);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
// 使用解密后的数据,构造新的读取流
InputStream newInputStream = new ByteArrayInputStream(body);
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
return newInputStream;
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
}
}
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
仅对使用@RequestBody
注解获取参数进行解密
# 2、加密:
# 实现ResponseBodyAdvice
接口
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leng.urlconversion.common.AesUtil;
import com.leng.urlconversion.common.response.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.security.GeneralSecurityException;
import java.util.Objects;
/**
* <p>响应消息统一使用 AES 加密</p>
*
* @author Wenhao Wang
* @version 1.0
* @date 2020-07-24
*/
@RestControllerAdvice // 可以通过指定 basePackages指定要作用的范围,默认情况下全局有效
public class ResponseBodyEncryptAdvice implements ResponseBodyAdvice<Result<Object>> {
private final Logger log = LoggerFactory.getLogger(ResponseBodyEncryptAdvice.class);
/**
* @param returnType 响应的数据类型
* @param converterType 最终将会使用的消息转换器
* @return 返回boolean,表示是否要在响应之前执行“beforeBodyWrite” 方法
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 返回对象必须是 Message 并且使用了 MappingJackson2HttpMessageConverter
return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
/**
* @param body 响应的数据,也就是响应体
* @param returnType 响应的数据类型
* @param selectedContentType 响应的ContentType
* @param selectedConverterType 最终将会使用的消息转换器
* @param request
* @param response
* @return 被修改后的响应体,可以为null,表示没有任何响应
*/
@Override
public Result<Object> beforeBodyWrite(Result<Object> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
Object data = body.getData();
if (Objects.nonNull(data)) {
// 重写 data,进行加密
try {
String dataJson = new ObjectMapper().writeValueAsString(data);
body.setData(AesUtil.encrypt(dataJson, AesUtil.AES_KEY, AesUtil.AES_IV));
return body;
} catch (JsonProcessingException | GeneralSecurityException e) {
log.error("encrypt data err." + e);
}
}
return null;
}
}
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
59
60
61
62
63
64
65
66
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
59
60
61
62
63
64
65
66
对@ResponseBody
响应参数加密
# 3、AES
工具类
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* <p>AES 加解密工具类</p>
*
* @author Wenhao Wang
* @version 1.0
* @date 2020-07-24
*/
public class AesUtil {
public static final String AES_KEY = "E4NGFkOWQtZmM3ZS00NTJiLWJjYTQtNz";
public static final String AES_IV = "M1ZTU2ZjYtYzdmMC";
/**
* 获得一个 密钥长度为 8*32 = 256 位的 AES 密钥,
*
* @return 返回经 BASE64 处理之后的密钥字符串(并截取 32 字节长度)
*/
public static String getAesStrKey() {
UUID uuid = UUID.randomUUID();
return Base64.getEncoder().encodeToString(uuid.toString().getBytes()).substring(2, 34);
}
/**
* 获得一个初始化向量,初始化向量长度为 4*4 = 16 个字节
*
* @return 返回经 BASE64 处理之后的密钥字符串(并截取 16 字节长度)
*/
public static String getIv() {
UUID uuid = UUID.randomUUID();
return Base64.getEncoder().encodeToString(uuid.toString().getBytes()).substring(2, 18);
}
/**
* 获得 AES key 及 初始化向量 iv
* 其实 iv 和 aesKey 两者的生成并没有什么关系,两者只是对各自的长度有限制,
* 这里只是为了方便使用,进行了一个组合返回。
*
* @return 返回 iv 和 aesKey 的组合
*/
public static HashMap<String, String> getAesKeyAndIv() {
HashMap<String, String> aesKeyAndIv = new HashMap<>(2);
aesKeyAndIv.put("aesKey", AesUtil.getAesStrKey());
aesKeyAndIv.put("iv", AesUtil.getIv());
return aesKeyAndIv;
}
/**
* 加密
*
* @param content 待加密内容
* @param secretKeyStr 加密使用的 AES 密钥,BASE64 编码后的字符串
* @param iv 初始化向量,长度为 16 个字节,16*8 = 128 位
* @return 加密后的密文, 进行 BASE64 处理之后返回
*/
public static String encrypt(String content, String secretKeyStr, String iv) throws GeneralSecurityException {
byte[] encrypt = encrypt(content.getBytes(StandardCharsets.UTF_8), secretKeyStr, iv);
// 执行加密并返回经 BASE64 处助理之后的密文
return new String(encrypt);
}
public static byte[] encrypt(byte[] content, String secretKeyStr, String iv) throws GeneralSecurityException {
// 获得一个 SecretKeySpec
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyStr.getBytes(), "AES");
// 获得加密算法实例对象 Cipher //"算法/模式/补码方式"
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 获得一个 IvParameterSpec
// 使用 CBC 模式,需要一个向量 iv, 可增加加密算法的强度
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
// 根据参数初始化算法
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
// 执行加密并返回经 BASE64 处助理之后的密文
return Base64.getEncoder().encode(cipher.doFinal(content));
}
/**
* 解密
*
* @param content: 待解密内容,是 BASE64 编码后的字符串
* @param secretKeyStr: 解密使用的 AES 密钥,BASE64 编码后的字符串
* @param iv: 初始化向量,长度 16 字节,16*8 = 128 位
* @return 解密后的明文,直接返回经 UTF-8 编码转换后的明文
*/
public static String decrypt(String content, String secretKeyStr, String iv) throws GeneralSecurityException {
byte[] decrypt = decrypt(content.getBytes(StandardCharsets.UTF_8), secretKeyStr, iv);
return new String(decrypt, StandardCharsets.UTF_8);
}
public static byte[] decrypt(byte[] content, String secretKeyStr, String iv) throws GeneralSecurityException {
// 密文进行 BASE64 解密处理
byte[] contentDecByBase64 = Base64.getDecoder().decode(content);
// 获得一个 SecretKeySpec
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyStr.getBytes(), "AES");
// 获得加密算法实例对象 Cipher
// "算法/模式/补码方式"
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 获得一个初始化 IvParameterSpec
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
// 根据参数初始化算法
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
// 解密
return cipher.doFinal(contentDecByBase64);
}
}
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# 4、测试
import com.leng.urlconversion.common.response.Result;
import com.leng.urlconversion.dto.Person;
import org.springframework.web.bind.annotation.*;
/**
* <p></p>
*
* @author Wenhao Wang
* @version 1.0
* @date 2020-07-24
*/
@RestController
@RequestMapping("aes")
public class AesController {
@PostMapping("string")
public Result<String> decryptString(@RequestBody String param) {
// x9Ho1l2udZOPrxVoihc2hD8EQCedsFiw/8fkLpu72O4=
System.err.println("param = " + param);
return Result.success("AES 解密字符串:" + param, param);
}
@PostMapping("int")
public Result<Integer> decryptInt(@RequestBody Integer param) {
// EJ1P5f9F9hBaKjI/htX35w==
System.err.println("param = " + param);
return Result.success("AES 解密字符串:" + param, param);
}
@PostMapping("object")
public Result<Person> decryptString(@RequestBody Person person) {
// L2VpLDhZjAtORl7zdiseMUcgjt8Xd/VHHGUcHVMcXM7KIh5qXwoy3iA1XsuN8yzd
System.err.println("person = " + person);
return Result.success("AES 解密对象:" + person, person);
}
@GetMapping("get/string")
public Result<String> encryptString() {
return Result.success("操作成功~", "我是字符串数据");
}
@GetMapping("get/int")
public Result<Integer> encryptInt() {
return Result.success("操作成功~", 10010);
}
@GetMapping("get/object")
public Result<Person> encryptObject() {
Person person = new Person();
person.setGender("男");
person.setAge(18);
person.setName("张三");
return Result.success("操作成功~", person);
}
}
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
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
# 5、测试涉及到的实体类
# Person
import lombok.Data;
import java.io.Serializable;
/**
* <p></p>
*
* @author Wenhao Wang
* @version 1.0
* @date 2020-07-24
*/
@Data
public class Person implements Serializable {
private static final long serialVersionUID = 4125741114235311479L;
private String name;
private Integer age;
private String gender;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Result
/**
* <p>通用响应类</p>
*
* @author Wenhao Wang
* @version 1.0
* @date 2020-07-24
*/
public class Result<T> {
private Integer code;
private String message;
private T data;
protected Result() {
}
protected Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 成功返回结果
*/
public static <T> Result<T> success() {
return new Result<T>(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getMessage(), null);
}
/**
* 成功返回结果
*
* @param data 获取的数据
*/
public static <T> Result<T> success(T data) {
return new Result<T>(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getMessage(), data);
}
/**
* 成功返回结果
*
* @param data 获取的数据
* @param message 提示信息
*/
public static <T> Result<T> success(String message, T data) {
return new Result<T>(ResultCodeEnum.SUCCESS.getCode(), message, data);
}
/**
* 失败返回结果
*
* @param resultCodeEnum 错误码
*/
public static <T> Result<T> failed(ResultCodeEnum resultCodeEnum) {
return new Result<T>(resultCodeEnum.getCode(), resultCodeEnum.getMessage(), null);
}
/**
* 失败返回结果
*
* @param resultCodeEnum 错误码
* @param message 错误信息
*/
public static <T> Result<T> failed(ResultCodeEnum resultCodeEnum, String message) {
return new Result<T>(resultCodeEnum.getCode(), message, null);
}
/**
* 失败返回结果
*
* @param message 提示信息
*/
public static <T> Result<T> failed(String message) {
return new Result<T>(ResultCodeEnum.FAILED.getCode(), message, null);
}
/**
* 失败返回结果
*/
public static <T> Result<T> failed() {
return failed(ResultCodeEnum.FAILED);
}
/**
* 参数验证失败返回结果
*/
public static <T> Result<T> validateFailed() {
return failed(ResultCodeEnum.VALIDATE_FAILED);
}
/**
* 参数验证失败返回结果
*
* @param message 提示信息
*/
public static <T> Result<T> validateFailed(String message) {
return new Result<T>(ResultCodeEnum.VALIDATE_FAILED.getCode(), message, null);
}
/**
* 未登录返回结果
*/
public static <T> Result<T> unauthorized(T data) {
return new Result<T>(ResultCodeEnum.UNAUTHORIZED.getCode(), ResultCodeEnum.UNAUTHORIZED.getMessage(), data);
}
/**
* 未授权返回结果
*/
public static <T> Result<T> forbidden(T data) {
return new Result<T>(ResultCodeEnum.FORBIDDEN.getCode(), ResultCodeEnum.FORBIDDEN.getMessage(), data);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137