从通信协议到装饰者模式

背景

最近有个新的需求,jdbc与后台的通信要求使用第三方提供的硬件加密卡进行加密。当前数据库与前台工具的通信有两种方式:分别是基于明文或基于ssl。

基于明文的通信如下:

明文传输

在应用层定义了前后台交互协议,封装的数据包结构大致如下:

oscar数据包

基于ssl的通信如下:

ssl加密通信

在应用层和传输层之间新加了ssl加密层,对于应用层而言,整个加密过程都是透明的。代码上的表现为直接替换普通socket为sslsocket。

现在的需求同样需要实现加密,方法是调用第三方提供的加密算法实现应用层数据的加密。第三方只提供了加密算法,所以需要从应用层入手,来实现数据加密。

思考两种方式如下:

  1. 对于现有的数据包,对其数据部分加密后传输。

    第一直觉可能会想到这种方式。但是仔细想想,这样的修改对于现有的应用层协议侵入性太大,每个类型的数据包均需要修改、测试,工作量也不小。同时,对于原有的明文传输方式兼容性不强。

  2. 对于计算机中遇到的问题,都可以通过"加一层"的方式解决。

    加密通信

    加密数据包结构如下:

    加密数据包

    通过这种加一层的方式,可以做到对原有的协议零侵入性,同时使用tag标识位标识数据包是否是加密包还是明文包,兼容明文传输。修改量也最小,故采用这种方式实现前后台加密通讯。

大致的思路有了,那么在代码中如何实现呢?这就用到了装饰者模式

装饰者模式

装饰者模式

装饰者模式在Java IO总结中也提到了:

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
Component:
定义一个对象接口,可以给这些对象动态地添加职责。
public interface Component
{
void operation();
}
Concrete Component:
定义一个对象,可以给这个对象添加一些职责。动作的具体实施者。
public class ConcreteComponent implements Component
{
public void operation()
{
// Write your code here
}
}
Decorator:
维持一个指向Component对象的引用,并定义一个与 Component接口一致的接口。
public class Decorator implements Component
{
public Decorator(Component component)
{
this.component = component;
}
public void operation()
{
component.operation();
}
private Component component;
}
Concrete Decorator:
在Concrete Component的行为之前或之后,加上自己的行为,以“贴上”附加的职责。
public class ConcreteDecorator extends Decorator
{
public void operation()
{
//addBehavior也可以在前面
super.operation();
addBehavior();
}
private void addBehavior()
{
//your code
}
}

使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。
装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

Java中的流家族就是装饰者模式的典型案例。

那么,与我们上面提到的加密层有什么联系呢?

在session建立一开始,协议协商确定使用加密通信后,将明文通信使用的BufferedOutputStream与BufferedInputStream替换成我们自定义的装饰者流:EncryptedOutputStream和EncryptedInputStream。

1
2
3
4
5
6
public void wrapEnprytedStream() {
//osr_input 是BufferedInputStream 实例
osr_input = new EncryptedInputStream(osr_input, con);
//osr_output 是 BufferedOutputStream 实例
osr_output = new EncryptedOutputStream(osr_output, con);
}

在EncryptedOutputStream.write方法和EncryptedInputStream.read方法中分别进行加密数据包的封包和拆包工作。把原有的应用层协议包封装在一个个加密数据包当中,相当于”加了一层”。

贴出这两个装饰者类的实现:

EncryptedInputStream.java

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
public class EncryptedInputStream extends FilterInputStream {
private static final byte encryptedTag = (byte) 0xA4;// 加密数据包标识
private static final byte unEncryptedTag = (byte) 0xA5;// 非加密数据包标识
private byte[] buffer;
private InputStream in;
private byte[] packetHeaderBuffer = new byte[3];
private int pos = 0;
private BaseConnection con;
private byte[] lock = new byte[0];
StringBuffer sb = new StringBuffer();
boolean logFlag = Driver.getLogLevel() >= TrackLog.PROTOCOLDETAIL_LEVEL;
protected EncryptedInputStream(InputStream in, BaseConnection con) {
super(in);
this.in = in;
this.con = con;
}
public int available() throws IOException {
if (this.buffer == null) {
return this.in.available();
}
return this.buffer.length - this.pos + this.in.available();
}
public void close() throws IOException {
this.in.close();
this.buffer = null;
}
private void getNextPacketFromServer() throws Exception {
byte[] decryptedData = null;
/**
* 数据头 第一个字节存放是否进行了加密。 第二三个字节存放加密后的数据的长度
*/
int lengthRead = readFully(this.packetHeaderBuffer, 0, 3);
if (lengthRead < 3) {
throw new IOException("Unexpected end of input stream");
}
/**
* 加密数据的长度
*/
int encryptedPacketLength = ((int) this.packetHeaderBuffer[1] & 0xff) << 8
| (int) this.packetHeaderBuffer[2] & 0xff;
if (encryptedPacketLength < 0) {
throw new Exception("数据包长度为负值:" + encryptedPacketLength + "----hb:" + this.packetHeaderBuffer[1] + "---lb:"
+ this.packetHeaderBuffer[2]);
}
/**
* 对数据进行了加密
*/
decryptedData = new byte[encryptedPacketLength];
readFully(decryptedData, 0, encryptedPacketLength);
if (this.packetHeaderBuffer[0] == encryptedTag) {
synchronized (lock) {
decryptedData = decryptData(con.getPublicKey(), decryptedData);
}
}
if ((this.buffer != null) && (this.pos < this.buffer.length)) {
int remaining = this.buffer.length - this.pos;
byte[] newBuffer = new byte[remaining + decryptedData.length];
int newIndex = buffer.length - pos;
System.arraycopy(buffer, pos, newBuffer, 0, newIndex);
System.arraycopy(decryptedData, 0, newBuffer, newIndex, decryptedData.length);
decryptedData = newBuffer;
}
this.pos = 0;
this.buffer = decryptedData;
}
private byte[] decryptData(String publicKey, byte[] src) {
byte[] data = DataEncryptUtil.instance.decryptDataByPublicKey(publicKey, src, src.length);
return data;
}
private void getNextPacketIfRequired(int numBytes) throws Exception {
if ((this.buffer == null) || ((this.pos + numBytes) > this.buffer.length)) {
getNextPacketFromServer();
}
}
synchronized public int read() throws IOException {
try {
getNextPacketIfRequired(1);
} catch (IOException ioEx) {
return -1;
} catch (Exception e) {
e.printStackTrace();
}
return this.buffer[this.pos++] & 0xff;
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
}
if (len <= 0) {
return 0;
}
try {
getNextPacketIfRequired(len);
} catch (IOException ioEx) {
return -1;
} catch (Exception e) {
e.printStackTrace();
}
int bufferLen = buffer.length;
if (bufferLen < len) {
System.arraycopy(this.buffer, this.pos, b, off, bufferLen);
this.pos += bufferLen;
return bufferLen;
} else {
System.arraycopy(this.buffer, this.pos, b, off, len);
this.pos += len;
return len;
}
}
private final int readFully(byte[] b, int off, int len) throws IOException {
if (len < 0) {
throw new IndexOutOfBoundsException();
}
int n = 0;
int count = 0;
while (n < len) {
count = this.in.read(b, off + n, len - n);
if (count < 0) {
throw new EOFException();
}
n += count;
}
return n;
}
public long skip(long n) throws IOException {
long count = 0;
int bytesRead = 0;
for (long i = 0; i < n; i++) {
bytesRead = read();
if (bytesRead == -1) {
break;
}
count++;
}
return count;
}
private void append(StringBuffer sb, byte[] value) {
if (value == null) {
sb.append("null");
} else {
for (int i = 0; i < value.length; i++) {
sb.append(value[i]).append(" ");
}
}
}
}

EncryptedOutputStream.java

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
public class EncryptedOutputStream extends BufferedOutputStream {
private static final byte encryptedTag = (byte) 0xA4;// 加密数据包标识
private static final byte unEncryptedTag = (byte) 0xA5;// 非加密数据包标识
private static final int K = 1024;
private static final int defaultBufferSize = 128 * K;
// 每个加密包的数据长度不超过48k
private static final int slice = 48 * K;
private BaseConnection con;
private OutputStream out;
private byte[] buffer;
// buffer 当前写入位置
private int pos = 0;
// 标志位 占一个字节
public byte[] singleBuf = new byte[1];
// 长度 占两个字节
public byte[] integerBuf = new byte[2];
public EncryptedOutputStream(OutputStream out, BaseConnection con) {
this(out, con, defaultBufferSize);
}
public EncryptedOutputStream(OutputStream out, BaseConnection con, int size) {
super(out);
this.out = out;
this.con = con;
this.buffer = new byte[size];
}
public void write(byte[] b, int off, int len) throws IOException {
if (len > buffer.length) {
flushBuffer();
writeWithEncrypted(b, off, len);
return;
}
if (pos + len > buffer.length) {
flushBuffer();
}
System.arraycopy(b, off, buffer, pos, len);
pos += len;
}
private void flushBuffer() throws IOException {
if (pos > 0) {
writeWithEncrypted(buffer, 0, pos);
pos = 0;
}
}
private void writeWithEncrypted(byte[] b, int off, int len) throws IOException {
int remainLen = len;
int offset = off;
byte[] buf = new byte[slice];;
while (remainLen > slice) {
System.arraycopy(b, offset, buf, 0, slice);
sendSlice(buf);
offset += slice;
remainLen -= slice;
}
if (remainLen > 0) {
buf = new byte[remainLen];
System.arraycopy(b, offset, buf, 0, remainLen);
sendSlice(buf);
}
}
/**
* 发送一个加密数据包
* @param buf 原始数据
* @throws IOException
*/
private void sendSlice(byte[] buf) throws IOException {
byte[] encryptedData = DataEncryptUtil.instance.encryptDataByPublicKey(con.getPublicKey(), buf, buf.length);
int encryptedDataLen = encryptedData.length;
writeChar(encryptedTag);
writeInteger(encryptedDataLen, 2);
out.write(encryptedData);
}
private void writeChar(int c) throws IOException {
singleBuf[0] = (byte) c;
out.write(singleBuf);
}
private void writeInteger(int val, int size) throws IOException {
int count = size;
while (size-- > 0) {
integerBuf[size] = (byte) (val & 0xff);
val >>= 8;
}
out.write(integerBuf, 0, count);
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
public void write(int b) throws IOException {
byte[] buf = new byte[1];
buf[0] = (byte) b;
write(buf);
}
public void flush() throws IOException {
flushBuffer();
out.flush();
}
public void close() throws IOException {
out.close();
buffer = null;
}
}

可以看到,使用了装饰者模式后,很容易就解决了我们的问题。现在想一下,假如现在又有新的需求,要求实现后台数据的压缩传输呢?(前台发送的数据量小,不考虑使用压缩)

压缩传输
1
2
3
4
5
6
public void wrapEnprytedStream() {
//osr_input 是BufferedInputStream 实例
osr_input = new CompressedInputStream(new EncryptedInputStream(osr_input), con);
//osr_output 是 BufferedOutputStream 实例
osr_output = new EncryptedOutputStream(osr_output, con);
}

装饰者与代理

读过上面的代码,是否觉得跟代理模式的代码长得很像?

代理模式

代理模式(Proxy Pattern),为其它对象提供一种代理以控制对这个对象的访问。
装饰模式(Decorator Pattern),动态地给一个对象添加一些额外的职责。

从语意上讲,代理模式的目标是控制对被代理对象的访问,而装饰模式是给原对象增加额外功能。

比如,我们使用了某些远程RPC通讯的sdk,在我们的代码中调用一个方法的时候,实际上调用的是代理类提供的方法,在其内部封装了远程通信的细节,而这些对我们而言都是透明的。调用方直接调用代理而不需要直接操作被代理对象甚至都不需要知道被代理对象的存在。屏蔽了实际对象。

上文提及的装饰者模式代码中,装饰类可装饰的类并不固定,并且被装饰对象是在使用时通过组合确定。

装饰模式的本质是动态组合。动态是手段,组合是目的。每个装饰类可以只负责添加一项额外功能,然后通过组合为被装饰类添加复杂功能。由于每个装饰类的职责比较简单单一,增加了这些装饰类的可重用性,同时也更符合单一职责原则。