1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sourceforge.statelessfilter.backend.aescookie;
17
18 import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
19 import static org.apache.commons.lang.StringUtils.isEmpty;
20 import static org.apache.commons.lang.StringUtils.trim;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.io.OutputStream;
29 import java.security.SignatureException;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.UUID;
33 import java.util.zip.GZIPInputStream;
34 import java.util.zip.GZIPOutputStream;
35
36 import javax.crypto.Cipher;
37 import javax.crypto.spec.IvParameterSpec;
38 import javax.crypto.spec.SecretKeySpec;
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
41
42 import net.sourceforge.statelessfilter.backend.ISessionData;
43 import net.sourceforge.statelessfilter.backend.support.CookieBackendSupport;
44 import net.sourceforge.statelessfilter.backend.support.CookieDataSupport;
45
46 import org.apache.commons.codec.binary.Base64;
47 import org.apache.commons.lang.ArrayUtils;
48 import org.apache.commons.lang.StringUtils;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public class AESCookieBackend extends CookieBackendSupport {
70 private static final String DESERIALIZE_ERROR = "Cannot deserialize session. A new one will be created";
71 private static final String ENCRYPTION = "AES";
72 private static final String ENCRYPTION_WITH_PARAM = "AES/CBC/PKCS5Padding";
73 private static final String ID = "aescookie";
74 private static Logger logger = LoggerFactory.getLogger(AESCookieBackend.class);
75 public static final String PARAM_COMPRESS = "compress";
76 public static final String PARAM_IV = "iv";
77 public static final String PARAM_KEY = "key";
78 public static final String PARAM_RESTRICT_IP = "restrictIP";
79 public static final String PARAM_SESSION_MAX_TIME = "sessionMaxTime";
80 public static final String PARAM_SIGN_SECRET = "secret";
81 private static final String SEPARATOR = "B";
82 private boolean compress = true;
83 private IvParameterSpec iv = null;
84 private boolean restrictIp;
85 private SecretKeySpec secretKey = null;
86 private Integer sessionMaxTime;
87 private String signSecret;
88
89 public AESCookieBackend() {
90 setCookieName("es");
91 }
92
93
94
95
96 @Override
97 public void destroy() {
98
99 }
100
101 public boolean getCompress() {
102 return compress;
103 }
104
105 private byte[] getEncryptionBytes(String data, int length) {
106 byte[] keyRaw = new byte[length];
107 for (int i = 0; i < length; i++) {
108 keyRaw[i] = 0;
109 }
110
111 byte[] dataRaw = Base64.decodeBase64(data);
112 System.arraycopy(dataRaw, 0, keyRaw, 0, dataRaw.length > length ? length : dataRaw.length);
113
114 return keyRaw;
115 }
116
117
118
119
120 @Override
121 public String getId() {
122 return ID;
123 }
124
125 public boolean getRestrictIp() {
126 return restrictIp;
127 }
128
129 public Integer getSessionMaxTime() {
130 return sessionMaxTime;
131 }
132
133 public String getSignSecret() {
134 return signSecret;
135 }
136
137
138
139
140
141
142
143 @Override
144 public void init(Map<String, String> config) throws Exception {
145 super.init(config);
146 this.compress = Boolean.parseBoolean(defaultIfEmpty(trim(config.get(PARAM_COMPRESS)), "true"));
147 this.restrictIp = Boolean.parseBoolean(defaultIfEmpty(trim(config.get(PARAM_RESTRICT_IP)), "true"));
148 this.signSecret = defaultIfEmpty(config.get(PARAM_SIGN_SECRET), UUID.randomUUID().toString());
149 this.sessionMaxTime = isEmpty(config.get(PARAM_SESSION_MAX_TIME)) ? null : Integer.parseInt(trim(config
150 .get(PARAM_SESSION_MAX_TIME)));
151 if (logger.isInfoEnabled()) {
152 logger.info(
153 "Cookie name: '{}', compression: '{}', " + "session max time: '{}', restrict IP: '{}'", new Object[] { this.cookieName, this.compress, this.sessionMaxTime, this.restrictIp });
154 }
155
156
157 String key = config.get(PARAM_KEY);
158 String iv = config.get(PARAM_IV);
159
160 if (isEmpty(key) || isEmpty(iv)) {
161 throw new IllegalArgumentException(ID
162 + "." + PARAM_KEY + " or " + ID + "." + PARAM_IV + " parameter missing in /stateless.properties.");
163 }
164
165 secretKey = new SecretKeySpec(getEncryptionBytes(key, 16), ENCRYPTION);
166 this.iv = new IvParameterSpec(getEncryptionBytes(iv, 16));
167
168 }
169
170
171
172
173 @Override
174 public ISessionData restore(HttpServletRequest request) {
175
176 try {
177 byte[] data = getCookieData(request, null, true, this.signSecret);
178 if (data != null) {
179 int index = ArrayUtils.indexOf(data, SEPARATOR.getBytes()[0]);
180
181 int size = Integer.parseInt(new String(ArrayUtils.subarray(data, 0, index)));
182 data = ArrayUtils.subarray(data, index + 1, data.length);
183
184 Cipher decryptCipher = Cipher.getInstance(ENCRYPTION_WITH_PARAM);
185 decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
186 data = decryptCipher.doFinal(data);
187
188 data = ArrayUtils.subarray(data, 0, size + 1);
189
190 InputStream inputStream = new ByteArrayInputStream(data);
191 if (compress) {
192 inputStream = new GZIPInputStream(inputStream);
193 }
194
195 ObjectInputStream ois = new ObjectInputStream(inputStream);
196 CookieDataSupport s = (CookieDataSupport) ois.readObject();
197
198 if (restrictIp
199 && (!StringUtils.equals(s.getRemoteAddress(), getFullRemoteAddr(request)) || StringUtils
200 .isEmpty(s.getRemoteAddress()))) {
201 s.setValid(false);
202 logger.warn("Invalid IP. Expected: " + s.getRemoteAddress() + ", current: "
203 + getFullRemoteAddr(request));
204 }
205
206 if (sessionMaxTime != null
207 && System.currentTimeMillis() > s.getCreationTime() + sessionMaxTime.intValue() * 1000) {
208 s.setValid(false);
209 logger.info("Session max time reached.");
210 }
211
212 if (s.isValid()) {
213 return s;
214 }
215 }
216 } catch (Exception e) {
217 logger.info(DESERIALIZE_ERROR, e);
218 }
219
220 return null;
221 }
222
223
224
225
226
227
228 @Override
229 public void save(ISessionData session, List<String> dirtyAttributes, HttpServletRequest request,
230 HttpServletResponse response) throws IOException {
231
232 try {
233 if (session != null) {
234 CookieDataSupport cookieData = new CookieDataSupport(session);
235 cookieData.setRemoteAddress(getFullRemoteAddr(request));
236
237 ByteArrayOutputStream baos = new ByteArrayOutputStream();
238 OutputStream outputStream = baos;
239 if (compress) {
240 outputStream = new GZIPOutputStream(outputStream);
241 }
242
243 ObjectOutputStream oos = new ObjectOutputStream(outputStream);
244 oos.writeObject(cookieData);
245 oos.close();
246 outputStream.close();
247 baos.close();
248
249 byte[] data;
250 try {
251 Cipher encryptCipher = Cipher.getInstance(ENCRYPTION_WITH_PARAM);
252 encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
253 data = encryptCipher.doFinal(baos.toByteArray());
254 } catch (Exception e) {
255 throw new IOException(e.getMessage());
256 }
257
258 byte[] size = (data.length + SEPARATOR).getBytes();
259
260 setCookieData(request, response, ArrayUtils.addAll(size, data), true, this.signSecret);
261
262 if (logger.isDebugEnabled()) {
263 logger.debug("Cookie size : " + ArrayUtils.addAll(size, data).length);
264 }
265
266 } else {
267 setCookieData(request, response, null, true, this.signSecret);
268 }
269 } catch (SignatureException e) {
270 throw new IOException(e);
271 }
272 }
273 }