- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Spring MVC请求参数与响应结果全局加密和解密详解由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
前提 。
前段时间在做一个对外的网关项目,涉及到加密和解密模块,这里详细分析解决方案和适用的场景。为了模拟真实的交互场景,先定制一下整个交互流程。第三方传输(包括请求和响应)数据报文包括三个部分:
1、timestamp,long类型,时间戳.
2、data,string类型,实际的业务请求数据转化成的json字符串再进行加密得到的密文.
3、sign,签名,生成规则算法伪代码是sha-256(data=xxx×tamp=11111),防篡改.
为了简单起见,加密和解密采用aes,对称秘钥为"throwable"。上面的场景和加解密例子仅仅是为了模拟真实场景,安全系数低,切勿直接用于生产环境.
现在还有一个地方要考虑,就是无法得知第三方如何提交请求数据,假定都是采用post的http请求方法,提交报文的时候指定contenttype为application/json或者application/x-www-form-urlencoded,两种contenttype提交方式的请求体是不相同的:
1
2
3
4
5
|
//application/x-www-form-urlencoded
timestamp=xxxx&data=yyyyyy&sign=zzzzzzz
//application/json
{
"timestamp"
:xxxxxx,
"data"
:
"yyyyyyyy"
,
"sign"
:
"zzzzzzz"
}
|
最后一个要考虑的地方是,第三方强制要求部分接口需要用明文进行请求,在提供一些接口方法的时候,允许使用明文交互。总结一下就是要做到以下三点:
1、需要加解密的接口请求参数要进行解密,响应结果要进行加密.
2、不需要加解密的接口可以用明文请求.
3、兼容contenttype为application/json或者application/x-www-form-urlencoded两种方式.
上面三种情况要同时兼容算是十分严苛的场景,在生产环境中可能也是极少情况下才遇到,不过还是能找到相对优雅的解决方案。先定义两个特定场景的接口:
1、下单接口(加密) 。
2、订单查询接口(明文) 。
两个接口的contenttype不相同是为了故意复杂化场景,在下面的可取方案中,做法是把application/x-www-form-urlencoded中的形式如xxx=yyy&aaa=bbb的表单参数和application/json中形式如{"key":"value"}的请求参数统一当做application/json形式的参数处理,这样的话,我们就可以直接在控制器方法中使用@requestbody.
方案 。
我们首先基于上面说到的加解密方案,提供一个加解密工具类:
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
|
public
enum
encryptutils {
/**
* singleton
*/
singleton;
private
static
final
string secret =
"throwable"
;
private
static
final
string charset =
"utf-8"
;
public
string sha(string raw)
throws
exception {
messagedigest messagedigest = messagedigest.getinstance(
"sha-256"
);
messagedigest.update(raw.getbytes(charset));
return
hex.encodehexstring(messagedigest.digest());
}
private
cipher createaescipher()
throws
exception {
return
cipher.getinstance(
"aes"
);
}
public
string encryptbyaes(string raw)
throws
exception {
cipher aescipher = createaescipher();
keygenerator keygenerator = keygenerator.getinstance(
"aes"
);
keygenerator.init(
128
,
new
securerandom(secret.getbytes(charset)));
secretkey secretkey = keygenerator.generatekey();
secretkeyspec secretkeyspec =
new
secretkeyspec(secretkey.getencoded(),
"aes"
);
aescipher.init(cipher.encrypt_mode, secretkeyspec);
byte
[] bytes = aescipher.dofinal(raw.getbytes(charset));
return
hex.encodehexstring(bytes);
}
public
string decryptbyaes(string raw)
throws
exception {
byte
[] bytes = hex.decodehex(raw);
cipher aescipher = createaescipher();
keygenerator keygenerator = keygenerator.getinstance(
"aes"
);
keygenerator.init(
128
,
new
securerandom(secret.getbytes(charset)));
secretkey secretkey = keygenerator.generatekey();
secretkeyspec secretkeyspec =
new
secretkeyspec(secretkey.getencoded(),
"aes"
);
aescipher.init(cipher.decrypt_mode, secretkeyspec);
return
new
string(aescipher.dofinal(bytes), charset);
}
}
|
注意为了简化加解密操作引入了apache的codec依赖:
1
2
3
4
5
|
<dependency>
<groupid>commons-codec</groupid>
<artifactid>commons-codec</artifactid>
<version>
1.11
</version>
</dependency>
|
上面的加解密过程中要注意两点:
1、加密后的结果是byte数组,要把二进制转化为十六进制字符串.
2、解密的时候要把原始密文由十六进制转化为二进制的byte数组.
上面两点必须注意,否则会产生乱码,这个和编码相关,具体可以看之前写的一篇博客.
不推荐的方案 。
其实最暴力的方案是直接定制每个控制器的方法参数类型,因为我们可以和第三方磋商哪些请求路径需要加密,哪些是不需要加密,甚至哪些是application/x-www-form-urlencoded,哪些是application/json的请求,这样我们可以通过大量的硬编码达到最终的目标。举个例子:
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
|
@restcontroller
public
class
controller1 {
@autowired
private
objectmapper objectmapper;
@postmapping
(value =
"/order/save"
,
consumes = mediatype.application_form_urlencoded_value,
produces = mediatype.application_json_utf8_value)
public
responseentity<encryptmodel> saveorder(
@requestparam
(name =
"sign"
) string sign,
@requestparam
(name =
"timestamp"
)
long
timestamp,
@requestparam
(name =
"data"
) string data)
throws
exception {
encryptmodel model =
new
encryptmodel();
model.setdata(data);
model.settimestamp(timestamp);
model.setsign(sign);
string inrawsign = string.format(
"data=%s×tamp=%d"
, model.getdata(), model.gettimestamp());
string insign = encryptutils.singleton.sha(inrawsign);
if
(!insign.equals(model.getsign())){
throw
new
illegalargumentexception(
"验证参数签名失败!"
);
}
//这里忽略实际的业务逻辑,简单设置返回的data为一个map
map<string, object> result =
new
hashmap<>(
8
);
result.put(
"code"
,
"200"
);
result.put(
"message"
,
"success"
);
encryptmodel out =
new
encryptmodel();
out.settimestamp(system.currenttimemillis());
out.setdata(encryptutils.singleton.encryptbyaes(objectmapper.writevalueasstring(result)));
string rawsign = string.format(
"data=%s×tamp=%d"
, out.getdata(), out.gettimestamp());
out.setsign(encryptutils.singleton.sha(rawsign));
return
responseentity.ok(out);
}
@postmapping
(value =
"/order/query"
,
consumes = mediatype.application_json_value,
produces = mediatype.application_json_utf8_value)
public
responseentity<order> queryorder(
@requestbody
user user){
order order =
new
order();
//这里忽略实际的业务逻辑
return
responseentity.ok(order);
}
}
|
这种做法能在短时间完成对应的加解密功能,不需要加解密的接口不用引入相关的代码即可。缺陷十分明显,存在硬编码、代码冗余等问题,一旦接口增多,项目的维护难度大大提高。因此,这种做法是不可取的.
混合方案之filter和springmvc的http消息转换器 。
这里先说一点,这里是在springmvc中使用filter。因为要兼容两种contenttype,我们需要做到几点:
1、修改请求头的contenttype为application/json.
2、修改请求体中的参数,统一转化为inputstream.
3、定制url规则,区别需要加解密和不需要加解密的url.
使用filter有一个优点:不需要理解springmvc的流程,也不需要扩展springmvc的相关组件。缺点也比较明显:
1、如果需要区分加解密,只能通过url规则进行过滤.
2、需要加密的接口的springmvc控制器的返回参数必须是加密后的实体类,无法做到加密逻辑和业务逻辑完全拆分,也就是解密逻辑对接收的参数是无感知,但是加密逻辑对返回结果是有感知的.
ps:上面提到的几个需要修改请求参数、请求头等是因为特殊场景的定制,所以如果无此场景可以直接看下面的"单纯的json请求参数和json响应结果"小节。流程大致如下:
编写filter的实现和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
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
|
//customencryptfilter
@requiredargsconstructor
public
class
customencryptfilter
extends
onceperrequestfilter {
private
final
objectmapper objectmapper;
@override
protected
void
dofilterinternal(httpservletrequest request,
httpservletresponse response,
filterchain filterchain)
throws
servletexception, ioexception {
//content-type
string contenttype = request.getcontenttype();
string requestbody =
null
;
boolean
shouldencrypt =
false
;
if
(stringutils.substringmatch(contenttype,
0
, mediatype.application_form_urlencoded_value)) {
shouldencrypt =
true
;
requestbody = convertformtostring(request);
}
else
if
(stringutils.substringmatch(contenttype,
0
, mediatype.application_json_value)) {
shouldencrypt =
true
;
requestbody = convertinputstreamtostring(request.getinputstream());
}
if
(!shouldencrypt) {
filterchain.dofilter(request, response);
}
else
{
customencrypthttpwrapper wrapper =
new
customencrypthttpwrapper(request, requestbody);
wrapper.putheader(
"content-type"
, mediatype.application_problem_json_utf8_value);
filterchain.dofilter(wrapper, response);
}
}
private
string convertformtostring(httpservletrequest request) {
map<string, string> result =
new
hashmap<>(
8
);
enumeration<string> parameternames = request.getparameternames();
while
(parameternames.hasmoreelements()) {
string name = parameternames.nextelement();
result.put(name, request.getparameter(name));
}
try
{
return
objectmapper.writevalueasstring(result);
}
catch
(jsonprocessingexception e) {
throw
new
illegalargumentexception(e);
}
}
private
string convertinputstreamtostring(inputstream inputstream)
throws
ioexception {
return
streamutils.copytostring(inputstream, charset.forname(
"utf-8"
));
}
}
//customencrypthttpwrapper
public
class
customencrypthttpwrapper
extends
httpservletrequestwrapper {
private
final
map<string, string> headers =
new
hashmap<>(
8
);
private
final
byte
[] data;
public
customencrypthttpwrapper(httpservletrequest request, string content) {
super
(request);
data = content.getbytes(charset.forname(
"utf-8"
));
enumeration<string> headernames = request.getheadernames();
while
(headernames.hasmoreelements()) {
string key = headernames.nextelement();
headers.put(key, request.getheader(key));
}
}
public
void
putheader(string key, string value) {
headers.put(key, value);
}
@override
public
string getheader(string name) {
return
headers.get(name);
}
@override
public
enumeration<string> getheaders(string name) {
return
collections.enumeration(collections.singletonlist(headers.get(name)));
}
@override
public
enumeration<string> getheadernames() {
return
collections.enumeration(headers.keyset());
}
@override
public
servletinputstream getinputstream()
throws
ioexception {
bytearrayinputstream inputstream =
new
bytearrayinputstream(data);
return
new
servletinputstream() {
@override
public
boolean
isfinished() {
return
!isready();
}
@override
public
boolean
isready() {
return
inputstream.available() >
0
;
}
@override
public
void
setreadlistener(readlistener listener) {
}
@override
public
int
read()
throws
ioexception {
return
inputstream.read();
}
};
}
@override
public
bufferedreader getreader()
throws
ioexception {
return
super
.getreader();
}
}
//customencryptconfiguration
@configuration
public
class
customencryptconfiguration {
@bean
public
filterregistrationbean<customencryptfilter> customencryptfilter(objectmapper objectmapper){
filterregistrationbean<customencryptfilter> bean =
new
filterregistrationbean<>(
new
customencryptfilter(objectmapper));
bean.addurlpatterns(
"/e/*"
);
return
bean;
}
}
|
控制器代码:
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
|
//可加密的,空接口
public
interface
encryptable {
}
@data
public
class
order
implements
encryptable{
private
long
userid;
}
@data
public
class
encryptresponse<t>
implements
encryptable {
private
integer code;
private
t data;
}
@requiredargsconstructor
@restcontroller
public
class
controller {
private
final
objectmapper objectmapper;
@postmapping
(value =
"/e/order/save"
,
consumes = mediatype.application_json_value,
produces = mediatype.application_json_utf8_value)
public
encryptresponse<order> saveorder(
@requestbody
order order)
throws
exception {
//这里忽略实际的业务逻辑,简单设置返回的data为一个map
encryptresponse<order> response =
new
encryptresponse<>();
response.setcode(
200
);
response.setdata(order);
return
response;
}
@postmapping
(value =
"/c/order/query"
,
consumes = mediatype.application_json_value,
produces = mediatype.application_json_utf8_value)
public
responseentity<order> queryorder(
@requestbody
user user) {
order order =
new
order();
//这里忽略实际的业务逻辑
return
responseentity.ok(order);
}
}
|
这里可能有人有疑问,为什么不在filter做加解密的操作?因为考虑到场景太特殊,要兼容两种形式的表单提交参数,如果在filter做加解密操作,会影响到controller的编码,这就违反了全局加解密不影响到里层业务代码的目标。上面的filter只会拦截url满足/e/*的请求,因此查询接口/c/order/query不会受到影响。这里使用了标识接口用于决定请求参数或者响应结果是否需要加解密,也就是只需要在httpmessageconverter中判断请求参数的类型或者响应结果的类型是否加解密标识接口的子类:
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
|
@requiredargsconstructor
public
class
customencrypthttpmessageconverter
extends
mappingjackson2httpmessageconverter {
private
final
objectmapper objectmapper;
@override
protected
object readinternal(
class
<?> clazz, httpinputmessage inputmessage)
throws
ioexception, httpmessagenotreadableexception {
if
(encryptable.
class
.isassignablefrom(clazz)) {
encryptmodel in = objectmapper.readvalue(streamutils.copytobytearray(inputmessage.getbody()), encryptmodel.
class
);
string inrawsign = string.format(
"data=%s×tamp=%d"
, in.getdata(), in.gettimestamp());
string insign;
try
{
insign = encryptutils.singleton.sha(inrawsign);
}
catch
(exception e) {
throw
new
illegalargumentexception(
"验证参数签名失败!"
);
}
if
(!insign.equals(in.getsign())) {
throw
new
illegalargumentexception(
"验证参数签名失败!"
);
}
try
{
return
objectmapper.readvalue(encryptutils.singleton.decryptbyaes(in.getdata()), clazz);
}
catch
(exception e) {
throw
new
illegalargumentexception(
"解密失败!"
);
}
}
else
{
return
super
.readinternal(clazz, inputmessage);
}
}
@override
protected
void
writeinternal(object object, type type, httpoutputmessage outputmessage)
throws
ioexception, httpmessagenotwritableexception {
class
<?> clazz = (
class
) type;
if
(encryptable.
class
.isassignablefrom(clazz)) {
encryptmodel out =
new
encryptmodel();
out.settimestamp(system.currenttimemillis());
try
{
out.setdata(encryptutils.singleton.encryptbyaes(objectmapper.writevalueasstring(object)));
string rawsign = string.format(
"data=%s×tamp=%d"
, out.getdata(), out.gettimestamp());
out.setsign(encryptutils.singleton.sha(rawsign));
}
catch
(exception e) {
throw
new
illegalargumentexception(
"参数签名失败!"
);
}
super
.writeinternal(out, type, outputmessage);
}
else
{
super
.writeinternal(object, type, outputmessage);
}
}
}
|
自实现的httpmessageconverter主要需要判断请求参数的类型和返回值的类型,从而判断是否需要进行加解密.
单纯的json请求参数和json响应结果的加解密处理最佳实践 。
一般情况下,对接方的请求参数和响应结果是完全规范统一使用json(contenttype指定为application/json,使用@requestbody接收参数),那么所有的事情就会变得简单,因为不需要考虑请求参数由xxx=yyy&aaa=bbb转换为inputstream再交给springmvc处理,因此我们只需要提供一个mappingjackson2httpmessageconverter子类实现(继承它并且覆盖对应方法,添加加解密特性)。我们还是使用标识接口用于决定请求参数或者响应结果是否需要加解密:
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
|
@requiredargsconstructor
public
class
customencrypthttpmessageconverter
extends
mappingjackson2httpmessageconverter {
private
final
objectmapper objectmapper;
@override
protected
object readinternal(
class
<?> clazz, httpinputmessage inputmessage)
throws
ioexception, httpmessagenotreadableexception {
if
(encryptable.
class
.isassignablefrom(clazz)) {
encryptmodel in = objectmapper.readvalue(streamutils.copytobytearray(inputmessage.getbody()), encryptmodel.
class
);
string inrawsign = string.format(
"data=%s×tamp=%d"
, in.getdata(), in.gettimestamp());
string insign;
try
{
insign = encryptutils.singleton.sha(inrawsign);
}
catch
(exception e) {
throw
new
illegalargumentexception(
"验证参数签名失败!"
);
}
if
(!insign.equals(in.getsign())) {
throw
new
illegalargumentexception(
"验证参数签名失败!"
);
}
try
{
return
objectmapper.readvalue(encryptutils.singleton.decryptbyaes(in.getdata()), clazz);
}
catch
(exception e) {
throw
new
illegalargumentexception(
"解密失败!"
);
}
}
else
{
return
super
.readinternal(clazz, inputmessage);
}
}
@override
protected
void
writeinternal(object object, type type, httpoutputmessage outputmessage)
throws
ioexception, httpmessagenotwritableexception {
class
<?> clazz = (
class
) type;
if
(encryptable.
class
.isassignablefrom(clazz)) {
encryptmodel out =
new
encryptmodel();
out.settimestamp(system.currenttimemillis());
try
{
out.setdata(encryptutils.singleton.encryptbyaes(objectmapper.writevalueasstring(object)));
string rawsign = string.format(
"data=%s×tamp=%d"
, out.getdata(), out.gettimestamp());
out.setsign(encryptutils.singleton.sha(rawsign));
}
catch
(exception e) {
throw
new
illegalargumentexception(
"参数签名失败!"
);
}
super
.writeinternal(out, type, outputmessage);
}
else
{
super
.writeinternal(object, type, outputmessage);
}
}
}
|
没错,代码是拷贝上一节提供的httpmessageconverter实现,然后控制器方法的参数使用@requestbody注解并且类型实现加解密标识接口encryptable即可,返回值的类型也需要实现加解密标识接口encryptable。这种做法可以让控制器的代码对加解密完全无感知。当然,也可以不改变原来的mappingjackson2httpmessageconverter实现,使用requestbodyadvice和responsebodyadvice完成相同的功能:
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
|
@requiredargsconstructor
public
class
customrequestbodyadvice
extends
requestbodyadviceadapter {
private
final
objectmapper objectmapper;
@override
public
boolean
supports(methodparameter methodparameter, type targettype,
class
<?
extends
httpmessageconverter<?>> convertertype) {
class
<?> clazz = (
class
) targettype;
return
encryptable.
class
.isassignablefrom(clazz);
}
@override
public
httpinputmessage beforebodyread(httpinputmessage inputmessage, methodparameter parameter, type targettype,
class
<?
extends
httpmessageconverter<?>> convertertype)
throws
ioexception {
class
<?> clazz = (
class
) targettype;
if
(encryptable.
class
.isassignablefrom(clazz)) {
string content = streamutils.copytostring(inputmessage.getbody(), charset.forname(
"utf-8"
));
encryptmodel in = objectmapper.readvalue(content, encryptmodel.
class
);
string inrawsign = string.format(
"data=%s×tamp=%d"
, in.getdata(), in.gettimestamp());
string insign;
try
{
insign = encryptutils.singleton.sha(inrawsign);
}
catch
(exception e) {
throw
new
illegalargumentexception(
"验证参数签名失败!"
);
}
if
(!insign.equals(in.getsign())) {
throw
new
illegalargumentexception(
"验证参数签名失败!"
);
}
bytearrayinputstream inputstream =
new
bytearrayinputstream(in.getdata().getbytes(charset.forname(
"utf-8"
)));
return
new
mappingjacksoninputmessage(inputstream, inputmessage.getheaders());
}
else
{
return
super
.beforebodyread(inputmessage, parameter, targettype, convertertype);
}
}
}
@requiredargsconstructor
public
class
customresponsebodyadvice
extends
jsonviewresponsebodyadvice {
private
final
objectmapper objectmapper;
@override
public
boolean
supports(methodparameter returntype,
class
<?
extends
httpmessageconverter<?>> convertertype) {
class
<?> parametertype = returntype.getparametertype();
return
encryptable.
class
.isassignablefrom(parametertype);
}
@override
protected
void
beforebodywriteinternal(mappingjacksonvalue bodycontainer, mediatype contenttype,
methodparameter returntype, serverhttprequest request,
serverhttpresponse response) {
class
<?> parametertype = returntype.getparametertype();
if
(encryptable.
class
.isassignablefrom(parametertype)) {
encryptmodel out =
new
encryptmodel();
out.settimestamp(system.currenttimemillis());
try
{
out.setdata(encryptutils.singleton.encryptbyaes(objectmapper.writevalueasstring(bodycontainer.getvalue())));
string rawsign = string.format(
"data=%s×tamp=%d"
, out.getdata(), out.gettimestamp());
out.setsign(encryptutils.singleton.sha(rawsign));
out.setsign(encryptutils.singleton.sha(rawsign));
}
catch
(exception e) {
throw
new
illegalargumentexception(
"参数签名失败!"
);
}
}
else
{
super
.beforebodywriteinternal(bodycontainer, contenttype, returntype, request, response);
}
}
}
|
单纯的application/x-www-form-urlencoded表单请求参数和json响应结果的加解密处理最佳实践 。
一般情况下,对接方的请求参数完全采用application/x-www-form-urlencoded表单请求参数返回结果全部按照json接收,我们也可以通过一个httpmessageconverter实现就完成加解密模块.
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
|
public
class
formhttpmessageconverter
implements
httpmessageconverter<object> {
private
final
list<mediatype> mediatypes;
private
final
objectmapper objectmapper;
public
formhttpmessageconverter(objectmapper objectmapper) {
this
.objectmapper = objectmapper;
this
.mediatypes =
new
arraylist<>(
1
);
this
.mediatypes.add(mediatype.application_form_urlencoded);
}
@override
public
boolean
canread(
class
<?> clazz, mediatype mediatype) {
return
encryptable.
class
.isassignablefrom(clazz) && mediatypes.contains(mediatype);
}
@override
public
boolean
canwrite(
class
<?> clazz, mediatype mediatype) {
return
encryptable.
class
.isassignablefrom(clazz) && mediatypes.contains(mediatype);
}
@override
public
list<mediatype> getsupportedmediatypes() {
return
mediatypes;
}
@override
public
object read(
class
<?> clazz, httpinputmessage inputmessage)
throws
ioexception, httpmessagenotreadableexception {
if
(encryptable.
class
.isassignablefrom(clazz)) {
string content = streamutils.copytostring(inputmessage.getbody(), charset.forname(
"utf-8"
));
encryptmodel in = objectmapper.readvalue(content, encryptmodel.
class
);
string inrawsign = string.format(
"data=%s×tamp=%d"
, in.getdata(), in.gettimestamp());
string insign;
try
{
insign = encryptutils.singleton.sha(inrawsign);
}
catch
(exception e) {
throw
new
illegalargumentexception(
"验证参数签名失败!"
);
}
if
(!insign.equals(in.getsign())) {
throw
new
illegalargumentexception(
"验证参数签名失败!"
);
}
try
{
return
objectmapper.readvalue(encryptutils.singleton.decryptbyaes(in.getdata()), clazz);
}
catch
(exception e) {
throw
new
illegalargumentexception(
"解密失败!"
);
}
}
else
{
mediatype contenttype = inputmessage.getheaders().getcontenttype();
charset charset = (contenttype !=
null
&& contenttype.getcharset() !=
null
?
contenttype.getcharset() : charset.forname(
"utf-8"
));
string body = streamutils.copytostring(inputmessage.getbody(), charset);
string[] pairs = stringutils.tokenizetostringarray(body,
"&"
);
multivaluemap<string, string> result =
new
linkedmultivaluemap<>(pairs.length);
for
(string pair : pairs) {
int
idx = pair.indexof(
'='
);
if
(idx == -
1
) {
result.add(urldecoder.decode(pair, charset.name()),
null
);
}
else
{
string name = urldecoder.decode(pair.substring(
0
, idx), charset.name());
string value = urldecoder.decode(pair.substring(idx +
1
), charset.name());
result.add(name, value);
}
}
return
result;
}
}
@override
public
void
write(object o, mediatype contenttype, httpoutputmessage outputmessage)
throws
ioexception, httpmessagenotwritableexception {
class
<?> clazz = o.getclass();
if
(encryptable.
class
.isassignablefrom(clazz)) {
encryptmodel out =
new
encryptmodel();
out.settimestamp(system.currenttimemillis());
try
{
out.setdata(encryptutils.singleton.encryptbyaes(objectmapper.writevalueasstring(o)));
string rawsign = string.format(
"data=%s×tamp=%d"
, out.getdata(), out.gettimestamp());
out.setsign(encryptutils.singleton.sha(rawsign));
streamutils.copy(objectmapper.writevalueasstring(out)
.getbytes(charset.forname(
"utf-8"
)), outputmessage.getbody());
}
catch
(exception e) {
throw
new
illegalargumentexception(
"参数签名失败!"
);
}
}
else
{
string out = objectmapper.writevalueasstring(o);
streamutils.copy(out.getbytes(charset.forname(
"utf-8"
)), outputmessage.getbody());
}
}
}
|
上面的httpmessageconverter的实现可以参考org.springframework.http.converter.formhttpmessageconverter.
小结 。
这篇文章强行复杂化了实际的情况(但是在实际中真的碰到过),一般情况下,现在流行使用json进行数据传输,在springmvc项目中,我们只需要针对性地改造mappingjackson2httpmessageconverter即可(继承并且添加特性),如果对springmvc的源码相对熟悉的话,直接添加自定义的requestbodyadvice(requestbodyadviceadapter)和responsebodyadvice(jsonviewresponsebodyadvice)实现也可以达到目的。至于为什么使用httpmessageconverter做加解密功能,这里基于springmvc源码的对请求参数处理的过程整理了一张处理流程图:
上面流程最核心的代码可以看abstractmessageconvertermethodargumentresolver#readwithmessageconverters和handlermethodargumentresolvercomposite#resolveargument,毕竟源码不会骗人。控制器方法返回值的处理基于是对称的,阅读起来也比较轻松.
参考资料:
spring-boot-web-starter:2.0.3.release源码.
总结 。
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我的支持.
原文链接:https://www.cnblogs.com/throwable/p/9471938.html 。
最后此篇关于Spring MVC请求参数与响应结果全局加密和解密详解的文章就讲到这里了,如果你想了解更多关于Spring MVC请求参数与响应结果全局加密和解密详解的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试检查 Entry 中是否存在重复项,并使用内联消息提醒用户该数字存在。 $(document).ready(function(){ $("#con1").blur(function(
我有一个基于类的 View 。我在引导模式上使用 Ajax。为了避免页面刷新,我想使用此类基于 View 返回 JSON 响应而不是 HTTP 响应,但我只看到了如何为基于函数的 View 返回 JS
关闭。这个问题是not reproducible or was caused by typos .它目前不接受答案。 这个问题是由于错别字或无法再重现的问题引起的。虽然类似的问题可能是on-topi
我有一个大型内部企业基于 Web 的应用程序在 IIS6 上运行 ASP.NET 3.5,生成 401 个“未经授权”响应,然后是 200 个“Ok”响应(如 Fiddler 所述)。我知道为什么会发
感谢您研究我的问题。 我有一个node/express服务器,配置了一个server.js文件,它调用urls.js,而urls.js又调用 Controller 来处理http请求,所有这些都配置相
当我使用以下命令时,我得到正确的 JSON 响应: $ curl --data "regno=&dob=&mobile=" https://vitacademics-rel.herokuapp.co
我有一个非常简单的 RESTful 服务,它通过 POST 接收一些表单数据,其目的是在云存储(Amazon S3、Azure Blob 存储等)中简单地保留文本主体(具有唯一 ID)作为一个文件..
UDP 不发送任何 ack,但它会发送任何响应吗? 我已经设置了客户端服务器UDP程序。如果我让客户端向不存在的服务器发送数据,那么客户端会收到任何响应吗? 我的假设是; 客户端 --> 广播服务器地
我有一个电梯项目,其中 有一个扩展 RestHelper 的类,看起来像这样 serve{ "api" / "mystuff" prefix { case a
我们正在寻求覆盖 Kong 错误响应结构并编写自定义消息(即用我们的自定义消息替换“超出 API 速率限制”、“无效的身份验证凭据”等)。 我们要找的错误响应结构(代码是自定义的内部错误代码,与HTT
我正在尝试监听 EKEventStoreChangedNotification 以检查当我的应用程序处于后台时日历是否已更改。 我在 View Controller 的 initWithNibMeth
我了解 javascript,并且正在学习 ASP.NET C# 我想要做什么(完成的是javascript): document.getElementById('divID-1'
是否可以过滤所有 har 对象并仅获取 POST 请求/响应?也许在初始化 BrowserMobProxyServer 期间是这样做的方法?我需要将 har 对象保存到文件中并上传到 har 查看器。
我正在尝试向 Oauth 的 API 发送响应。遗憾的是,Symfony2 文档在解释 $response->headers->set(...); 的所有不同部分方面做得很差。 这是我的 OauthC
我正在尝试测试用例来模拟 api 调用,并使用 python 响应来模拟 api 调用。 下面是我的模拟, with responses.RequestsMock() as rsps: url
在尝试在 Haskell 中进行一些领域驱动设计时,我发现自己遇到了这个问题: data FetchAccessories = FetchAccessories data AccessoriesRes
我正在与 ANT+ USB 棒连接,并用项目 react 器替换我自己天真的“MessageBus”,因为它看起来非常合适。 USB接口(interface)本质上是异步的(单独的输入/输出管道),我
我正在将项目迁移到AFNetworking 2.0。使用AFNetworking 1.0时,我编写了代码来记录控制台中的每个请求/响应。这是代码: -(AFHTTPRequestOperation *
我有以下代码段。 ajaxRequest.onreadystatechange = function(){ if(ajaxRequest.readyState == 4){
我有问题......我在 php 中有一个监听器脚本可以执行以下操作: if ($count != 1) {echo 'no';} else { echo "yes";} 因此它会回显"is"或“
我是一名优秀的程序员,十分优秀!