Compare commits
1084 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
546febd959 | ||
|
|
ea857025bc | ||
|
|
782167b306 | ||
|
|
17005c51a7 | ||
|
|
54859070f3 | ||
|
|
3d03c02924 | ||
|
|
4f9d55feec | ||
|
|
7e7d08a99f | ||
|
|
f6a5bdacb8 | ||
|
|
79de4f9a0d | ||
|
|
9a379fd5bc | ||
|
|
be58c00838 | ||
|
|
3475043bf0 | ||
|
|
c6470c6f48 | ||
|
|
6faa67a64e | ||
|
|
340b614860 | ||
|
|
79b092d8f0 | ||
|
|
2e91024a56 | ||
|
|
e359468abc | ||
|
|
4ec6a9a08b | ||
|
|
084d1c7286 | ||
|
|
a4ff3682f6 | ||
|
|
35f09d0da7 | ||
|
|
566b388b2f | ||
|
|
78b72f2d1b | ||
|
|
52f59a83e4 | ||
|
|
6809168541 | ||
|
|
02bb998f97 | ||
|
|
57ffb90a0c | ||
|
|
402272d5aa | ||
|
|
1dbaffef5e | ||
|
|
8ae0e0b314 | ||
|
|
d94d58d96b | ||
|
|
cc41ea5271 | ||
|
|
e6a88ccf4c | ||
|
|
36f1cd5302 | ||
|
|
3bd892ec16 | ||
|
|
dcd5342204 | ||
|
|
48c32228de | ||
|
|
ec8ef595a2 | ||
|
|
b98c5216b4 | ||
|
|
eb0f89a18a | ||
|
|
83e0f4f24a | ||
|
|
397d4e5ca1 | ||
|
|
f7b7737896 | ||
|
|
2a5b221d70 | ||
|
|
5a462b3594 | ||
|
|
c252c00a85 | ||
|
|
2a7b6ec4f7 | ||
|
|
044e5bbe3c | ||
|
|
f247a78c32 | ||
|
|
c0f708d0b3 | ||
|
|
c2a3ffdb00 | ||
|
|
856a9b2c51 | ||
|
|
0d24033749 | ||
|
|
7a5f9bc29e | ||
|
|
094ffe8a60 | ||
|
|
55102aa8bb | ||
|
|
f2e9634789 | ||
|
|
7dccade893 | ||
|
|
39408ff42a | ||
|
|
a5a3bc5d0b | ||
|
|
90c08340fa | ||
|
|
13208dd3b5 | ||
|
|
0975d4d47e | ||
|
|
0c0bfa4414 | ||
|
|
8b40e8cce8 | ||
|
|
408f2da108 | ||
|
|
3b7bb7ec12 | ||
|
|
4c849caccd | ||
|
|
ea84b8bd22 | ||
|
|
0c9fbedd05 | ||
|
|
08495b1321 | ||
|
|
60cd6d8be6 | ||
|
|
5503057523 | ||
|
|
8fc2f1130a | ||
|
|
f5d63efcf1 | ||
|
|
ed76514f30 | ||
|
|
e1c0d62d33 | ||
|
|
8041358d18 | ||
|
|
cdd85cb349 | ||
|
|
381a07cb8c | ||
|
|
0e89539e20 | ||
|
|
f00a1b9c6f | ||
|
|
39cecb0902 | ||
|
|
c5fc4b57ad | ||
|
|
3b826e51a1 | ||
|
|
67063ec0c6 | ||
|
|
72198e9e80 | ||
|
|
b3ee28b972 | ||
|
|
2d498bf69d | ||
|
|
c365e4d941 | ||
|
|
f7f8d4f6c0 | ||
|
|
fd5115fae5 | ||
|
|
a4c39fde9f | ||
|
|
96391ce41a | ||
|
|
d48eebea99 | ||
|
|
57cd6c367d | ||
|
|
68f83b00eb | ||
|
|
a4a5b529ef | ||
|
|
f5ae38d94c | ||
|
|
b201ed971c | ||
|
|
70346b0a84 | ||
|
|
d4fd8b97b4 | ||
|
|
b3d01063d9 | ||
|
|
124565306b | ||
|
|
f709a6c787 | ||
|
|
c354927ef3 | ||
|
|
2db79e2bb8 | ||
|
|
3480c65c2b | ||
|
|
e0dc0262ef | ||
|
|
3b7da0c370 | ||
|
|
72984f9ca6 | ||
|
|
8a4872b919 | ||
|
|
6b6c6f27df | ||
|
|
640bee3fc4 | ||
|
|
3bfdb9be93 | ||
|
|
c8f3d1a1ec | ||
|
|
11ad1db6e7 | ||
|
|
7b87128db6 | ||
|
|
bf861933ed | ||
|
|
979e91256d | ||
|
|
05986d68b2 | ||
|
|
e17b047800 | ||
|
|
5ab2424b14 | ||
|
|
196919efbb | ||
|
|
717e16cb71 | ||
|
|
5f1b7d6722 | ||
|
|
4d3a01919c | ||
|
|
e408d7f557 | ||
|
|
f34acebf84 | ||
|
|
1aab3e8285 | ||
|
|
c3528996d2 | ||
|
|
3ccc3eb6e1 | ||
|
|
de76be95ac | ||
|
|
bc127ab3fc | ||
|
|
3e9f6a35c4 | ||
|
|
49daa3a9c7 | ||
|
|
a67bd634d9 | ||
|
|
2762f001bf | ||
|
|
93aee206fb | ||
|
|
3df3b30117 | ||
|
|
5fb0c4dd35 | ||
|
|
6fbce6e3e8 | ||
|
|
a3fd05326a | ||
|
|
4c6dc976b3 | ||
|
|
58ae28b0a0 | ||
|
|
3e98ecf234 | ||
|
|
41ed429f98 | ||
|
|
def15b05ca | ||
|
|
eae8592f2b | ||
|
|
81460ede09 | ||
|
|
ca4ec9a557 | ||
|
|
fd2165f471 | ||
|
|
ad1e57a1fe | ||
|
|
0ffcaa2d35 | ||
|
|
b61937def7 | ||
|
|
c523fb591d | ||
|
|
227fab2e42 | ||
|
|
7f7815d80c | ||
|
|
002136bad4 | ||
|
|
1085661984 | ||
|
|
12bb0741bb | ||
|
|
eecdcb49d9 | ||
|
|
3e1a22102d | ||
|
|
9f6e791e5d | ||
|
|
efc35eddad | ||
|
|
4c37ec9f4a | ||
|
|
1a3da5944d | ||
|
|
5d0775b802 | ||
|
|
603a258172 | ||
|
|
22ebe65931 | ||
|
|
55033bcb64 | ||
|
|
57955b7d7b | ||
|
|
d5da38f2e0 | ||
|
|
6cc4bcd13d | ||
|
|
dc43f5bd2d | ||
|
|
7584cbd54c | ||
|
|
0db1160dc4 | ||
|
|
10a18366f9 | ||
|
|
7ea5e2f3ee | ||
|
|
d3134ad065 | ||
|
|
6208d0298d | ||
|
|
c031ee278d | ||
|
|
8267a90fcc | ||
|
|
2113b330a7 | ||
|
|
c4ac68b777 | ||
|
|
0be2759e68 | ||
|
|
1181e52bb0 | ||
|
|
5277d945ed | ||
|
|
1fc0162fe9 | ||
|
|
9df259b1ae | ||
|
|
0c2f756533 | ||
|
|
de16c304ea | ||
|
|
3ce3962ebd | ||
|
|
3c4a309a0f | ||
|
|
38de434158 | ||
|
|
7ef0faf259 | ||
|
|
f65cee0a7b | ||
|
|
a2cd1e37fa | ||
|
|
b768042506 | ||
|
|
3140bd06b2 | ||
|
|
172c18d666 | ||
|
|
7fdf2876b2 | ||
|
|
87c2e53b5a | ||
|
|
268ba663e5 | ||
|
|
3f4873f0eb | ||
|
|
644239ee14 | ||
|
|
97e52de41b | ||
|
|
f4bbc18f94 | ||
|
|
dfe216b482 | ||
|
|
a976c9dd6d | ||
|
|
deb2863507 | ||
|
|
7bdb3f6ded | ||
|
|
7d3472f55d | ||
|
|
00465a6f00 | ||
|
|
ad35d7ca30 | ||
|
|
18e9ab4c0f | ||
|
|
1c9a6d3e5d | ||
|
|
d2936ed0b4 | ||
|
|
cdf6089ccd | ||
|
|
1ca8a6476a | ||
|
|
cf926045dc | ||
|
|
7123df8656 | ||
|
|
096a5683cb | ||
|
|
db31527c8c | ||
|
|
3d2a742328 | ||
|
|
7ac6e458e0 | ||
|
|
9adf0a6e0c | ||
|
|
58219fa016 | ||
|
|
83cbff5ce2 | ||
|
|
936fc853df | ||
|
|
dba475c48f | ||
|
|
9956e91b93 | ||
|
|
c902981eba | ||
|
|
2e26c6e9d3 | ||
|
|
b9cd3865c5 | ||
|
|
1f7232f12e | ||
|
|
03f0a571b6 | ||
|
|
63a215f73b | ||
|
|
8dac35cf73 | ||
|
|
19b8583d65 | ||
|
|
6de0f44241 | ||
|
|
60d6120b9c | ||
|
|
3bc899e695 | ||
|
|
c2fe999d6c | ||
|
|
d214971e72 | ||
|
|
f4704293a1 | ||
|
|
a8c4f65903 | ||
|
|
4a52de0c18 | ||
|
|
63f105082a | ||
|
|
f55b793185 | ||
|
|
6d027900ee | ||
|
|
42818a1b90 | ||
|
|
b6348736ac | ||
|
|
60581c6427 | ||
|
|
836ea12e93 | ||
|
|
670148f182 | ||
|
|
a39295c02b | ||
|
|
02cd5a6301 | ||
|
|
5824566621 | ||
|
|
b2711600e2 | ||
|
|
06eb768721 | ||
|
|
fb05fa70c7 | ||
|
|
1e93fe87db | ||
|
|
e67f84c6b6 | ||
|
|
dfb2f2f334 | ||
|
|
c8e9630fdd | ||
|
|
751375338c | ||
|
|
538712d162 | ||
|
|
941fdb46f2 | ||
|
|
bb1c099094 | ||
|
|
1d1253e643 | ||
|
|
0e7e2eaf5c | ||
|
|
e601e03e1e | ||
|
|
2c81e50b5e | ||
|
|
ac5ff996f4 | ||
|
|
44130cba80 | ||
|
|
2cd8063c7c | ||
|
|
f42a6c7d1c | ||
|
|
6c2f6c26cc | ||
|
|
91b4efc5bd | ||
|
|
6f8359ba16 | ||
|
|
62bfeb3f05 | ||
|
|
2395582fe6 | ||
|
|
5173026aa8 | ||
|
|
d97ad2ca3e | ||
|
|
a780ee0264 | ||
|
|
d8e7a2aa9f | ||
|
|
45b18dec84 | ||
|
|
ec5406fb01 | ||
|
|
3c2f0fd485 | ||
|
|
cdfa557442 | ||
|
|
edc8a7efff | ||
|
|
a7a30dad30 | ||
|
|
be1d3d30a8 | ||
|
|
010aa5f013 | ||
|
|
bfcb4afef7 | ||
|
|
72a902009e | ||
|
|
1e799f211f | ||
|
|
90599b9bd3 | ||
|
|
8d7136072a | ||
|
|
4f0f3806a2 | ||
|
|
a18037759c | ||
|
|
eb479af1d4 | ||
|
|
d0b472e8e2 | ||
|
|
17ee9d51f2 | ||
|
|
003996a1b3 | ||
|
|
13c0e325b4 | ||
|
|
7acdeffe22 | ||
|
|
de03b20619 | ||
|
|
becee53dbf | ||
|
|
4eb64e8140 | ||
|
|
e520ea237d | ||
|
|
175e05dcda | ||
|
|
bb427ff1af | ||
|
|
0a65b82373 | ||
|
|
e25c64efae | ||
|
|
43fcba65c4 | ||
|
|
1cc2c83f36 | ||
|
|
0941358807 | ||
|
|
7d3698515e | ||
|
|
d382603445 | ||
|
|
22e3b5ce38 | ||
|
|
ebd4b349d2 | ||
|
|
ffa1bca898 | ||
|
|
d0ee9fd16a | ||
|
|
7a631fe414 | ||
|
|
d217077dec | ||
|
|
a9b3ce034b | ||
|
|
1ba434a357 | ||
|
|
45807998f6 | ||
|
|
2f49a8ac25 | ||
|
|
e364511c7e | ||
|
|
79ccbe7066 | ||
|
|
1edce117aa | ||
|
|
c0f4c7f381 | ||
|
|
7fa07b2973 | ||
|
|
3252b38c87 | ||
|
|
c4daeff3d8 | ||
|
|
2fccca1158 | ||
|
|
81798c36f6 | ||
|
|
27283e29d5 | ||
|
|
77bb9dfdb1 | ||
|
|
c874592323 | ||
|
|
676f0e474e | ||
|
|
e5ec612771 | ||
|
|
280d5c5a77 | ||
|
|
6a370b1ef8 | ||
|
|
41de1b087a | ||
|
|
6188fe68b7 | ||
|
|
ed328ff4b1 | ||
|
|
97ad0311e2 | ||
|
|
702bc37a99 | ||
|
|
17e56dda18 | ||
|
|
f5912da089 | ||
|
|
bff8ce3c03 | ||
|
|
a3803e9e1f | ||
|
|
3fcdc9ebce | ||
|
|
36d157a658 | ||
|
|
f28ab07b9a | ||
|
|
42a6001aae | ||
|
|
fc4d2238bc | ||
|
|
36d349f328 | ||
|
|
5f23a41674 | ||
|
|
4c9fbd5b6b | ||
|
|
f2ba773ec2 | ||
|
|
647dd7c7bb | ||
|
|
555223755d | ||
|
|
2e65d89ecc | ||
|
|
f3f18432ee | ||
|
|
03f6611e04 | ||
|
|
fff1d83097 | ||
|
|
91d4a5bfca | ||
|
|
34f29cf36c | ||
|
|
7e26897ec2 | ||
|
|
9ea1fb9af1 | ||
|
|
2c664d1d9e | ||
|
|
97698fd590 | ||
|
|
fe3f40c6f4 | ||
|
|
f8583bb02f | ||
|
|
5df555cd53 | ||
|
|
6f05c84aa7 | ||
|
|
cd394bbe10 | ||
|
|
2ecb2e60c0 | ||
|
|
d04a95ebfb | ||
|
|
858b52235e | ||
|
|
00ede81665 | ||
|
|
6cfa975b29 | ||
|
|
8b9d421ad6 | ||
|
|
df7ab9d99e | ||
|
|
7d61c5496a | ||
|
|
3492bc01d2 | ||
|
|
e08ac357dd | ||
|
|
1c29c7f14f | ||
|
|
33fbaa03a8 | ||
|
|
88b26f2cfe | ||
|
|
3f670050ef | ||
|
|
e3b61d25bb | ||
|
|
19b8effa41 | ||
|
|
9f5f7540d2 | ||
|
|
eb8c22939c | ||
|
|
45cfa1e9a4 | ||
|
|
99221e0948 | ||
|
|
41cf2ef152 | ||
|
|
c51bce4777 | ||
|
|
b6f1184c4c | ||
|
|
c69a8b8762 | ||
|
|
99fb17a66b | ||
|
|
937b2fcbf1 | ||
|
|
9c5a7e9156 | ||
|
|
4deccd3ad0 | ||
|
|
da058e9510 | ||
|
|
d28ca4658b | ||
|
|
c14fdb283d | ||
|
|
ee1ff3ed3b | ||
|
|
eb7bcc5eeb | ||
|
|
188e5ba4e0 | ||
|
|
1e46630467 | ||
|
|
b72c600884 | ||
|
|
274aec1691 | ||
|
|
52ea98b4ce | ||
|
|
5c294ae1d2 | ||
|
|
1752928d96 | ||
|
|
0cdee25405 | ||
|
|
4a9f1700d5 | ||
|
|
36ab358d24 | ||
|
|
8e3371aed9 | ||
|
|
2161f966de | ||
|
|
63b67a501d | ||
|
|
2b0431eae4 | ||
|
|
04ec086014 | ||
|
|
5697f49a71 | ||
|
|
dfce66383f | ||
|
|
a83e59bf52 | ||
|
|
8b233e84ef | ||
|
|
84e7fbace1 | ||
|
|
f455df3333 | ||
|
|
a7bb9d3b31 | ||
|
|
5f0e4c3b85 | ||
|
|
23c6c7cf31 | ||
|
|
c8c5fae678 | ||
|
|
f4a58622e4 | ||
|
|
5384764021 | ||
|
|
56033a9b68 | ||
|
|
99a2b079ac | ||
|
|
9120151692 | ||
|
|
5abbe66b1d | ||
|
|
f00c196430 | ||
|
|
be2604ca69 | ||
|
|
2aa71ffb6d | ||
|
|
8bdcba6e50 | ||
|
|
8dd1a10f1b | ||
|
|
1d247aa96f | ||
|
|
c00d6a7bf2 | ||
|
|
c0df3bf28b | ||
|
|
1b8c9838a4 | ||
|
|
8a1b454121 | ||
|
|
ef69c8169a | ||
|
|
40b3d07224 | ||
|
|
8c726f2215 | ||
|
|
c2a86a27ce | ||
|
|
6a08ef6f97 | ||
|
|
9c4e20f074 | ||
|
|
5845a9c46a | ||
|
|
7c6693a268 | ||
|
|
05a3f59813 | ||
|
|
47a7a35aa4 | ||
|
|
04b4fe3e3b | ||
|
|
36bb65e4b5 | ||
|
|
8ef36e4f3e | ||
|
|
ab3e280993 | ||
|
|
30562b5749 | ||
|
|
d42a7b65ea | ||
|
|
db9807d12b | ||
|
|
db09fa8168 | ||
|
|
031541bc05 | ||
|
|
084e3428fb | ||
|
|
b321ff02f0 | ||
|
|
c6c6beb40c | ||
|
|
0127ef9f9b | ||
|
|
cd8686ae9c | ||
|
|
233d179bfa | ||
|
|
4e8ae8d9d4 | ||
|
|
8c6810c6dd | ||
|
|
fca411996a | ||
|
|
79b8296e1c | ||
|
|
043cb42149 | ||
|
|
c28f047eb5 | ||
|
|
972cf66d7e | ||
|
|
f1319483ee | ||
|
|
6ad5006280 | ||
|
|
f7e07b7f6b | ||
|
|
4cf26d9c36 | ||
|
|
a848df1235 | ||
|
|
f8292ba512 | ||
|
|
21bcc6e8d7 | ||
|
|
905a77a3a8 | ||
|
|
295f9f78c3 | ||
|
|
04ecc82d09 | ||
|
|
2ddd9e58a3 | ||
|
|
3c52298c47 | ||
|
|
7b385c7d33 | ||
|
|
87d51c54c9 | ||
|
|
210e8eebc5 | ||
|
|
7d52c87173 | ||
|
|
e3c6fb67f2 | ||
|
|
79f187ddd6 | ||
|
|
22f4b0bc9d | ||
|
|
c5ea626d03 | ||
|
|
d067cd1e66 | ||
|
|
76a6be572a | ||
|
|
78db900303 | ||
|
|
7f9a9c4185 | ||
|
|
83c67d3e11 | ||
|
|
4f3324bac4 | ||
|
|
23a28f790a | ||
|
|
dd4983f33e | ||
|
|
e9e5d8eda6 | ||
|
|
a745d471ad | ||
|
|
df267774da | ||
|
|
2b2f385d5f | ||
|
|
86e892c806 | ||
|
|
448133494f | ||
|
|
e0fc9e92ba | ||
|
|
5b4d0c40d8 | ||
|
|
78ea101a43 | ||
|
|
63097e9d82 | ||
|
|
2ebbe762f0 | ||
|
|
63b836b212 | ||
|
|
02da23a2a0 | ||
|
|
b254c7c6b9 | ||
|
|
89adc13201 | ||
|
|
e6e752aea5 | ||
|
|
d590ca58e4 | ||
|
|
e23aaeca5f | ||
|
|
0312c31a42 | ||
|
|
815cbf4ee8 | ||
|
|
6327d36ce9 | ||
|
|
707b8bb062 | ||
|
|
adbff45a23 | ||
|
|
f30cb7a1e6 | ||
|
|
432eb84a94 | ||
|
|
bd31710117 | ||
|
|
e67afefcd8 | ||
|
|
327323da38 | ||
|
|
83e5d6f2a7 | ||
|
|
887f024551 | ||
|
|
8dd6aa38ed | ||
|
|
6f4025eacb | ||
|
|
3cc53fae2c | ||
|
|
25ded686ac | ||
|
|
9b30726805 | ||
|
|
aeb182712c | ||
|
|
18ccee051f | ||
|
|
3f239b4956 | ||
|
|
dc3b6ba6f1 | ||
|
|
ddf9ef66c1 | ||
|
|
b65423f296 | ||
|
|
b3706addbb | ||
|
|
5c6565bd9c | ||
|
|
3e24393e9a | ||
|
|
0e10b7763c | ||
|
|
012f121c48 | ||
|
|
c0cc15679c | ||
|
|
43d83f6398 | ||
|
|
862659b9b7 | ||
|
|
536156a4ec | ||
|
|
aa3536a71a | ||
|
|
dd23c96c1a | ||
|
|
41fbc90ec2 | ||
|
|
3cc3784313 | ||
|
|
489cf01812 | ||
|
|
94fc80a8f0 | ||
|
|
3ad0028785 | ||
|
|
801f88d793 | ||
|
|
0d6b62b7a9 | ||
|
|
00d5d76833 | ||
|
|
85a1b43242 | ||
|
|
800d52279f | ||
|
|
61a6344ffa | ||
|
|
c182e90a1a | ||
|
|
7dc3e12e07 | ||
|
|
ce5e44233e | ||
|
|
22c416e32b | ||
|
|
ff72bf1234 | ||
|
|
a45199059b | ||
|
|
37045e337c | ||
|
|
41e3f91b75 | ||
|
|
efeed5e2cf | ||
|
|
1952a4550f | ||
|
|
9efb5b59e5 | ||
|
|
4b196744f2 | ||
|
|
8e7c736a0a | ||
|
|
1a318b89d9 | ||
|
|
f98697416e | ||
|
|
d66fa56513 | ||
|
|
89c91c19d8 | ||
|
|
5e294b805f | ||
|
|
a7b5f86bcd | ||
|
|
9c236fa256 | ||
|
|
304e32eef5 | ||
|
|
288c622012 | ||
|
|
3827ae1e72 | ||
|
|
9067f8235d | ||
|
|
19928e6b7f | ||
|
|
1df1a76069 | ||
|
|
17e397212d | ||
|
|
39503a21a7 | ||
|
|
ebbc10b2b4 | ||
|
|
9a51cb9ca7 | ||
|
|
5e0ee5077a | ||
|
|
526c6ee012 | ||
|
|
ff4045acbd | ||
|
|
2d359986d3 | ||
|
|
6424910c83 | ||
|
|
49e3a1c7cd | ||
|
|
3b8258f233 | ||
|
|
a2b30eb54b | ||
|
|
4c2581d432 | ||
|
|
25aec99357 | ||
|
|
1c9dfa6638 | ||
|
|
466e2cf102 | ||
|
|
eb0f292c20 | ||
|
|
43fda301e2 | ||
|
|
4a06b38c5f | ||
|
|
6a78101db5 | ||
|
|
b5ea6c752d | ||
|
|
46633274d5 | ||
|
|
038287b2cc | ||
|
|
32c053271c | ||
|
|
802e0e714b | ||
|
|
6263f6e927 | ||
|
|
5671037c39 | ||
|
|
3d44467275 | ||
|
|
94221c70a9 | ||
|
|
dd3a571494 | ||
|
|
e4fe53abf8 | ||
|
|
7b65d7930b | ||
|
|
0f8326516b | ||
|
|
2aec28289e | ||
|
|
af7a5a208f | ||
|
|
e9924d27a1 | ||
|
|
f0820c8038 | ||
|
|
7d680ff3ef | ||
|
|
6d9885455b | ||
|
|
06104c348d | ||
|
|
090742350c | ||
|
|
a290b11019 | ||
|
|
eabad84ba8 | ||
|
|
4e33b7740c | ||
|
|
f8967c4c13 | ||
|
|
245e634bea | ||
|
|
7a2914323f | ||
|
|
6e04d903ae | ||
|
|
d8c3a4dd61 | ||
|
|
997813088a | ||
|
|
5ecf390932 | ||
|
|
8167b43e63 | ||
|
|
1f7193f32d | ||
|
|
0e1d81f509 | ||
|
|
8b97a32db2 | ||
|
|
d3379029bb | ||
|
|
26eca5b448 | ||
|
|
6335894e13 | ||
|
|
34948d6451 | ||
|
|
bff0f8f845 | ||
|
|
2052ec8d44 | ||
|
|
cbd96999e0 | ||
|
|
61492c4ae1 | ||
|
|
97fef0f9bd | ||
|
|
2792d2a0e9 | ||
|
|
2724b333b3 | ||
|
|
8db7d394ba | ||
|
|
3e293e8b54 | ||
|
|
de7bb05fc1 | ||
|
|
3ee4c5b5d0 | ||
|
|
acf37fc8f4 | ||
|
|
1256a94d7e | ||
|
|
bbb94361f8 | ||
|
|
9af3f1fcec | ||
|
|
5b70d55a21 | ||
|
|
9133e337e6 | ||
|
|
9f36fd69ee | ||
|
|
e684f58403 | ||
|
|
508e3e90a7 | ||
|
|
0018a2f772 | ||
|
|
e9f097db5c | ||
|
|
7e41e40762 | ||
|
|
dc553104c6 | ||
|
|
2b4a349b1a | ||
|
|
4a1f132e07 | ||
|
|
940555653c | ||
|
|
82cb417910 | ||
|
|
808a550343 | ||
|
|
cc08f83ce0 | ||
|
|
53c81de5e3 | ||
|
|
dd268992a8 | ||
|
|
00004e2f50 | ||
|
|
afb9a11abf | ||
|
|
fb356f02ff | ||
|
|
509fb99a5d | ||
|
|
2aff5b0377 | ||
|
|
016a007d12 | ||
|
|
2a3ec2f4b9 | ||
|
|
458ac6e700 | ||
|
|
33321d2e53 | ||
|
|
20130f8e8a | ||
|
|
03f008f283 | ||
|
|
00e7110594 | ||
|
|
f973e63fce | ||
|
|
edbf8bf587 | ||
|
|
234f6c954a | ||
|
|
e0417523f6 | ||
|
|
5865b0a715 | ||
|
|
67201417df | ||
|
|
01a149737e | ||
|
|
2d6f505a30 | ||
|
|
7c616a1adf | ||
|
|
61b01d9ecd | ||
|
|
f9163d94fd | ||
|
|
3f819a94b1 | ||
|
|
1e1d24895c | ||
|
|
e134a4cdb8 | ||
|
|
75006cd7dd | ||
|
|
9c8f8894e1 | ||
|
|
99db45ea72 | ||
|
|
c07583bd47 | ||
|
|
cce8dac4b7 | ||
|
|
5bde226ecc | ||
|
|
f8f6ee20c0 | ||
|
|
3bb96e8e82 | ||
|
|
b7367680cb | ||
|
|
a26a21b663 | ||
|
|
3825a46418 | ||
|
|
779277b16d | ||
|
|
b97306a83d | ||
|
|
6b13111079 | ||
|
|
63006db45d | ||
|
|
0a99e065ff | ||
|
|
bd2d846917 | ||
|
|
79928bd7fe | ||
|
|
903cac492e | ||
|
|
3980be349b | ||
|
|
128e0c4d47 | ||
|
|
37bc6a352f | ||
|
|
b88f48f01d | ||
|
|
028e277fc9 | ||
|
|
904cdf4e65 | ||
|
|
b58ea03a3b | ||
|
|
9014ac9060 | ||
|
|
7a82915a98 | ||
|
|
e16224583c | ||
|
|
96861ea535 | ||
|
|
de3d04fb29 | ||
|
|
68823eec29 | ||
|
|
a58d4f64fb | ||
|
|
40ad82806c | ||
|
|
6db7d45f65 | ||
|
|
bbbdf939e8 | ||
|
|
e23398b890 | ||
|
|
5cfbeae161 | ||
|
|
c6e71a90bb | ||
|
|
195228ff5a | ||
|
|
831d2f4152 | ||
|
|
4f7728f5b5 | ||
|
|
11e418526e | ||
|
|
49f24e493d | ||
|
|
bd6d5bf419 | ||
|
|
9b989c4a3e | ||
|
|
68ddcef83e | ||
|
|
2592905b41 | ||
|
|
4be47f6b40 | ||
|
|
35b5fcdc75 | ||
|
|
4d010b23b8 | ||
|
|
f0200696ef | ||
|
|
7f3302253b | ||
|
|
9e3bcafa75 | ||
|
|
216bfd7355 | ||
|
|
34cebc3df6 | ||
|
|
7b28b214ff | ||
|
|
8f8cfe5d79 | ||
|
|
e0e29eab35 | ||
|
|
cd38e307e0 | ||
|
|
091d0d8d9f | ||
|
|
3300b58afe | ||
|
|
00af8456d4 | ||
|
|
aeb0add50e | ||
|
|
671bfa0444 | ||
|
|
8bf3a251bb | ||
|
|
5d1c60711f | ||
|
|
32cf8ad7ad | ||
|
|
d3139b04e1 | ||
|
|
cc0bfc2299 | ||
|
|
c0a5916413 | ||
|
|
11ca552625 | ||
|
|
8e4ed22974 | ||
|
|
53d7c84f73 | ||
|
|
1871915c62 | ||
|
|
d5259bf0e9 | ||
|
|
12b1dda24c | ||
|
|
fad66c5cd2 | ||
|
|
e338e70972 | ||
|
|
4c01782d6c | ||
|
|
5bf12fdd93 | ||
|
|
d209a10514 | ||
|
|
1d0eb68f70 | ||
|
|
b64d0c0d3c | ||
|
|
305ce1ae65 | ||
|
|
aed79ddbf7 | ||
|
|
3b5fc5e9cc | ||
|
|
e0bbbbcd45 | ||
|
|
5f23b3c272 | ||
|
|
bf7729b936 | ||
|
|
7c53558454 | ||
|
|
f2443f5e21 | ||
|
|
2b5386ad98 | ||
|
|
c2b407189e | ||
|
|
7de11753a9 | ||
|
|
2d6a474c2c | ||
|
|
cc1d3a7e9e | ||
|
|
fe17e3fcc0 | ||
|
|
ef904a4544 | ||
|
|
207e4e27b3 | ||
|
|
bda5c85acf | ||
|
|
c9acd3c812 | ||
|
|
f20acbf9b9 | ||
|
|
ad09b498a3 | ||
|
|
7618aafb90 | ||
|
|
7a3bf533b3 | ||
|
|
723ea5e2e0 | ||
|
|
7463592988 | ||
|
|
198acc0648 | ||
|
|
f53e991d02 | ||
|
|
22f6f9dd72 | ||
|
|
019e0083b0 | ||
|
|
1931da83a5 | ||
|
|
5dff0ff37b | ||
|
|
1003821dad | ||
|
|
5399b7c299 | ||
|
|
969053fa87 | ||
|
|
eb646ef5d8 | ||
|
|
f14942edb0 | ||
|
|
18a07abeb4 | ||
|
|
e766ad774d | ||
|
|
b38cc74764 | ||
|
|
3df40dd8a9 | ||
|
|
9d95638cf8 | ||
|
|
d85624c58e | ||
|
|
5ca6377015 | ||
|
|
33834fdf19 | ||
|
|
8e60c968a1 | ||
|
|
41298c490e | ||
|
|
ee09a9e863 | ||
|
|
8bd29c1ab2 | ||
|
|
f1d0350356 | ||
|
|
2fa7ecdee5 | ||
|
|
8a5cba914b | ||
|
|
5fbf333c61 | ||
|
|
33c9485031 | ||
|
|
2e82b311ee | ||
|
|
8c07537bec | ||
|
|
c701e1877e | ||
|
|
7186878f84 | ||
|
|
aeef1417e1 | ||
|
|
76d341d6ca | ||
|
|
d1c00c6080 | ||
|
|
a48864bf20 | ||
|
|
bebb596846 | ||
|
|
af5899fb1a | ||
|
|
489a26c14c | ||
|
|
134d23bb07 | ||
|
|
8b5469ef5c | ||
|
|
e3414ef11e | ||
|
|
68ad464b67 | ||
|
|
db2759c497 | ||
|
|
8760d7037a | ||
|
|
d2f7ac4898 | ||
|
|
5fd2c71559 | ||
|
|
6414d0ddb8 | ||
|
|
ba97bdf2fa | ||
|
|
6c82628402 | ||
|
|
16b65973b7 | ||
|
|
db45698e25 | ||
|
|
6234dd5681 | ||
|
|
00d50593c1 | ||
|
|
881ca7c2d4 | ||
|
|
77eb6cfd71 | ||
|
|
21065b23c0 | ||
|
|
7693d0e624 | ||
|
|
2503537e36 | ||
|
|
d27d9a22bf | ||
|
|
6e1ecadcc4 | ||
|
|
690fbc8d3f | ||
|
|
3ba07ec0a8 | ||
|
|
7518306975 | ||
|
|
249361bb7f | ||
|
|
f633037d6b | ||
|
|
5dd36c95d6 | ||
|
|
6b3f814cd6 | ||
|
|
3a17a84434 | ||
|
|
916a5cb7dd | ||
|
|
d5484e15ca | ||
|
|
3d93f2cf56 | ||
|
|
8929e85fb1 | ||
|
|
f6c1407259 | ||
|
|
b79913240d | ||
|
|
4c24384243 | ||
|
|
1635ea90ca | ||
|
|
9724953b2f | ||
|
|
1266fd052e | ||
|
|
6c970efb09 | ||
|
|
a32ee3935d | ||
|
|
cc0c6bda42 | ||
|
|
1bee69ef15 | ||
|
|
3b0dd129ba | ||
|
|
c3c6215a24 | ||
|
|
8eca5ea4c6 | ||
|
|
e4c74ebe91 | ||
|
|
141aba9526 | ||
|
|
f5bb381191 | ||
|
|
b9151c6908 | ||
|
|
664b5aae21 | ||
|
|
b44b7365b5 | ||
|
|
23afc1b354 | ||
|
|
f711876347 | ||
|
|
5879d15692 | ||
|
|
30d4ea996b | ||
|
|
d2e5005fa0 | ||
|
|
865e381c7c | ||
|
|
8c1f1c4c52 | ||
|
|
c2414c498f | ||
|
|
4f8588b4bf | ||
|
|
98cb22c670 | ||
|
|
4a0aa6e608 | ||
|
|
88151b4287 | ||
|
|
3a42147d3e | ||
|
|
4631b57531 | ||
|
|
5417e59a50 | ||
|
|
9ac570cba7 | ||
|
|
3d1cd9fae4 | ||
|
|
3c72828500 | ||
|
|
d3ab764c76 | ||
|
|
eddb10d52c | ||
|
|
208493b6fd | ||
|
|
d46bc636de | ||
|
|
4dedb4d10a | ||
|
|
93b8856a20 | ||
|
|
07e1c91f1c | ||
|
|
2d8664d841 | ||
|
|
72227b8d83 | ||
|
|
34e2faa692 | ||
|
|
6347c32f7b | ||
|
|
a7f686af73 | ||
|
|
020fbdaf4f | ||
|
|
369e98c7ef | ||
|
|
29ad238307 | ||
|
|
ae03fac468 | ||
|
|
9534f9ee3a | ||
|
|
a25da734c1 | ||
|
|
0ac460563f | ||
|
|
8dd885a6c7 | ||
|
|
4926606841 | ||
|
|
d4d8a4e6c9 | ||
|
|
61a2586aed | ||
|
|
3be13ac10b | ||
|
|
8dabbd26e5 | ||
|
|
701f20cf04 | ||
|
|
3f23643121 | ||
|
|
37aafc5148 | ||
|
|
afc3752b33 | ||
|
|
cf1a4b83bf | ||
|
|
ba07c1d562 | ||
|
|
f5e3deafa9 | ||
|
|
43b393fb3f | ||
|
|
b5f96c3564 | ||
|
|
00d30f3dfd | ||
|
|
97a05cf95d | ||
|
|
1c829ae78a | ||
|
|
480d193b19 | ||
|
|
d96c8f2ce5 | ||
|
|
57c361b878 | ||
|
|
d62d5a8731 | ||
|
|
4fd62ae0af | ||
|
|
3be96d903b | ||
|
|
ae722375d8 | ||
|
|
5eb3695967 | ||
|
|
5847801a32 | ||
|
|
9b168423b8 | ||
|
|
6904678382 | ||
|
|
0f09d5e000 | ||
|
|
1eca2bc0ad | ||
|
|
f619cef67a | ||
|
|
0f034312a2 | ||
|
|
090d106376 | ||
|
|
cdf52507c0 | ||
|
|
8acaed750a | ||
|
|
79569f5a6f | ||
|
|
ed28eab835 | ||
|
|
37572113c3 | ||
|
|
1e56874338 | ||
|
|
d2639570bb | ||
|
|
f4b87b7ae7 | ||
|
|
755754e173 | ||
|
|
62bf5fa11d | ||
|
|
cd85c647ff | ||
|
|
64a0312bf7 | ||
|
|
59dbd75ca2 | ||
|
|
ddff1f4c0d | ||
|
|
375db97f1c | ||
|
|
8f3600c49a | ||
|
|
7f9b5c0515 | ||
|
|
b3130edd98 | ||
|
|
873f4bacd6 | ||
|
|
900b9174d3 | ||
|
|
dd4ea20656 | ||
|
|
0b403c3313 | ||
|
|
c7e9c698a5 | ||
|
|
8e80347022 | ||
|
|
977d1f474b | ||
|
|
646682b056 | ||
|
|
62ce30fb38 | ||
|
|
604ec4a50d | ||
|
|
9834e91c6d | ||
|
|
3254bc2bef | ||
|
|
5caf045ca0 | ||
|
|
71cac12479 | ||
|
|
d015f4c25a | ||
|
|
f7a35e8da3 | ||
|
|
519c83ea5a | ||
|
|
3cd4364566 | ||
|
|
495d19982d | ||
|
|
6b6ed48095 | ||
|
|
a0805e8411 | ||
|
|
9df0594573 | ||
|
|
0926df9c54 | ||
|
|
b001580334 | ||
|
|
25804f1725 | ||
|
|
04fcc393fd | ||
|
|
46173f6486 | ||
|
|
6430e43f0e | ||
|
|
dbc7018a11 | ||
|
|
9cb5c75b4e | ||
|
|
1ad47fc7b1 | ||
|
|
2a558885ad | ||
|
|
0f8c3b8d07 | ||
|
|
87e4dd8a0b | ||
|
|
97f3333194 | ||
|
|
f1ea4793bb | ||
|
|
07ff3088bf | ||
|
|
6a22a11187 | ||
|
|
b05fe2355e | ||
|
|
480296c185 | ||
|
|
d026dd6821 | ||
|
|
417174388b | ||
|
|
2ca482f060 | ||
|
|
750ae663fc | ||
|
|
0abbba0309 | ||
|
|
6130a10d14 | ||
|
|
52827f4a46 | ||
|
|
e96e0e3dbb | ||
|
|
876d466563 | ||
|
|
83b902c86b | ||
|
|
ee4bd71855 | ||
|
|
dcfd85e933 | ||
|
|
a4e003ebf1 | ||
|
|
2732a183f3 | ||
|
|
0d217c680a | ||
|
|
eb0da565f3 | ||
|
|
903017285b | ||
|
|
f1fa380bdd | ||
|
|
699bdf94a0 | ||
|
|
cc71e1f567 | ||
|
|
6708f6a5f4 | ||
|
|
923c00fd5f | ||
|
|
256043a3a1 | ||
|
|
4aacf2fcea | ||
|
|
c8c8844fff | ||
|
|
07021da671 | ||
|
|
f271920936 | ||
|
|
f0482dba20 | ||
|
|
8e553f5e4c | ||
|
|
df280f956b | ||
|
|
fb5bed1416 |
19
.editorconfig
Normal file
19
.editorconfig
Normal file
@@ -0,0 +1,19 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
|
||||
[*.java]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
charset = latin1
|
||||
continuation_indent_size = 8
|
||||
|
||||
[*.xml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
charset = latin1
|
||||
continuation_indent_size = 8
|
||||
7
.github/ISSUE_TEMPLATE.md
vendored
Normal file
7
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--
|
||||
For Security Vulnerabilities, please use https://pivotal.io/security#reporting
|
||||
-->
|
||||
|
||||
<!--
|
||||
Thanks for raising a Spring Session issue. Please provide a brief description of your problem along with the version of Spring Session that you are using. If possible, please also consider putting together a sample application that reproduces the issue.
|
||||
-->
|
||||
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--
|
||||
For Security Vulnerabilities, please use https://pivotal.io/security#reporting
|
||||
-->
|
||||
|
||||
<!--
|
||||
Thanks for contributing to Spring Session. Please provide a brief description of your pull-request and reference any related issue numbers (prefix references with #).
|
||||
-->
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -7,4 +7,9 @@ bin
|
||||
.settings
|
||||
.project
|
||||
target
|
||||
out
|
||||
out
|
||||
.springBeans
|
||||
*.rdb
|
||||
.checkstyle
|
||||
!etc/eclipse/.checkstyle
|
||||
!**/src/**/build
|
||||
|
||||
20
.travis.yml
Normal file
20
.travis.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
language: java
|
||||
|
||||
sudo: required
|
||||
|
||||
services: docker
|
||||
|
||||
jdk: oraclejdk8
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
||||
install: true
|
||||
|
||||
script: ./gradlew clean build --refresh-dependencies --no-daemon
|
||||
44
CODE_OF_CONDUCT.adoc
Normal file
44
CODE_OF_CONDUCT.adoc
Normal file
@@ -0,0 +1,44 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
||||
patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for
|
||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
||||
that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing this project. Project
|
||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
||||
from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
||||
with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
|
||||
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
|
||||
26
CONTRIBUTING.adoc
Normal file
26
CONTRIBUTING.adoc
Normal file
@@ -0,0 +1,26 @@
|
||||
= Contributing to Spring Session
|
||||
|
||||
Spring Session is released under the Apache 2.0 license. If you would like to contribute
|
||||
something, or simply want to hack on the code this document should help you get started.
|
||||
|
||||
== Code of Conduct
|
||||
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
|
||||
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
== Using GitHub issues
|
||||
|
||||
We use GitHub issues to track bugs and enhancements. If you have a general usage question
|
||||
please ask on https://stackoverflow.com[Stack Overflow]. The Spring Session team and the
|
||||
broader community monitor the https://stackoverflow.com/tags/spring-session[`spring-session`]
|
||||
tag.
|
||||
|
||||
If you are reporting a bug, please help to speed up problem diagnosis by providing as much
|
||||
information as possible. Ideally, that would include a small sample project that
|
||||
reproduces the problem.
|
||||
|
||||
|
||||
|
||||
== Sign the Contributor License Agreement
|
||||
If you have not previously done so, please fill out and
|
||||
submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].
|
||||
|
||||
179
Jenkinsfile
vendored
Normal file
179
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
properties([
|
||||
buildDiscarder(logRotator(numToKeepStr: '10')),
|
||||
pipelineTriggers([
|
||||
cron('@daily')
|
||||
]),
|
||||
])
|
||||
|
||||
def SUCCESS = hudson.model.Result.SUCCESS.toString()
|
||||
currentBuild.result = SUCCESS
|
||||
|
||||
try {
|
||||
parallel check: {
|
||||
stage('Check') {
|
||||
timeout(time: 45, unit: 'MINUTES') {
|
||||
node('linux') {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
|
||||
sh './gradlew clean check --no-daemon --refresh-dependencies --stacktrace'
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: check'
|
||||
throw e
|
||||
}
|
||||
finally {
|
||||
junit '**/build/test-results/*/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
jdk9: {
|
||||
stage('JDK 9') {
|
||||
timeout(time: 45, unit: 'MINUTES') {
|
||||
node('linux') {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${tool 'jdk9'}"]) {
|
||||
sh './gradlew clean test --no-daemon --refresh-dependencies --stacktrace'
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: jdk9'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
jdk10: {
|
||||
stage('JDK 10') {
|
||||
timeout(time: 45, unit: 'MINUTES') {
|
||||
node('linux') {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${tool 'jdk10'}"]) {
|
||||
sh './gradlew clean test --no-daemon --refresh-dependencies --stacktrace'
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: jdk10'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
jdk11: {
|
||||
stage('JDK 11') {
|
||||
timeout(time: 45, unit: 'MINUTES') {
|
||||
node('linux') {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${tool 'jdk11'}"]) {
|
||||
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies --stacktrace'
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: jdk11'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
jdk12: {
|
||||
stage('JDK 12') {
|
||||
timeout(time: 45, unit: 'MINUTES') {
|
||||
node('linux') {
|
||||
checkout scm
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${tool 'openjdk12'}"]) {
|
||||
sh './gradlew clean test integrationTest --no-daemon --refresh-dependencies --stacktrace'
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: jdk12'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentBuild.result == 'SUCCESS') {
|
||||
parallel artifacts: {
|
||||
stage('Deploy Artifacts') {
|
||||
node('linux') {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
|
||||
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
|
||||
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
|
||||
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
|
||||
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
|
||||
sh './gradlew deployArtifacts finalizeDeployArtifacts --no-daemon --refresh-dependencies --stacktrace -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: artifacts'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
docs: {
|
||||
stage('Deploy Docs') {
|
||||
node('linux') {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
|
||||
withEnv(["JAVA_HOME=${tool 'jdk8'}"]) {
|
||||
sh './gradlew deployDocs --no-daemon --refresh-dependencies --stacktrace -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME'
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: docs'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
def buildStatus = currentBuild.result
|
||||
def buildNotSuccess = !SUCCESS.equals(buildStatus)
|
||||
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
|
||||
|
||||
if (buildNotSuccess || lastBuildNotSuccess) {
|
||||
stage('Notify') {
|
||||
node {
|
||||
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
|
||||
|
||||
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
|
||||
def details = "The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"
|
||||
|
||||
emailext(
|
||||
subject: subject,
|
||||
body: details,
|
||||
recipientProviders: RECIPIENTS,
|
||||
to: "$SPRING_SESSION_TEAM_EMAILS"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
202
LICENSE.txt
Normal file
202
LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
197
README.adoc
197
README.adoc
@@ -1,191 +1,32 @@
|
||||
= Spring Session
|
||||
Rob Winch
|
||||
1.0.0.BUILD-SNAPSHOT
|
||||
:toc:
|
||||
:toc-placement: preamble
|
||||
:sectanchors:
|
||||
:icons: font
|
||||
:source-highlighter: prettify
|
||||
:idseparator: -
|
||||
:idprefix:
|
||||
:doctype: book
|
||||
:spring-session-version: 1.0.0.BUILD-SNAPSHOT
|
||||
|
||||
Spring Session aims to provide a common infrastructure for managing sessions. This allows for:
|
||||
image:https://travis-ci.org/spring-projects/spring-session.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-session"] image:https://badges.gitter.im/spring-projects/spring-session.svg[link="https://gitter.im/spring-projects/spring-session?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
|
||||
|
||||
* Accessing a session from any environment (i.e. web, messaging infrastructure, etc)
|
||||
* In a web environment
|
||||
** Support for clustering in a vendor neutral way
|
||||
** Pluggable strategy for determining the session id
|
||||
** Easily keep the HttpSession alive when a WebSocket is active
|
||||
Spring Session provides an API and implementations for managing a user's session information, while also making it trivial to support clustered sessions without being tied to an application container specific solution.
|
||||
It also provides transparent integration with:
|
||||
|
||||
= Quick Start
|
||||
* `HttpSession` - allows replacing the `HttpSession` in an application container (i.e. Tomcat) neutral way, with support for providing session IDs in headers to work with RESTful APIs.
|
||||
* `WebSocket` - provides the ability to keep the `HttpSession` alive when receiving WebSocket messages
|
||||
* `WebSession` - allows replacing the Spring WebFlux's `WebSession` in an application container neutral way.
|
||||
|
||||
This section describes how to use Spring Session to use Redis when interacting with a web application's HttpSession. If you'd like to skip the reading, you can also refer to the <<sample>>
|
||||
== Modules
|
||||
|
||||
== Updating Dependencies
|
||||
Before you use the project, you must ensure to update your dependencies. Instructions for building with Maven and Gradle have been provided below:
|
||||
Spring Session consists of the following modules:
|
||||
|
||||
* <<building-with-maven>>
|
||||
* <<building-with-gradle>>
|
||||
* Spring Session Core - provides core Spring Session functionalities and APIs
|
||||
* Spring Session Data Redis - provides `SessionRepository` and `ReactiveSessionRepository` implementation backed by Redis and configuration support
|
||||
* Spring Session JDBC - provides `SessionRepository` implementation backed by a relational database and configuration support
|
||||
* Spring Session Hazelcast - provides `SessionRepository` implementation backed by Hazelcast and configuration support
|
||||
|
||||
=== Building with Maven
|
||||
== Code of Conduct
|
||||
|
||||
The project is available in the https://github.com/spring-projects/spring-framework/wiki/SpringSource-repository-FAQ[Spring Maven Repository]. If you are using Maven, you will want to make the following updates.
|
||||
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
|
||||
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
**Using the latest Snapshot in Maven**
|
||||
== Spring Session Project Site
|
||||
|
||||
If you want the latest snapshot, ensure you have the following repository in your pom.xml:
|
||||
You can find the documentation, issue management, support, samples, and guides for using Spring Session at https://projects.spring.io/spring-session/
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
----
|
||||
== License
|
||||
|
||||
Then ensure you have added the dependency:
|
||||
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>{spring-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
<version>1.3.0.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
<version>2.2</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
=== Building with Gradle
|
||||
|
||||
**Using the latest Snapshot in Gradle**
|
||||
|
||||
If you want the latest snapshot, ensure you have the following repository in your pom.xml:
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
repositories {
|
||||
maven { url 'https://repo.spring.io/libs-snapshot' }
|
||||
}
|
||||
----
|
||||
|
||||
Then ensure you have added the dependency:
|
||||
|
||||
[source,groovy]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
dependencies {
|
||||
compile "org.springframework.session:spring-session:{spring-session-version}",
|
||||
"org.springframework:spring-web:{spring-version}",
|
||||
"org.springframework.data:spring-data-redis:1.3.0.RELEASE",
|
||||
"redis.clients:jedis:2.4.1",
|
||||
"org.apache.commons:commons-pool2:2.2"
|
||||
}
|
||||
----
|
||||
|
||||
== Spring Configuration
|
||||
|
||||
Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class Config {
|
||||
|
||||
@Bean
|
||||
public JedisConnectionFactory connectionFactory() throws Exception {
|
||||
return new JedisConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String,Session> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Session> template = new RedisTemplate<String, Session>();
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, Session> redisTemplate) {
|
||||
return new RedisOperationsSessionRepository(redisTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionRepositoryFilter sessionFilter(RedisOperationsSessionRepository sessionRepository) {
|
||||
return new SessionRepositoryFilter(sessionRepository);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In our example, we are connecting to the default port (6379). For more information on configuring Spring Data Redis, refer to the http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/[reference documentation].
|
||||
|
||||
== Servlet Initialization
|
||||
|
||||
We next need to be sure our Servlet Container (i.e. Tomcat) is properly configured.
|
||||
|
||||
. First we need ensure that our `Config` class from above was loaded. In the example below we do this by extending `AbstractContextLoaderInitializer` and implementing `createRootApplicationContext`.
|
||||
. Next we need to be sure the `SessionRepositoryFilter` is regsitered with the Servlet Container. We can do this by mapping a `DelegatingFilterProxy` to every request with the same name as the bean name of our `SessionRepositoryFilter`. In our instance, the bean name is the method name we used to create our `SessionRepositoryFilter`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class Initializer extends AbstractContextLoaderInitializer {
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
super.onStartup(servletContext);
|
||||
servletContext.addFilter("sessionFilter", DelegatingFilterProxy.class)
|
||||
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebApplicationContext createRootApplicationContext() {
|
||||
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
|
||||
context.register(Config.class);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
= Sample
|
||||
|
||||
The code contains a https://github.com/spring-projects/spring-session/tree/master/samples/web[sample web application]. To run the sample:
|
||||
|
||||
. Obtain the source by https://github.com/spring-projects/spring-session[cloning the repository] or https://github.com/spring-projects/spring-session/archive/master.zip[downloading] it.
|
||||
. Run the application using gradle
|
||||
.. Linux / OSX `./gradlew tomcatRun`
|
||||
.. Windows `.\gradlew.bat tomcatRun`
|
||||
. Visit http://localhost:8080/
|
||||
|
||||
= Benefits
|
||||
|
||||
* This can make clustering much easier. This is nice because the clustering setup is done in a vendor neutral way. Furthermore, in some environments (i.e. PaaS solutions) developers cannot modify the cluster settings easily.
|
||||
* We can use different strategies for determining the session id. This gives us at least a few benefits
|
||||
** Allowing for a single browser to have multiple simultaneous sessions in a transparent fashion. For example, many developers wish to allow a user to authenticate with multiple accounts and switch between them similar to how you can in gmail.
|
||||
** When using a REST API, the session can be specified using a header instead of the JSESSIONID cookie (which leaks implementation details to the client). Many would argue that session is bad in REST because it has state, but it is important to note that session is just a form of cache and used responsibly it will increase performance & security.
|
||||
** When a session id is acquired in a header, we can default CSRF protection to off. This is because if the session id is found in the header we know that it is impossible to be a CSRF attack since, unlike cookies, headers must be manually populated.
|
||||
* We can easily keep the HttpSession and WebSocket Session in sync. Imagine a web application like gmail where you can authenticate and either write emails (HTTP requests) or chat (WebSocket). In standard servlet environment there is no way to keep the HttpSession alive through the WebSocket so you must ping the server. With our own session strategy we can have the WebSocket messages automatically keep the HttpSession alive. We can also destroy both sessions at once easily.
|
||||
* We can provide hooks to allow users to invalidate sessions that should not be active. For example, if you look in the lower right of gmail you can see the last account activity and click "Details". This shows a listing of all the active sessions along with the IP address, location, and browser information for your account.
|
||||
** Users can look through this and determine if anything is suspicious (i.e. if their account has a session that is associated to a country they have never been) and invalidate that session and change their password.
|
||||
** Another useful example is perhaps they checked their mail at the library and forgot to log out. With this custom mechanism this is very possible.
|
||||
* Spring Security currently supports restricting the number of concurrent sessions each user can have. The implementation works, but does so passively since we cannot get a handle to the session from the session id. Specifically, each time a user requests a page we check to see if that session id is valid in a separate data store. If it is no longer valid, we invalidate the session. With this new mechanism we can invalidate the session from the session id.
|
||||
Spring Session is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].
|
||||
|
||||
63
build.gradle
63
build.gradle
@@ -1,35 +1,40 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url "https://repo.spring.io/plugins-release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.gradle.api.plugins:gradle-tomcat-plugin:1.2.3")
|
||||
classpath("org.springframework.build.gradle:propdeps-plugin:0.0.6")
|
||||
classpath("org.springframework.build.gradle:spring-io-plugin:0.0.3.RELEASE")
|
||||
classpath('me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1')
|
||||
classpath('org.asciidoctor:asciidoctor-gradle-plugin:0.7.0')
|
||||
classpath('org.asciidoctor:asciidoctor-java-integration:0.1.4.preview.1')
|
||||
}
|
||||
ext {
|
||||
releaseBuild = version.endsWith('RELEASE')
|
||||
snapshotBuild = version.endsWith('SNAPSHOT')
|
||||
milestoneBuild = !(releaseBuild || snapshotBuild)
|
||||
|
||||
springBootVersion = '2.2.0.M2'
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { url 'https://repo.spring.io/plugins-release/' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.25.RELEASE'
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
||||
classpath 'io.spring.nohttp:nohttp-gradle:0.0.2.RELEASE'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.convention.root'
|
||||
apply plugin: 'io.spring.nohttp'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
group = 'org.springframework.session'
|
||||
description = 'Spring Session'
|
||||
|
||||
ext.JAVA_GRADLE = "$rootDir/gradle/java.gradle"
|
||||
ext.TOMCAT_GRADLE = "$rootDir/gradle/tomcat.gradle"
|
||||
subprojects {
|
||||
plugins.withType(JavaPlugin) {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
apply plugin: 'sonar-runner'
|
||||
|
||||
|
||||
sonarRunner {
|
||||
sonarProperties {
|
||||
property "sonar.java.coveragePlugin", "jacoco"
|
||||
property "sonar.projectName", "Spring Session"
|
||||
property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec"
|
||||
property "sonar.links.homepage", 'https://github.com/spring-projects/spring-session'
|
||||
property "sonar.links.ci", 'https://build.spring.io/browse/SESSION'
|
||||
property "sonar.links.issue", 'https://github.com/spring-projects/spring-session/issues'
|
||||
property "sonar.links.scm", 'https://github.com/spring-projects/spring-session'
|
||||
property "sonar.links.scm_dev", 'https://github.com/spring-projects/spring-session.git'
|
||||
property "sonar.java.coveragePlugin", "jacoco"
|
||||
}
|
||||
}
|
||||
tasks.withType(Test) {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
37
etc/checkstyle/checkstyle.xml
Normal file
37
etc/checkstyle/checkstyle.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${config_loc}/suppressions.xml"/>
|
||||
</module>
|
||||
<module name="io.spring.javaformat.checkstyle.SpringChecks"/>
|
||||
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
|
||||
<property name="regexp" value="true"/>
|
||||
<property name="illegalPkgs"
|
||||
value="^sun.*, ^org\.apache\.commons\.(?!compress|dbcp2|lang|lang3|logging|pool2).*, ^com\.google\.common.*, ^org\.flywaydb\.core\.internal.*, ^org\.testcontainers\.shaded.*"/>
|
||||
<property name="illegalClasses"
|
||||
value="^reactor\.core\.support\.Assert, ^org\.junit\.rules\.ExpectedException, ^org\.slf4j\.LoggerFactory"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="format" value="org\.junit\.Assert\.assert"/>
|
||||
<property name="message" value="Please use AssertJ imports."/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="format"
|
||||
value="assertThatExceptionOfType\((NullPointerException|IllegalArgumentException|IOException|IllegalStateException)\.class\)"/>
|
||||
<property name="message" value="Please use specialized AssertJ assertThat*Exception method."/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</module>
|
||||
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="format" value="org\.mockito\.Mockito\.(when|doThrow|doAnswer)"/>
|
||||
<property name="message" value="Please use BDDMockito imports."/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</module>
|
||||
</module>
|
||||
</module>
|
||||
11
etc/checkstyle/suppressions.xml
Normal file
11
etc/checkstyle/suppressions.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE suppressions PUBLIC "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
|
||||
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
||||
<suppressions>
|
||||
<suppress files="[\\/]src[\\/]integration-test[\\/]java[\\/]" checks="Javadoc*"/>
|
||||
<suppress files="[\\/]spring-session-docs[\\/]" checks="Javadoc*"/>
|
||||
<suppress files="[\\/]spring-session-docs[\\/]" checks="InnerTypeLast"/>
|
||||
<suppress files="[\\/]spring-session-samples[\\/]" checks="Javadoc*"/>
|
||||
<suppress files="[\\/]spring-session-samples[\\/].+Application\.java" checks="HideUtilityClassConstructor"/>
|
||||
<suppress files="SessionRepositoryFilterTests\.java" checks="SpringLambda"/>
|
||||
</suppressions>
|
||||
11
etc/eclipse/.checkstyle
Normal file
11
etc/eclipse/.checkstyle
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
|
||||
<local-check-config name="Spring Session Checkstyle" location="${configDir}/checkstyle.xml" type="external" description="">
|
||||
<property name="configDir" value="${configDir}"/>
|
||||
<additional-data name="protect-config-file" value="false"/>
|
||||
</local-check-config>
|
||||
<fileset name="all" enabled="true" check-config-name="Spring Session Checkstyle" local="true">
|
||||
<file-match-pattern match-pattern="." include-pattern="true"/>
|
||||
</fileset>
|
||||
</fileset-config>
|
||||
295
etc/eclipse/eclipse-code-formatter.xml
Normal file
295
etc/eclipse/eclipse-code-formatter.xml
Normal file
@@ -0,0 +1,295 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="12">
|
||||
<profile kind="CodeFormatterProfile" name="Spring Session Java Conventions" version="12">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="90"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="90"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
391
etc/eclipse/org.eclipse.jdt.core.prefs
Normal file
391
etc/eclipse/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,391 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.codeComplete.argumentPrefixes=
|
||||
org.eclipse.jdt.core.codeComplete.argumentSuffixes=
|
||||
org.eclipse.jdt.core.codeComplete.fieldPrefixes=
|
||||
org.eclipse.jdt.core.codeComplete.fieldSuffixes=
|
||||
org.eclipse.jdt.core.codeComplete.localPrefixes=
|
||||
org.eclipse.jdt.core.codeComplete.localSuffixes=
|
||||
org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
|
||||
org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
|
||||
org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
|
||||
org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
|
||||
org.eclipse.jdt.core.compiler.problem.deadCode=warning
|
||||
org.eclipse.jdt.core.compiler.problem.deprecation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
|
||||
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
|
||||
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
|
||||
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning
|
||||
org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=default
|
||||
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
|
||||
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
|
||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags
|
||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning
|
||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=default
|
||||
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
|
||||
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
|
||||
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.nullReference=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
|
||||
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
|
||||
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
|
||||
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
|
||||
org.eclipse.jdt.core.compiler.processAnnotations=disabled
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
||||
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
|
||||
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
|
||||
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
|
||||
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
|
||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_header=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_html=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_source_code=false
|
||||
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
|
||||
org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
|
||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert
|
||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
|
||||
org.eclipse.jdt.core.formatter.comment.line_length=90
|
||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
|
||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
|
||||
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
|
||||
org.eclipse.jdt.core.formatter.compact_else_if=true
|
||||
org.eclipse.jdt.core.formatter.continuation_indentation=2
|
||||
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
|
||||
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
|
||||
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
|
||||
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
|
||||
org.eclipse.jdt.core.formatter.indent_empty_lines=false
|
||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
|
||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
|
||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
|
||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
|
||||
org.eclipse.jdt.core.formatter.indentation.size=4
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
|
||||
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
|
||||
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
|
||||
org.eclipse.jdt.core.formatter.lineSplit=90
|
||||
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
|
||||
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
|
||||
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
|
||||
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
|
||||
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
|
||||
org.eclipse.jdt.core.formatter.tabulation.char=tab
|
||||
org.eclipse.jdt.core.formatter.tabulation.size=4
|
||||
org.eclipse.jdt.core.formatter.use_on_off_tags=true
|
||||
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
|
||||
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
|
||||
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
|
||||
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
|
||||
org.eclipse.jdt.core.javaFormatter=org.springframework.ide.eclipse.jdt.formatter.javaformatter
|
||||
125
etc/eclipse/org.eclipse.jdt.ui.prefs
Normal file
125
etc/eclipse/org.eclipse.jdt.ui.prefs
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
version=1.0.0.M1
|
||||
version=2.2.0.M2
|
||||
|
||||
33
gradle/dependency-management.gradle
Normal file
33
gradle/dependency-management.gradle
Normal file
@@ -0,0 +1,33 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
|
||||
mavenBom 'io.projectreactor:reactor-bom:Dysprosium-M2'
|
||||
mavenBom 'org.junit:junit-bom:5.4.2'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.2.0.M3'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Moore-RC1'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.2.0.M3'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.11.3'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependencySet(group: 'com.hazelcast', version: '3.12.1') {
|
||||
entry 'hazelcast'
|
||||
entry 'hazelcast-client'
|
||||
}
|
||||
|
||||
dependency 'com.h2database:h2:1.4.199'
|
||||
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8'
|
||||
dependency 'com.zaxxer:HikariCP:3.3.1'
|
||||
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
|
||||
dependency 'io.lettuce:lettuce-core:5.1.7.RELEASE'
|
||||
dependency 'javax.annotation:javax.annotation-api:1.3.2'
|
||||
dependency 'javax.servlet:javax.servlet-api:4.0.1'
|
||||
dependency 'mysql:mysql-connector-java:8.0.16'
|
||||
dependency 'org.apache.derby:derby:10.14.2.0'
|
||||
dependency 'org.assertj:assertj-core:3.12.2'
|
||||
dependency 'org.hsqldb:hsqldb:2.5.0'
|
||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.4.1'
|
||||
dependency 'org.mockito:mockito-core:2.28.2'
|
||||
dependency 'org.postgresql:postgresql:42.2.5'
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'javadocHotfix'
|
||||
apply plugin: 'eclipse-wtp'
|
||||
apply plugin: 'propdeps'
|
||||
apply plugin: 'propdeps-idea'
|
||||
apply plugin: 'propdeps-eclipse'
|
||||
|
||||
group = 'org.springframework.session'
|
||||
|
||||
ext.jstlVersion = '1.2.1'
|
||||
ext.servletApiVersion = '3.0.1'
|
||||
ext.springSecurityVersion = '3.2.4.RELEASE'
|
||||
ext.springVersion = '4.0.2.RELEASE'
|
||||
ext.groovyVersion = '2.0.5'
|
||||
ext.seleniumVersion = '2.33.0'
|
||||
ext.spockVersion = '0.7-groovy-2.0'
|
||||
ext.gebVersion = '0.9.0'
|
||||
|
||||
ext.spockDependencies = [
|
||||
dependencies.create("org.spockframework:spock-core:$spockVersion") {
|
||||
exclude group: 'junit', module: 'junit-dep'
|
||||
}
|
||||
]
|
||||
|
||||
ext.gebDependencies = spockDependencies + [
|
||||
"org.seleniumhq.selenium:selenium-htmlunit-driver:$seleniumVersion",
|
||||
"org.gebish:geb-spock:$gebVersion",
|
||||
'commons-httpclient:commons-httpclient:3.1',
|
||||
"org.codehaus.groovy:groovy:$groovyVersion"
|
||||
]
|
||||
|
||||
ext.jstlDependencies = [
|
||||
"javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:$jstlVersion",
|
||||
"org.apache.taglibs:taglibs-standard-jstlel:1.2.1"
|
||||
]
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'http://clojars.org/repo' }
|
||||
}
|
||||
|
||||
// Integration test setup
|
||||
configurations {
|
||||
integrationTestCompile {
|
||||
extendsFrom testCompile, optional, provided
|
||||
}
|
||||
integrationTestRuntime {
|
||||
extendsFrom integrationTestCompile, testRuntime
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
integrationTest {
|
||||
java.srcDir file('src/integration-test/java')
|
||||
groovy.srcDirs file('src/integration-test/groovy')
|
||||
resources.srcDir file('src/integration-test/resources')
|
||||
compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.integrationTestCompile
|
||||
runtimeClasspath = output + compileClasspath + configurations.integrationTestRuntime
|
||||
}
|
||||
}
|
||||
|
||||
task integrationTest(type: Test, dependsOn: jar) {
|
||||
testClassesDir = sourceSets.integrationTest.output.classesDir
|
||||
logging.captureStandardOutput(LogLevel.INFO)
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
maxParallelForks = 1
|
||||
reports {
|
||||
html.destination = project.file("$project.buildDir/reports/integration-tests/")
|
||||
junitXml.destination = project.file("$project.buildDir/integration-test-results/")
|
||||
}
|
||||
}
|
||||
|
||||
project.idea.module {
|
||||
scopes.TEST.plus += [project.configurations.integrationTestRuntime]
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url "https://repo.spring.io/plugins-release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.gradle.api.plugins:gradle-tomcat-plugin:1.2.3")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'war'
|
||||
apply plugin: 'tomcat'
|
||||
|
||||
dependencies {
|
||||
def tomcatVersion = '7.0.54'
|
||||
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
|
||||
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}"
|
||||
tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
|
||||
exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
|
||||
}
|
||||
}
|
||||
|
||||
[tomcatRun,tomcatRunWar]*.contextPath = '/'
|
||||
|
||||
|
||||
task integrationTomcatRun(type: org.gradle.api.plugins.tomcat.tasks.TomcatRun) {
|
||||
onlyIf { !sourceSets.integrationTest.allSource.empty }
|
||||
buildscriptClasspath = tomcatRun.buildscriptClasspath
|
||||
contextPath = tomcatRun.contextPath
|
||||
daemon = true
|
||||
tomcatClasspath = tomcatRun.tomcatClasspath
|
||||
webAppClasspath = tomcatRun.webAppClasspath
|
||||
webAppSourceDirectory = tomcatRun.webAppSourceDirectory
|
||||
doFirst {
|
||||
def mainOutputDir = project.sourceSets.main.output.classesDir
|
||||
if(mainOutputDir) {
|
||||
classesDirectory = mainOutputDir
|
||||
}
|
||||
// delay reserving ports to ensure they are still available
|
||||
def ports = reservePorts(3)
|
||||
httpPort = ports[0]
|
||||
ajpPort = ports[1]
|
||||
stopPort = ports[2]
|
||||
}
|
||||
}
|
||||
|
||||
task integrationTomcatStop(type: org.gradle.api.plugins.tomcat.tasks.TomcatStop) {
|
||||
onlyIf { !sourceSets.integrationTest.allSource.empty }
|
||||
doFirst {
|
||||
stopPort = integrationTomcatRun.stopPort
|
||||
}
|
||||
}
|
||||
|
||||
integrationTest {
|
||||
dependsOn integrationTomcatRun
|
||||
doFirst {
|
||||
def host = 'localhost:' + integrationTomcatRun.httpPort
|
||||
systemProperties['geb.build.baseUrl'] = 'http://'+host+'/' + integrationTomcatRun.contextPath
|
||||
systemProperties['geb.build.reportsDir'] = 'build/geb-reports'
|
||||
}
|
||||
finalizedBy integrationTomcatStop
|
||||
}
|
||||
|
||||
def reservePorts(int count) {
|
||||
def sockets = []
|
||||
for(int i in 1..count) {
|
||||
sockets << new ServerSocket(0)
|
||||
}
|
||||
def result = sockets*.localPort
|
||||
sockets*.close()
|
||||
result
|
||||
}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Wed Jun 18 14:02:09 CDT 2014
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-1.12-all.zip
|
||||
|
||||
126
gradlew
vendored
126
gradlew
vendored
@@ -1,4 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +22,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,9 +36,49 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
@@ -90,7 +105,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -114,6 +129,7 @@ fi
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
@@ -154,11 +170,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
30
gradlew.bat
vendored
30
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@@ -8,14 +24,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +62,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
@@ -60,11 +75,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>samples/web</name>
|
||||
<comment/>
|
||||
<projects/>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||
</natures>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments/>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||
<arguments/>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||
<arguments/>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<linkedResources/>
|
||||
</projectDescription>
|
||||
@@ -1,23 +0,0 @@
|
||||
apply from: JAVA_GRADLE
|
||||
apply from: TOMCAT_GRADLE
|
||||
|
||||
tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
sonarRunner {
|
||||
skipProject = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session'),
|
||||
"org.springframework:spring-web:$springVersion",
|
||||
"org.springframework.data:spring-data-redis:1.3.0.RELEASE",
|
||||
"redis.clients:jedis:2.4.1",
|
||||
"org.apache.commons:commons-pool2:2.2",
|
||||
"redis.embedded:embedded-redis:0.2",
|
||||
jstlDependencies
|
||||
|
||||
providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
|
||||
|
||||
testCompile 'junit:junit:4.11'
|
||||
|
||||
integrationTestCompile gebDependencies
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
import geb.spock.*
|
||||
import spock.lang.Stepwise
|
||||
import pages.*
|
||||
|
||||
/**
|
||||
* Tests the CAS sample application using service tickets.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Stepwise
|
||||
class AttributeTests extends GebReportingSpec {
|
||||
def 'first visit no attributes'() {
|
||||
when:
|
||||
to HomePage
|
||||
then:
|
||||
attributes.empty
|
||||
}
|
||||
|
||||
def 'create attribute'() {
|
||||
when:
|
||||
createAttribute('a','b')
|
||||
then:
|
||||
attributes.size() == 1
|
||||
attributes[0].name == 'a'
|
||||
attributes[0].value == 'b'
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package pages
|
||||
|
||||
import geb.*
|
||||
|
||||
/**
|
||||
* The home page
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class HomePage extends Page {
|
||||
static url = ''
|
||||
static at = { assert driver.title == 'Session Attributes'; true}
|
||||
static content = {
|
||||
form { $('form') }
|
||||
submit { $('input[type=submit]') }
|
||||
createAttribute(required:false) { name, value ->
|
||||
form.attributeName = name
|
||||
form.attributeValue = value
|
||||
submit.click(HomePage)
|
||||
}
|
||||
attributes { moduleList AttributeRow, $("table tr").tail() }
|
||||
}
|
||||
}
|
||||
class AttributeRow extends Module {
|
||||
static content = {
|
||||
cell { $("td", it) }
|
||||
name { cell(0).text() }
|
||||
value { cell(1).text() }
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
|
||||
import org.springframework.session.web.SessionRepositoryFilter;
|
||||
import redis.clients.jedis.Protocol;
|
||||
import redis.embedded.RedisServer;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration
|
||||
public class Config {
|
||||
|
||||
@Bean
|
||||
public RedisServerBean redisServer() {
|
||||
return new RedisServerBean();
|
||||
}
|
||||
|
||||
class RedisServerBean implements InitializingBean, DisposableBean {
|
||||
private RedisServer redisServer;
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
redisServer = new RedisServer(Protocol.DEFAULT_PORT);
|
||||
redisServer.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if(redisServer != null) {
|
||||
redisServer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JedisConnectionFactory connectionFactory() throws Exception {
|
||||
return new JedisConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String,Session> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Session> template = new RedisTemplate<String, Session>();
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, Session> redisTemplate) {
|
||||
return new RedisOperationsSessionRepository(redisTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionRepositoryFilter sessionFilter(RedisOperationsSessionRepository sessionRepository) {
|
||||
return new SessionRepositoryFilter(sessionRepository);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
|
||||
import org.springframework.web.context.AbstractContextLoaderInitializer;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class Initializer extends AbstractContextLoaderInitializer {
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
super.onStartup(servletContext);
|
||||
servletContext.addFilter("sessionFilter", DelegatingFilterProxy.class)
|
||||
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebApplicationContext createRootApplicationContext() {
|
||||
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
|
||||
context.register(Config.class);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,16 @@
|
||||
rootProject.name = 'spring-session-build'
|
||||
|
||||
include 'spring-session'
|
||||
include 'samples/web'
|
||||
include 'spring-session-core'
|
||||
include 'spring-session-data-redis'
|
||||
include 'spring-session-docs'
|
||||
include 'spring-session-hazelcast'
|
||||
include 'spring-session-jdbc'
|
||||
|
||||
file('spring-session-samples').eachDirMatch(~/spring-session-sample-.*/) { dir ->
|
||||
include dir.name
|
||||
project(":$dir.name").projectDir = dir
|
||||
}
|
||||
|
||||
rootProject.children.each { project ->
|
||||
project.buildFileName = "${project.name}.gradle"
|
||||
}
|
||||
|
||||
29
spring-session-core/spring-session-core.gradle
Normal file
29
spring-session-core/spring-session-core.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
description = "Spring Session"
|
||||
|
||||
dependencies {
|
||||
compile "org.springframework:spring-jcl"
|
||||
|
||||
optional "io.projectreactor:reactor-core"
|
||||
optional "javax.annotation:javax.annotation-api"
|
||||
optional "javax.servlet:javax.servlet-api"
|
||||
optional "org.springframework:spring-context"
|
||||
optional "org.springframework:spring-jdbc"
|
||||
optional "org.springframework:spring-messaging"
|
||||
optional "org.springframework:spring-web"
|
||||
optional "org.springframework:spring-webflux"
|
||||
optional "org.springframework:spring-websocket"
|
||||
optional "org.springframework.security:spring-security-core"
|
||||
optional "org.springframework.security:spring-security-web"
|
||||
|
||||
testCompile "io.projectreactor:reactor-test"
|
||||
testCompile "org.mockito:mockito-core"
|
||||
testCompile "edu.umd.cs.mtc:multithreadedtc"
|
||||
testCompile "org.springframework:spring-test"
|
||||
testCompile "org.assertj:assertj-core"
|
||||
testCompile "org.springframework.security:spring-security-core"
|
||||
testCompile "org.junit.jupiter:junit-jupiter-api"
|
||||
testCompile "org.junit.jupiter:junit-jupiter-params"
|
||||
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Extends a basic {@link SessionRepository} to allow finding sessions by the specified
|
||||
* index name and index value.
|
||||
*
|
||||
* @param <S> the type of Session being managed by this
|
||||
* {@link FindByIndexNameSessionRepository}
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public interface FindByIndexNameSessionRepository<S extends Session>
|
||||
extends SessionRepository<S> {
|
||||
|
||||
/**
|
||||
* A session index that contains the current principal name (i.e. username).
|
||||
* <p>
|
||||
* It is the responsibility of the developer to ensure the index is populated since
|
||||
* Spring Session is not aware of the authentication mechanism being used.
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
String PRINCIPAL_NAME_INDEX_NAME = FindByIndexNameSessionRepository.class.getName()
|
||||
.concat(".PRINCIPAL_NAME_INDEX_NAME");
|
||||
|
||||
/**
|
||||
* Find a {@link Map} of the session id to the {@link Session} of all sessions that
|
||||
* contain the specified index name index value.
|
||||
*
|
||||
* @param indexName the name of the index (i.e.
|
||||
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME})
|
||||
* @param indexValue the value of the index to search for.
|
||||
* @return a {@code Map} (never {@code null}) of the session id to the {@code Session}
|
||||
* of all sessions that contain the specified index name and index value. If no
|
||||
* results are found, an empty {@code Map} is returned.
|
||||
*/
|
||||
Map<String, S> findByIndexNameAndIndexValue(String indexName, String indexValue);
|
||||
|
||||
/**
|
||||
* Find a {@link Map} of the session id to the {@link Session} of all sessions that
|
||||
* contain the index with the name
|
||||
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the
|
||||
* specified principal name.
|
||||
*
|
||||
* @param principalName the principal name
|
||||
* @return a {@code Map} (never {@code null}) of the session id to the {@code Session}
|
||||
* of all sessions that contain the specified principal name. If no results are found,
|
||||
* an empty {@code Map} is returned.
|
||||
* @since 2.1.0
|
||||
*/
|
||||
default Map<String, S> findByPrincipalName(String principalName) {
|
||||
|
||||
return findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A {@link Session} implementation that is backed by a {@link java.util.Map}. The
|
||||
* defaults for the properties are:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>id - a secure random generated id</li>
|
||||
* <li>creationTime - the moment the {@link MapSession} was instantiated</li>
|
||||
* <li>lastAccessedTime - the moment the {@link MapSession} was instantiated</li>
|
||||
* <li>maxInactiveInterval - 30 minutes</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* This implementation has no synchronization, so it is best to use the copy constructor
|
||||
* when working on multiple threads.
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 1.0
|
||||
*/
|
||||
public final class MapSession implements Session, Serializable {
|
||||
/**
|
||||
* Default {@link #setMaxInactiveInterval(Duration)} (30 minutes).
|
||||
*/
|
||||
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800;
|
||||
|
||||
private String id;
|
||||
private final String originalId;
|
||||
private Map<String, Object> sessionAttrs = new HashMap<>();
|
||||
private Instant creationTime = Instant.now();
|
||||
private Instant lastAccessedTime = this.creationTime;
|
||||
|
||||
/**
|
||||
* Defaults to 30 minutes.
|
||||
*/
|
||||
private Duration maxInactiveInterval = Duration.ofSeconds(DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
|
||||
|
||||
/**
|
||||
* Creates a new instance with a secure randomly generated identifier.
|
||||
*/
|
||||
public MapSession() {
|
||||
this(generateId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified id. This is preferred to the default
|
||||
* constructor when the id is known to prevent unnecessary consumption on entropy
|
||||
* which can be slow.
|
||||
*
|
||||
* @param id the identifier to use
|
||||
*/
|
||||
public MapSession(String id) {
|
||||
this.id = id;
|
||||
this.originalId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance from the provided {@link Session}.
|
||||
*
|
||||
* @param session the {@link Session} to initialize this {@link Session} with. Cannot
|
||||
* be null.
|
||||
*/
|
||||
public MapSession(Session session) {
|
||||
if (session == null) {
|
||||
throw new IllegalArgumentException("session cannot be null");
|
||||
}
|
||||
this.id = session.getId();
|
||||
this.originalId = this.id;
|
||||
this.sessionAttrs = new HashMap<>(
|
||||
session.getAttributeNames().size());
|
||||
for (String attrName : session.getAttributeNames()) {
|
||||
Object attrValue = session.getAttribute(attrName);
|
||||
if (attrValue != null) {
|
||||
this.sessionAttrs.put(attrName, attrValue);
|
||||
}
|
||||
}
|
||||
this.lastAccessedTime = session.getLastAccessedTime();
|
||||
this.creationTime = session.getCreationTime();
|
||||
this.maxInactiveInterval = session.getMaxInactiveInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.lastAccessedTime = lastAccessedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getCreationTime() {
|
||||
return this.creationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original session id.
|
||||
* @return the original session id
|
||||
* @see #changeSessionId()
|
||||
*/
|
||||
public String getOriginalId() {
|
||||
return this.originalId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String changeSessionId() {
|
||||
String changedId = generateId();
|
||||
setId(changedId);
|
||||
return changedId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getLastAccessedTime() {
|
||||
return this.lastAccessedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(Duration interval) {
|
||||
this.maxInactiveInterval = interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getMaxInactiveInterval() {
|
||||
return this.maxInactiveInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return isExpired(Instant.now());
|
||||
}
|
||||
|
||||
boolean isExpired(Instant now) {
|
||||
if (this.maxInactiveInterval.isNegative()) {
|
||||
return false;
|
||||
}
|
||||
return now.minus(this.maxInactiveInterval).compareTo(this.lastAccessedTime) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
return (T) this.sessionAttrs.get(attributeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNames() {
|
||||
return new HashSet<>(this.sessionAttrs.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
if (attributeValue == null) {
|
||||
removeAttribute(attributeName);
|
||||
}
|
||||
else {
|
||||
this.sessionAttrs.put(attributeName, attributeValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String attributeName) {
|
||||
this.sessionAttrs.remove(attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time that this {@link Session} was created. The default is when the
|
||||
* {@link Session} was instantiated.
|
||||
* @param creationTime the time that this {@link Session} was created.
|
||||
*/
|
||||
public void setCreationTime(Instant creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the identifier for this {@link Session}. The id should be a secure random
|
||||
* generated value to prevent malicious users from guessing this value. The default is
|
||||
* a secure random generated identifier.
|
||||
*
|
||||
* @param id the identifier for this session.
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Session && this.id.equals(((Session) obj).getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.id.hashCode();
|
||||
}
|
||||
|
||||
private static String generateId() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 7160779239673823561L;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
|
||||
/**
|
||||
* A {@link SessionRepository} backed by a {@link java.util.Map} and that uses a
|
||||
* {@link MapSession}. The injected {@link java.util.Map} can be backed by a distributed
|
||||
* NoSQL store like Hazelcast, for instance. Note that the supplied map itself is
|
||||
* responsible for purging the expired sessions.
|
||||
*
|
||||
* <p>
|
||||
* The implementation does NOT support firing {@link SessionDeletedEvent} or
|
||||
* {@link SessionExpiredEvent}.
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
public class MapSessionRepository implements SessionRepository<MapSession> {
|
||||
|
||||
/**
|
||||
* If non-null, this value is used to override
|
||||
* {@link Session#setMaxInactiveInterval(Duration)}.
|
||||
*/
|
||||
private Integer defaultMaxInactiveInterval;
|
||||
|
||||
private final Map<String, Session> sessions;
|
||||
|
||||
/**
|
||||
* Creates a new instance backed by the provided {@link java.util.Map}. This allows
|
||||
* injecting a distributed {@link java.util.Map}.
|
||||
*
|
||||
* @param sessions the {@link java.util.Map} to use. Cannot be null.
|
||||
*/
|
||||
public MapSessionRepository(Map<String, Session> sessions) {
|
||||
if (sessions == null) {
|
||||
throw new IllegalArgumentException("sessions cannot be null");
|
||||
}
|
||||
this.sessions = sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* If non-null, this value is used to override
|
||||
* {@link Session#setMaxInactiveInterval(Duration)}.
|
||||
* @param defaultMaxInactiveInterval the number of seconds that the {@link Session}
|
||||
* should be kept alive between client requests.
|
||||
*/
|
||||
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
|
||||
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(MapSession session) {
|
||||
if (!session.getId().equals(session.getOriginalId())) {
|
||||
this.sessions.remove(session.getOriginalId());
|
||||
}
|
||||
this.sessions.put(session.getId(), new MapSession(session));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapSession findById(String id) {
|
||||
Session saved = this.sessions.get(id);
|
||||
if (saved == null) {
|
||||
return null;
|
||||
}
|
||||
if (saved.isExpired()) {
|
||||
deleteById(saved.getId());
|
||||
return null;
|
||||
}
|
||||
return new MapSession(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(String id) {
|
||||
this.sessions.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapSession createSession() {
|
||||
MapSession result = new MapSession();
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
result.setMaxInactiveInterval(
|
||||
Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
|
||||
/**
|
||||
* A {@link ReactiveSessionRepository} backed by a {@link Map} and that uses a
|
||||
* {@link MapSession}. The injected {@link java.util.Map} can be backed by a distributed
|
||||
* NoSQL store like Hazelcast, for instance. Note that the supplied map must be a
|
||||
* non-blocking map, and is itself responsible for purging the expired sessions.
|
||||
*
|
||||
* <p>
|
||||
* The implementation does NOT support firing {@link SessionDeletedEvent} or
|
||||
* {@link SessionExpiredEvent}.
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ReactiveMapSessionRepository implements ReactiveSessionRepository<MapSession> {
|
||||
|
||||
/**
|
||||
* If non-null, this value is used to override
|
||||
* {@link Session#setMaxInactiveInterval(Duration)}.
|
||||
*/
|
||||
private Integer defaultMaxInactiveInterval;
|
||||
|
||||
private final Map<String, Session> sessions;
|
||||
|
||||
/**
|
||||
* Creates a new instance backed by the provided {@link Map}. This allows injecting a
|
||||
* distributed {@link Map}.
|
||||
*
|
||||
* @param sessions the {@link Map} to use. Cannot be null.
|
||||
*/
|
||||
public ReactiveMapSessionRepository(Map<String, Session> sessions) {
|
||||
if (sessions == null) {
|
||||
throw new IllegalArgumentException("sessions cannot be null");
|
||||
}
|
||||
this.sessions = sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* If non-null, this value is used to override
|
||||
* {@link Session#setMaxInactiveInterval(Duration)}.
|
||||
* @param defaultMaxInactiveInterval the number of seconds that the {@link Session}
|
||||
* should be kept alive between client requests.
|
||||
*/
|
||||
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
|
||||
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(MapSession session) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
if (!session.getId().equals(session.getOriginalId())) {
|
||||
this.sessions.remove(session.getOriginalId());
|
||||
}
|
||||
this.sessions.put(session.getId(), new MapSession(session));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MapSession> findById(String id) {
|
||||
// @formatter:off
|
||||
return Mono.defer(() -> Mono.justOrEmpty(this.sessions.get(id))
|
||||
.filter((session) -> !session.isExpired())
|
||||
.map(MapSession::new)
|
||||
.switchIfEmpty(deleteById(id).then(Mono.empty())));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteById(String id) {
|
||||
return Mono.fromRunnable(() -> this.sessions.remove(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MapSession> createSession() {
|
||||
return Mono.defer(() -> {
|
||||
MapSession result = new MapSession();
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
result.setMaxInactiveInterval(
|
||||
Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
}
|
||||
return Mono.just(result);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A repository interface for managing {@link Session} instances.
|
||||
*
|
||||
* @param <S> the {@link Session} type
|
||||
* @author Rob Winch
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveSessionRepository<S extends Session> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link Session} that is capable of being persisted by this
|
||||
* {@link ReactiveSessionRepository}.
|
||||
*
|
||||
* <p>
|
||||
* This allows optimizations and customizations in how the {@link Session} is
|
||||
* persisted. For example, the implementation returned might keep track of the changes
|
||||
* ensuring that only the delta needs to be persisted on a save.
|
||||
* </p>
|
||||
*
|
||||
* @return a new {@link Session} that is capable of being persisted by this
|
||||
* {@link ReactiveSessionRepository}
|
||||
*/
|
||||
Mono<S> createSession();
|
||||
|
||||
/**
|
||||
* Ensures the {@link Session} created by
|
||||
* {@link ReactiveSessionRepository#createSession()} is saved.
|
||||
*
|
||||
* <p>
|
||||
* Some implementations may choose to save as the {@link Session} is updated by
|
||||
* returning a {@link Session} that immediately persists any changes. In this case,
|
||||
* this method may not actually do anything.
|
||||
* </p>
|
||||
*
|
||||
* @param session the {@link Session} to save
|
||||
* @return indicator of operation completion
|
||||
*/
|
||||
Mono<Void> save(S session);
|
||||
|
||||
/**
|
||||
* Gets the {@link Session} by the {@link Session#getId()} or null if no
|
||||
* {@link Session} is found.
|
||||
*
|
||||
* @param id the {@link Session#getId()} to lookup
|
||||
* @return the {@link Session} by the {@link Session#getId()} or null if no
|
||||
* {@link Session} is found.
|
||||
*/
|
||||
Mono<S> findById(String id);
|
||||
|
||||
/**
|
||||
* Deletes the {@link Session} with the given {@link Session#getId()} or does nothing
|
||||
* if the {@link Session} is not found.
|
||||
* @param id the {@link Session#getId()} to delete
|
||||
* @return indicator of operation completion
|
||||
*/
|
||||
Mono<Void> deleteById(String id);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides a way to identify a user in an agnostic way. This allows the session to be
|
||||
* used by an HttpSession, WebSocket Session, or even non web related sessions.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface Session {
|
||||
|
||||
/**
|
||||
* Gets a unique string that identifies the {@link Session}.
|
||||
*
|
||||
* @return a unique string that identifies the {@link Session}
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Changes the session id. After invoking the {@link #getId()} will return a new identifier.
|
||||
* @return the new session id which {@link #getId()} will now return
|
||||
*/
|
||||
String changeSessionId();
|
||||
|
||||
/**
|
||||
* Gets the Object associated with the specified name or null if no Object is
|
||||
* associated to that name.
|
||||
*
|
||||
* @param <T> the return type of the attribute
|
||||
* @param attributeName the name of the attribute to get
|
||||
* @return the Object associated with the specified name or null if no Object is
|
||||
* associated to that name
|
||||
*/
|
||||
<T> T getAttribute(String attributeName);
|
||||
|
||||
/**
|
||||
* Return the session attribute value or if not present raise an
|
||||
* {@link IllegalArgumentException}.
|
||||
* @param name the attribute name
|
||||
* @param <T> the attribute type
|
||||
* @return the attribute value
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T> T getRequiredAttribute(String name) {
|
||||
T result = getAttribute(name);
|
||||
if (result == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Required attribute '" + name + "' is missing.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the session attribute value, or a default, fallback value.
|
||||
* @param name the attribute name
|
||||
* @param defaultValue a default value to return instead
|
||||
* @param <T> the attribute type
|
||||
* @return the attribute value
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T> T getAttributeOrDefault(String name, T defaultValue) {
|
||||
T result = getAttribute(name);
|
||||
return (result != null) ? result : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the attribute names that have a value associated with it. Each value can be
|
||||
* passed into {@link org.springframework.session.Session#getAttribute(String)} to
|
||||
* obtain the attribute value.
|
||||
*
|
||||
* @return the attribute names that have a value associated with it.
|
||||
* @see #getAttribute(String)
|
||||
*/
|
||||
Set<String> getAttributeNames();
|
||||
|
||||
/**
|
||||
* Sets the attribute value for the provided attribute name. If the attributeValue is
|
||||
* null, it has the same result as removing the attribute with
|
||||
* {@link org.springframework.session.Session#removeAttribute(String)} .
|
||||
*
|
||||
* @param attributeName the attribute name to set
|
||||
* @param attributeValue the value of the attribute to set. If null, the attribute
|
||||
* will be removed.
|
||||
*/
|
||||
void setAttribute(String attributeName, Object attributeValue);
|
||||
|
||||
/**
|
||||
* Removes the attribute with the provided attribute name.
|
||||
* @param attributeName the name of the attribute to remove
|
||||
*/
|
||||
void removeAttribute(String attributeName);
|
||||
|
||||
/**
|
||||
* Gets the time when this session was created.
|
||||
*
|
||||
* @return the time when this session was created.
|
||||
*/
|
||||
Instant getCreationTime();
|
||||
|
||||
/**
|
||||
* Sets the last accessed time.
|
||||
*
|
||||
* @param lastAccessedTime the last accessed time
|
||||
*/
|
||||
void setLastAccessedTime(Instant lastAccessedTime);
|
||||
|
||||
/**
|
||||
* Gets the last time this {@link Session} was accessed.
|
||||
*
|
||||
* @return the last time the client sent a request associated with the session
|
||||
*/
|
||||
Instant getLastAccessedTime();
|
||||
|
||||
/**
|
||||
* Sets the maximum inactive interval between requests before this session will be
|
||||
* invalidated. A negative time indicates that the session will never timeout.
|
||||
*
|
||||
* @param interval the amount of time that the {@link Session} should be kept alive
|
||||
* between client requests.
|
||||
*/
|
||||
void setMaxInactiveInterval(Duration interval);
|
||||
|
||||
/**
|
||||
* Gets the maximum inactive interval between requests before this session will be
|
||||
* invalidated. A negative time indicates that the session will never timeout.
|
||||
*
|
||||
* @return the maximum inactive interval between requests before this session will be
|
||||
* invalidated. A negative time indicates that the session will never timeout.
|
||||
*/
|
||||
Duration getMaxInactiveInterval();
|
||||
|
||||
/**
|
||||
* Returns true if the session is expired.
|
||||
*
|
||||
* @return true if the session is expired, else false.
|
||||
*/
|
||||
boolean isExpired();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
/**
|
||||
* A repository interface for managing {@link Session} instances.
|
||||
*
|
||||
* @param <S> the {@link Session} type
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface SessionRepository<S extends Session> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link Session} that is capable of being persisted by this
|
||||
* {@link SessionRepository}.
|
||||
*
|
||||
* <p>
|
||||
* This allows optimizations and customizations in how the {@link Session} is
|
||||
* persisted. For example, the implementation returned might keep track of the changes
|
||||
* ensuring that only the delta needs to be persisted on a save.
|
||||
* </p>
|
||||
*
|
||||
* @return a new {@link Session} that is capable of being persisted by this
|
||||
* {@link SessionRepository}
|
||||
*/
|
||||
S createSession();
|
||||
|
||||
/**
|
||||
* Ensures the {@link Session} created by
|
||||
* {@link org.springframework.session.SessionRepository#createSession()} is saved.
|
||||
*
|
||||
* <p>
|
||||
* Some implementations may choose to save as the {@link Session} is updated by
|
||||
* returning a {@link Session} that immediately persists any changes. In this case,
|
||||
* this method may not actually do anything.
|
||||
* </p>
|
||||
*
|
||||
* @param session the {@link Session} to save
|
||||
*/
|
||||
void save(S session);
|
||||
|
||||
/**
|
||||
* Gets the {@link Session} by the {@link Session#getId()} or null if no
|
||||
* {@link Session} is found.
|
||||
*
|
||||
* @param id the {@link org.springframework.session.Session#getId()} to lookup
|
||||
* @return the {@link Session} by the {@link Session#getId()} or null if no
|
||||
* {@link Session} is found.
|
||||
*/
|
||||
S findById(String id);
|
||||
|
||||
/**
|
||||
* Deletes the {@link Session} with the given {@link Session#getId()} or does nothing
|
||||
* if the {@link Session} is not found.
|
||||
* @param id the {@link org.springframework.session.Session#getId()} to delete
|
||||
*/
|
||||
void deleteById(String id);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.config.annotation.web.http;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
|
||||
/**
|
||||
* Add this annotation to an {@code @Configuration} class to expose the
|
||||
* SessionRepositoryFilter as a bean named "springSessionRepositoryFilter" and backed by a
|
||||
* user provided implementation of {@link SessionRepository}. In order to leverage the
|
||||
* annotation, a single {@link SessionRepository} bean must be provided. For example:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {@literal @Configuration}
|
||||
* {@literal @EnableSpringHttpSession}
|
||||
* public class SpringHttpSessionConfig {
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public MapSessionRepository sessionRepository() {
|
||||
* return new MapSessionRepository();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </code> </pre>
|
||||
*
|
||||
* <p>
|
||||
* It is important to note that no infrastructure for session expirations is configured
|
||||
* for you out of the box. This is because things like session expiration are highly
|
||||
* implementation dependent. This means if you require cleaning up expired sessions, you
|
||||
* are responsible for cleaning up the expired sessions.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The following is provided for you with the base configuration:
|
||||
* </p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>SessionRepositoryFilter - is responsible for wrapping the HttpServletRequest with
|
||||
* an implementation of HttpSession that is backed by a SessionRepository</li>
|
||||
* <li>SessionEventHttpSessionListenerAdapter - is responsible for translating Spring
|
||||
* Session events into HttpSessionEvent. In order for it to work, the implementation of
|
||||
* SessionRepository you provide must support {@link SessionCreatedEvent} and
|
||||
* {@link SessionDestroyedEvent}.</li>
|
||||
* <li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
|
||||
@Target({ java.lang.annotation.ElementType.TYPE })
|
||||
@Documented
|
||||
@Import(SpringHttpSessionConfiguration.class)
|
||||
@Configuration
|
||||
public @interface EnableSpringHttpSession {
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.config.annotation.web.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.SessionCookieConfig;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
|
||||
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
|
||||
import org.springframework.session.web.http.CookieSerializer;
|
||||
import org.springframework.session.web.http.DefaultCookieSerializer;
|
||||
import org.springframework.session.web.http.HttpSessionIdResolver;
|
||||
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Configures the basics for setting up Spring Session in a web environment. In order to
|
||||
* use it, you must provide a {@link SessionRepository}. For example:
|
||||
*
|
||||
* <pre>
|
||||
* {@literal @Configuration}
|
||||
* {@literal @EnableSpringHttpSession}
|
||||
* public class SpringHttpSessionConfig {
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public MapSessionRepository sessionRepository() {
|
||||
* return new MapSessionRepository();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* It is important to note that no infrastructure for session expirations is configured
|
||||
* for you out of the box. This is because things like session expiration are highly
|
||||
* implementation dependent. This means if you require cleaning up expired sessions, you
|
||||
* are responsible for cleaning up the expired sessions.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The following is provided for you with the base configuration:
|
||||
* </p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>SessionRepositoryFilter - is responsible for wrapping the HttpServletRequest with
|
||||
* an implementation of HttpSession that is backed by a SessionRepository</li>
|
||||
* <li>SessionEventHttpSessionListenerAdapter - is responsible for translating Spring
|
||||
* Session events into HttpSessionEvent. In order for it to work, the implementation of
|
||||
* SessionRepository you provide must support {@link SessionCreatedEvent} and
|
||||
* {@link SessionDestroyedEvent}.</li>
|
||||
* <li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 1.1
|
||||
*
|
||||
* @see EnableSpringHttpSession
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class SpringHttpSessionConfiguration implements ApplicationContextAware {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private CookieHttpSessionIdResolver defaultHttpSessionIdResolver = new CookieHttpSessionIdResolver();
|
||||
|
||||
private boolean usesSpringSessionRememberMeServices;
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
private CookieSerializer cookieSerializer;
|
||||
|
||||
private HttpSessionIdResolver httpSessionIdResolver = this.defaultHttpSessionIdResolver;
|
||||
|
||||
private List<HttpSessionListener> httpSessionListeners = new ArrayList<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
CookieSerializer cookieSerializer = (this.cookieSerializer != null)
|
||||
? this.cookieSerializer
|
||||
: createDefaultCookieSerializer();
|
||||
this.defaultHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionEventHttpSessionListenerAdapter sessionEventHttpSessionListenerAdapter() {
|
||||
return new SessionEventHttpSessionListenerAdapter(this.httpSessionListeners);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
|
||||
SessionRepository<S> sessionRepository) {
|
||||
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(
|
||||
sessionRepository);
|
||||
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
|
||||
return sessionRepositoryFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
if (ClassUtils.isPresent(
|
||||
"org.springframework.security.web.authentication.RememberMeServices",
|
||||
null)) {
|
||||
this.usesSpringSessionRememberMeServices = !ObjectUtils
|
||||
.isEmpty(applicationContext
|
||||
.getBeanNamesForType(SpringSessionRememberMeServices.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setCookieSerializer(CookieSerializer cookieSerializer) {
|
||||
this.cookieSerializer = cookieSerializer;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
|
||||
this.httpSessionIdResolver = httpSessionIdResolver;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setHttpSessionListeners(List<HttpSessionListener> listeners) {
|
||||
this.httpSessionListeners = listeners;
|
||||
}
|
||||
|
||||
private CookieSerializer createDefaultCookieSerializer() {
|
||||
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
|
||||
if (this.servletContext != null) {
|
||||
SessionCookieConfig sessionCookieConfig = null;
|
||||
try {
|
||||
sessionCookieConfig = this.servletContext.getSessionCookieConfig();
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
this.logger
|
||||
.warn("Unable to obtain SessionCookieConfig: " + ex.getMessage());
|
||||
}
|
||||
if (sessionCookieConfig != null) {
|
||||
if (sessionCookieConfig.getName() != null) {
|
||||
cookieSerializer.setCookieName(sessionCookieConfig.getName());
|
||||
}
|
||||
if (sessionCookieConfig.getDomain() != null) {
|
||||
cookieSerializer.setDomainName(sessionCookieConfig.getDomain());
|
||||
}
|
||||
if (sessionCookieConfig.getPath() != null) {
|
||||
cookieSerializer.setCookiePath(sessionCookieConfig.getPath());
|
||||
}
|
||||
if (sessionCookieConfig.getMaxAge() != -1) {
|
||||
cookieSerializer.setCookieMaxAge(sessionCookieConfig.getMaxAge());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.usesSpringSessionRememberMeServices) {
|
||||
cookieSerializer.setRememberMeRequestAttribute(
|
||||
SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR);
|
||||
}
|
||||
return cookieSerializer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.session.config.annotation.web.server;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Add this annotation to a {@code @Configuration} class to configure a {@code WebSessionManager} for a WebFlux
|
||||
* application. This annotation assumes a {@code ReactiveSessionRepository} is defined somewhere in the application
|
||||
* context. If not, it will fail with a clear error message. For example:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {@literal @Configuration}
|
||||
* {@literal @EnableSpringWebSession}
|
||||
* public class SpringWebFluxConfig {
|
||||
*
|
||||
* {@literal @Bean}
|
||||
* public ReactiveSessionRepository sessionRepository() {
|
||||
* return new ReactiveMapSessionRepository();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 2.0
|
||||
*/
|
||||
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
|
||||
@Target({ java.lang.annotation.ElementType.TYPE })
|
||||
@Documented
|
||||
@Import(SpringWebSessionConfiguration.class)
|
||||
@Configuration
|
||||
public @interface EnableSpringWebSession {
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.session.config.annotation.web.server;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.ReactiveSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.web.server.session.SpringSessionWebSessionStore;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionIdResolver;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
/**
|
||||
* Wire up a {@link WebSessionManager} using a Reactive {@link ReactiveSessionRepository} from the application context.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Rob Winch
|
||||
* @since 2.0
|
||||
*
|
||||
* @see EnableSpringWebSession
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class SpringWebSessionConfiguration {
|
||||
|
||||
/**
|
||||
* Optional override of default {@link WebSessionIdResolver}.
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
private WebSessionIdResolver webSessionIdResolver;
|
||||
|
||||
/**
|
||||
* Configure a {@link WebSessionManager} using a provided {@link ReactiveSessionRepository}.
|
||||
*
|
||||
* @param repository a bean that implements {@link ReactiveSessionRepository}.
|
||||
* @return a configured {@link WebSessionManager} registered with a preconfigured name.
|
||||
*/
|
||||
@Bean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME)
|
||||
public WebSessionManager webSessionManager(ReactiveSessionRepository<? extends Session> repository) {
|
||||
SpringSessionWebSessionStore<? extends Session> sessionStore = new SpringSessionWebSessionStore<>(repository);
|
||||
DefaultWebSessionManager manager = new DefaultWebSessionManager();
|
||||
manager.setSessionStore(sessionStore);
|
||||
|
||||
if (this.webSessionIdResolver != null) {
|
||||
manager.setSessionIdResolver(this.webSessionIdResolver);
|
||||
}
|
||||
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.events;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
/**
|
||||
* For {@link SessionRepository} implementations that support it, this event is fired when
|
||||
* a {@link Session} is updated.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class AbstractSessionEvent extends ApplicationEvent {
|
||||
|
||||
private final String sessionId;
|
||||
|
||||
private final Session session;
|
||||
|
||||
AbstractSessionEvent(Object source, Session session) {
|
||||
super(source);
|
||||
this.session = session;
|
||||
this.sessionId = session.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Session} that was destroyed. For some {@link SessionRepository}
|
||||
* implementations it may not be possible to get the original session in which case
|
||||
* this may be null.
|
||||
*
|
||||
* @param <S> the type of Session
|
||||
* @return the expired {@link Session} or null if the data store does not support
|
||||
* obtaining it
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S extends Session> S getSession() {
|
||||
return (S) this.session;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return this.sessionId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.events;
|
||||
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
/**
|
||||
* For {@link SessionRepository} implementations that support it, this event is fired when
|
||||
* a {@link Session} is created.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SessionCreatedEvent extends AbstractSessionEvent {
|
||||
|
||||
/**
|
||||
* Create a new {@link SessionCreatedEvent}.
|
||||
* @param source the source of the event
|
||||
* @param session the session that was created
|
||||
*/
|
||||
public SessionCreatedEvent(Object source, Session session) {
|
||||
super(source, session);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.events;
|
||||
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
/**
|
||||
* For {@link SessionRepository} implementations that support it, this event is fired when
|
||||
* a {@link Session} is destroyed via deletion.
|
||||
*
|
||||
* @author Mark Anderson
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SessionDeletedEvent extends SessionDestroyedEvent {
|
||||
|
||||
/**
|
||||
* Create a new {@link SessionDeletedEvent}.
|
||||
* @param source the source of the event
|
||||
* @param session the session that was created
|
||||
*/
|
||||
public SessionDeletedEvent(Object source, Session session) {
|
||||
super(source, session);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.events;
|
||||
|
||||
import org.springframework.session.Session;
|
||||
|
||||
/**
|
||||
* Base class for events fired when a {@link Session} is destroyed explicitly.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SessionDestroyedEvent extends AbstractSessionEvent {
|
||||
|
||||
/**
|
||||
* Create a new {@link SessionDestroyedEvent}.
|
||||
* @param source the source of the event
|
||||
* @param session the session that was created
|
||||
*/
|
||||
public SessionDestroyedEvent(Object source, Session session) {
|
||||
super(source, session);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.events;
|
||||
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
/**
|
||||
* For {@link SessionRepository} implementations that support it, this event is fired when
|
||||
* a {@link Session} is destroyed via expiration.
|
||||
*
|
||||
* @author Mark Anderson
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SessionExpiredEvent extends SessionDestroyedEvent {
|
||||
|
||||
/**
|
||||
* Create a new {@link SessionExpiredEvent}.
|
||||
* @param source the source of the event
|
||||
* @param session the session that was created
|
||||
*/
|
||||
public SessionExpiredEvent(Object source, Session session) {
|
||||
super(source, session);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.security;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
/**
|
||||
* Ensures that calling {@link #expireNow()} propagates to Spring Session, since this
|
||||
* session information contains only derived data and is not the authoritative source.
|
||||
*
|
||||
* @param <S> the {@link Session} type.
|
||||
* @author Joris Kuipers
|
||||
* @author Vedran Pavic
|
||||
* @since 1.3
|
||||
*/
|
||||
class SpringSessionBackedSessionInformation<S extends Session>
|
||||
extends SessionInformation {
|
||||
|
||||
static final String EXPIRED_ATTR = SpringSessionBackedSessionInformation.class
|
||||
.getName() + ".EXPIRED";
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(SpringSessionBackedSessionInformation.class);
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private final SessionRepository<S> sessionRepository;
|
||||
|
||||
SpringSessionBackedSessionInformation(S session,
|
||||
SessionRepository<S> sessionRepository) {
|
||||
super(resolvePrincipal(session), session.getId(),
|
||||
Date.from(session.getLastAccessedTime()));
|
||||
this.sessionRepository = sessionRepository;
|
||||
Boolean expired = session.getAttribute(EXPIRED_ATTR);
|
||||
if (Boolean.TRUE.equals(expired)) {
|
||||
super.expireNow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to determine the principal's name from the given Session.
|
||||
*
|
||||
* @param session the session
|
||||
* @return the principal's name, or empty String if it couldn't be determined
|
||||
*/
|
||||
private static String resolvePrincipal(Session session) {
|
||||
String principalName = session
|
||||
.getAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
|
||||
if (principalName != null) {
|
||||
return principalName;
|
||||
}
|
||||
SecurityContext securityContext = session
|
||||
.getAttribute(SPRING_SECURITY_CONTEXT);
|
||||
if (securityContext != null
|
||||
&& securityContext.getAuthentication() != null) {
|
||||
return securityContext.getAuthentication().getName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireNow() {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Expiring session " + getSessionId() + " for user '"
|
||||
+ getPrincipal() + "', presumably because maximum allowed concurrent "
|
||||
+ "sessions was exceeded");
|
||||
}
|
||||
super.expireNow();
|
||||
S session = this.sessionRepository.findById(getSessionId());
|
||||
if (session != null) {
|
||||
session.setAttribute(EXPIRED_ATTR, Boolean.TRUE);
|
||||
this.sessionRepository.save(session);
|
||||
}
|
||||
else {
|
||||
logger.info("Could not find Session with id " + getSessionId()
|
||||
+ " to mark as expired");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.security;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link SessionRegistry} that retrieves session information from Spring Session,
|
||||
* rather than maintaining it itself. This allows concurrent session management with
|
||||
* Spring Security in a clustered environment.
|
||||
* <p>
|
||||
* Relies on being able to derive the same String-based representation of the principal
|
||||
* given to {@link #getAllSessions(Object, boolean)} as used by Spring Session in order to
|
||||
* look up the user's sessions.
|
||||
* <p>
|
||||
* Does not support {@link #getAllPrincipals()}, since that information is not available.
|
||||
*
|
||||
* @param <S> the {@link Session} type.
|
||||
* @author Joris Kuipers
|
||||
* @author Vedran Pavic
|
||||
* @since 1.3
|
||||
*/
|
||||
public class SpringSessionBackedSessionRegistry<S extends Session>
|
||||
implements SessionRegistry {
|
||||
|
||||
private final FindByIndexNameSessionRepository<S> sessionRepository;
|
||||
|
||||
public SpringSessionBackedSessionRegistry(
|
||||
FindByIndexNameSessionRepository<S> sessionRepository) {
|
||||
Assert.notNull(sessionRepository, "sessionRepository cannot be null");
|
||||
this.sessionRepository = sessionRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> getAllPrincipals() {
|
||||
throw new UnsupportedOperationException("SpringSessionBackedSessionRegistry does "
|
||||
+ "not support retrieving all principals, since Spring Session provides "
|
||||
+ "no way to obtain that information");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SessionInformation> getAllSessions(Object principal,
|
||||
boolean includeExpiredSessions) {
|
||||
Collection<S> sessions = this.sessionRepository
|
||||
.findByPrincipalName(name(principal)).values();
|
||||
List<SessionInformation> infos = new ArrayList<>();
|
||||
for (S session : sessions) {
|
||||
if (includeExpiredSessions || !Boolean.TRUE.equals(session
|
||||
.getAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR))) {
|
||||
infos.add(new SpringSessionBackedSessionInformation<>(session,
|
||||
this.sessionRepository));
|
||||
}
|
||||
}
|
||||
return infos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionInformation getSessionInformation(String sessionId) {
|
||||
S session = this.sessionRepository.findById(sessionId);
|
||||
if (session != null) {
|
||||
return new SpringSessionBackedSessionInformation<>(session,
|
||||
this.sessionRepository);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a no-op, as we don't administer sessions ourselves.
|
||||
*/
|
||||
@Override
|
||||
public void refreshLastRequest(String sessionId) {
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a no-op, as we don't administer sessions ourselves.
|
||||
*/
|
||||
@Override
|
||||
public void registerNewSession(String sessionId, Object principal) {
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a no-op, as we don't administer sessions ourselves.
|
||||
*/
|
||||
@Override
|
||||
public void removeSessionInformation(String sessionId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a String name for the given principal.
|
||||
*
|
||||
* @param principal as provided by Spring Security
|
||||
* @return name of the principal, or its {@code toString()} representation if no name
|
||||
* could be derived
|
||||
*/
|
||||
protected String name(Object principal) {
|
||||
if (principal instanceof UserDetails) {
|
||||
return ((UserDetails) principal).getUsername();
|
||||
}
|
||||
if (principal instanceof Principal) {
|
||||
return ((Principal) principal).getName();
|
||||
}
|
||||
return principal.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.security.web.authentication;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.RememberMeServices;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link RememberMeServices} implementation that uses Spring Session backed
|
||||
* {@link HttpSession} to provide remember-me service capabilities.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class SpringSessionRememberMeServices
|
||||
implements RememberMeServices, LogoutHandler {
|
||||
|
||||
/**
|
||||
* Remember-me login request attribute name.
|
||||
*/
|
||||
public static final String REMEMBER_ME_LOGIN_ATTR = SpringSessionRememberMeServices.class
|
||||
.getName() + "REMEMBER_ME_LOGIN_ATTR";
|
||||
|
||||
private static final String DEFAULT_REMEMBERME_PARAMETER = "remember-me";
|
||||
|
||||
private static final int THIRTY_DAYS_SECONDS = 2592000;
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(SpringSessionRememberMeServices.class);
|
||||
|
||||
private String rememberMeParameterName = DEFAULT_REMEMBERME_PARAMETER;
|
||||
|
||||
private boolean alwaysRemember;
|
||||
|
||||
private int validitySeconds = THIRTY_DAYS_SECONDS;
|
||||
|
||||
private String sessionAttrToDeleteOnLoginFail = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
|
||||
|
||||
@Override
|
||||
public final Authentication autoLogin(HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void loginFail(HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
logout(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void loginSuccess(HttpServletRequest request,
|
||||
HttpServletResponse response, Authentication successfulAuthentication) {
|
||||
if (!this.alwaysRemember
|
||||
&& !rememberMeRequested(request, this.rememberMeParameterName)) {
|
||||
logger.debug("Remember-me login not requested.");
|
||||
return;
|
||||
}
|
||||
request.setAttribute(REMEMBER_ME_LOGIN_ATTR, true);
|
||||
request.getSession().setMaxInactiveInterval(this.validitySeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows customization of whether a remember-me login has been requested. The default
|
||||
* is to return {@code true} if the configured parameter name has been included in the
|
||||
* request and is set to the value {@code true}.
|
||||
* @param request the request submitted from an interactive login, which may include
|
||||
* additional information indicating that a persistent login is desired.
|
||||
* @param parameter the configured remember-me parameter name.
|
||||
* @return true if the request includes information indicating that a persistent login
|
||||
* has been requested.
|
||||
*/
|
||||
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
|
||||
String rememberMe = request.getParameter(parameter);
|
||||
if (rememberMe != null) {
|
||||
if (rememberMe.equalsIgnoreCase("true") || rememberMe.equalsIgnoreCase("on")
|
||||
|| rememberMe.equalsIgnoreCase("yes") || rememberMe.equals("1")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Did not send remember-me cookie (principal did not set "
|
||||
+ "parameter '" + parameter + "')");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the parameter which should be checked for to see if a remember-me
|
||||
* has been requested during a login request. This should be the same name you assign
|
||||
* to the checkbox in your login form.
|
||||
* @param rememberMeParameterName the request parameter
|
||||
*/
|
||||
public void setRememberMeParameterName(String rememberMeParameterName) {
|
||||
Assert.hasText(rememberMeParameterName,
|
||||
"rememberMeParameterName cannot be empty or null");
|
||||
this.rememberMeParameterName = rememberMeParameterName;
|
||||
}
|
||||
|
||||
public void setAlwaysRemember(boolean alwaysRemember) {
|
||||
this.alwaysRemember = alwaysRemember;
|
||||
}
|
||||
|
||||
public void setValiditySeconds(int validitySeconds) {
|
||||
this.validitySeconds = validitySeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) {
|
||||
logout(request);
|
||||
}
|
||||
|
||||
private void logout(HttpServletRequest request) {
|
||||
logger.debug("Interactive login attempt was unsuccessful.");
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
session.removeAttribute(this.sessionAttrToDeleteOnLoginFail);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.context;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterRegistration.Dynamic;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.WebApplicationInitializer;
|
||||
import org.springframework.web.context.AbstractContextLoaderInitializer;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
|
||||
/**
|
||||
* Registers the {@link DelegatingFilterProxy} to use the springSessionRepositoryFilter
|
||||
* before any other registered {@link Filter}. When used with
|
||||
* {@link #AbstractHttpSessionApplicationInitializer(Class...)}, it will also register a
|
||||
* {@link ContextLoaderListener}. When used with
|
||||
* {@link #AbstractHttpSessionApplicationInitializer()}, this class is typically used in
|
||||
* addition to a subclass of {@link AbstractContextLoaderInitializer}.
|
||||
*
|
||||
* <p>
|
||||
* By default the {@link DelegatingFilterProxy} is registered with support for
|
||||
* asynchronous requests, but can be enabled by overriding
|
||||
* {@link #isAsyncSessionSupported()} and {@link #getSessionDispatcherTypes()}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Additional configuration before and after the springSecurityFilterChain can be added by
|
||||
* overriding {@link #afterSessionRepositoryFilter(ServletContext)}.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <h2>Caveats</h2>
|
||||
* <p>
|
||||
* Subclasses of {@code AbstractDispatcherServletInitializer} will register their filters
|
||||
* before any other {@link Filter}. This means that you will typically want to ensure
|
||||
* subclasses of {@code AbstractDispatcherServletInitializer} are invoked first. This can
|
||||
* be done by ensuring the {@link Order} or {@link Ordered} of
|
||||
* {@code AbstractDispatcherServletInitializer} are sooner than subclasses of
|
||||
* {@code AbstractSecurityWebApplicationInitializer}.
|
||||
* </p>
|
||||
*
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@Order(100)
|
||||
public abstract class AbstractHttpSessionApplicationInitializer
|
||||
implements WebApplicationInitializer {
|
||||
|
||||
private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT.";
|
||||
|
||||
/**
|
||||
* The default name for Spring Session's repository filter.
|
||||
*/
|
||||
public static final String DEFAULT_FILTER_NAME = "springSessionRepositoryFilter";
|
||||
|
||||
private final Class<?>[] configurationClasses;
|
||||
|
||||
/**
|
||||
* Creates a new instance that assumes the Spring Session configuration is loaded by
|
||||
* some other means than this class. For example, a user might create a
|
||||
* {@link ContextLoaderListener} using a subclass of
|
||||
* {@link AbstractContextLoaderInitializer}.
|
||||
*
|
||||
* @see ContextLoaderListener
|
||||
*/
|
||||
protected AbstractHttpSessionApplicationInitializer() {
|
||||
this.configurationClasses = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance that will instantiate the {@link ContextLoaderListener} with
|
||||
* the specified classes.
|
||||
*
|
||||
* @param configurationClasses {@code @Configuration} classes that will be used to
|
||||
* configure the context
|
||||
*/
|
||||
protected AbstractHttpSessionApplicationInitializer(
|
||||
Class<?>... configurationClasses) {
|
||||
this.configurationClasses = configurationClasses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
beforeSessionRepositoryFilter(servletContext);
|
||||
if (this.configurationClasses != null) {
|
||||
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
|
||||
rootAppContext.register(this.configurationClasses);
|
||||
servletContext.addListener(new ContextLoaderListener(rootAppContext));
|
||||
}
|
||||
insertSessionRepositoryFilter(servletContext);
|
||||
afterSessionRepositoryFilter(servletContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the springSessionRepositoryFilter.
|
||||
* @param servletContext the {@link ServletContext}
|
||||
*/
|
||||
private void insertSessionRepositoryFilter(ServletContext servletContext) {
|
||||
String filterName = DEFAULT_FILTER_NAME;
|
||||
DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(
|
||||
filterName);
|
||||
String contextAttribute = getWebApplicationContextAttribute();
|
||||
if (contextAttribute != null) {
|
||||
springSessionRepositoryFilter.setContextAttribute(contextAttribute);
|
||||
}
|
||||
registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the provided {@link Filter}s before existing {@link Filter}s using default
|
||||
* generated names, {@link #getSessionDispatcherTypes()}, and
|
||||
* {@link #isAsyncSessionSupported()}.
|
||||
*
|
||||
* @param servletContext the {@link ServletContext} to use
|
||||
* @param filters the {@link Filter}s to register
|
||||
*/
|
||||
protected final void insertFilters(ServletContext servletContext, Filter... filters) {
|
||||
registerFilters(servletContext, true, filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the provided {@link Filter}s after existing {@link Filter}s using default
|
||||
* generated names, {@link #getSessionDispatcherTypes()}, and
|
||||
* {@link #isAsyncSessionSupported()}.
|
||||
*
|
||||
* @param servletContext the {@link ServletContext} to use
|
||||
* @param filters the {@link Filter}s to register
|
||||
*/
|
||||
protected final void appendFilters(ServletContext servletContext, Filter... filters) {
|
||||
registerFilters(servletContext, false, filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the provided {@link Filter}s using default generated names,
|
||||
* {@link #getSessionDispatcherTypes()}, and {@link #isAsyncSessionSupported()}.
|
||||
*
|
||||
* @param servletContext the {@link ServletContext} to use
|
||||
* @param insertBeforeOtherFilters if true, will insert the provided {@link Filter}s
|
||||
* before other {@link Filter}s. Otherwise, will insert the {@link Filter}s after
|
||||
* other {@link Filter}s.
|
||||
* @param filters the {@link Filter}s to register
|
||||
*/
|
||||
private void registerFilters(ServletContext servletContext,
|
||||
boolean insertBeforeOtherFilters, Filter... filters) {
|
||||
Assert.notEmpty(filters, "filters cannot be null or empty");
|
||||
|
||||
for (Filter filter : filters) {
|
||||
if (filter == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"filters cannot contain null values. Got "
|
||||
+ Arrays.asList(filters));
|
||||
}
|
||||
String filterName = Conventions.getVariableName(filter);
|
||||
registerFilter(servletContext, insertBeforeOtherFilters, filterName, filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the provided filter using the {@link #isAsyncSessionSupported()} and
|
||||
* {@link #getSessionDispatcherTypes()}.
|
||||
*
|
||||
* @param servletContext the servlet context
|
||||
* @param insertBeforeOtherFilters should this Filter be inserted before or after
|
||||
* other {@link Filter}
|
||||
* @param filterName the filter name
|
||||
* @param filter the filter
|
||||
*/
|
||||
private void registerFilter(ServletContext servletContext,
|
||||
boolean insertBeforeOtherFilters, String filterName, Filter filter) {
|
||||
Dynamic registration = servletContext.addFilter(filterName, filter);
|
||||
if (registration == null) {
|
||||
throw new IllegalStateException(
|
||||
"Duplicate Filter registration for '" + filterName
|
||||
+ "'. Check to ensure the Filter is only configured once.");
|
||||
}
|
||||
registration.setAsyncSupported(isAsyncSessionSupported());
|
||||
EnumSet<DispatcherType> dispatcherTypes = getSessionDispatcherTypes();
|
||||
registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
|
||||
"/*");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link DelegatingFilterProxy#getContextAttribute()} or null if the
|
||||
* parent {@link ApplicationContext} should be used. The default behavior is to use
|
||||
* the parent {@link ApplicationContext}.
|
||||
*
|
||||
* <p>
|
||||
* If {@link #getDispatcherWebApplicationContextSuffix()} is non-null the
|
||||
* {@link WebApplicationContext} for the Dispatcher will be used. This means the child
|
||||
* {@link ApplicationContext} is used to look up the springSessionRepositoryFilter
|
||||
* bean.
|
||||
* </p>
|
||||
*
|
||||
* @return the {@link DelegatingFilterProxy#getContextAttribute()} or null if the
|
||||
* parent {@link ApplicationContext} should be used
|
||||
*/
|
||||
private String getWebApplicationContextAttribute() {
|
||||
String dispatcherServletName = getDispatcherWebApplicationContextSuffix();
|
||||
if (dispatcherServletName == null) {
|
||||
return null;
|
||||
}
|
||||
return SERVLET_CONTEXT_PREFIX + dispatcherServletName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code <servlet-name>} to use the DispatcherServlet's
|
||||
* {@link WebApplicationContext} to find the {@link DelegatingFilterProxy} or null to
|
||||
* use the parent {@link ApplicationContext}.
|
||||
*
|
||||
* <p>
|
||||
* For example, if you are using AbstractDispatcherServletInitializer or
|
||||
* AbstractAnnotationConfigDispatcherServletInitializer and using the provided Servlet
|
||||
* name, you can return "dispatcher" from this method to use the DispatcherServlet's
|
||||
* {@link WebApplicationContext}.
|
||||
* </p>
|
||||
*
|
||||
* @return the {@code <servlet-name>} of the DispatcherServlet to use its
|
||||
* {@link WebApplicationContext} or null (default) to use the parent
|
||||
* {@link ApplicationContext}.
|
||||
*/
|
||||
protected String getDispatcherWebApplicationContextSuffix() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before the springSessionRepositoryFilter is added.
|
||||
* @param servletContext the {@link ServletContext}
|
||||
*/
|
||||
protected void beforeSessionRepositoryFilter(ServletContext servletContext) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after the springSessionRepositoryFilter is added.
|
||||
* @param servletContext the {@link ServletContext}
|
||||
*/
|
||||
protected void afterSessionRepositoryFilter(ServletContext servletContext) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link DispatcherType} for the springSessionRepositoryFilter.
|
||||
* @return the {@link DispatcherType} for the filter
|
||||
*/
|
||||
protected EnumSet<DispatcherType> getSessionDispatcherTypes() {
|
||||
return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR,
|
||||
DispatcherType.ASYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the springSessionRepositoryFilter should be marked as supporting
|
||||
* asynch. Default is true.
|
||||
*
|
||||
* @return true if springSessionRepositoryFilter should be marked as supporting asynch
|
||||
*/
|
||||
protected boolean isAsyncSessionSupported() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.session.web.http.CookieSerializer.CookieValue;
|
||||
|
||||
/**
|
||||
* A {@link HttpSessionIdResolver} that uses a cookie to obtain the session from.
|
||||
* Specifically, this implementation will allow specifying a cookie serialization strategy
|
||||
* using {@link CookieHttpSessionIdResolver#setCookieSerializer(CookieSerializer)}. The
|
||||
* default is cookie name is "SESSION".
|
||||
*
|
||||
* When a session is created, the HTTP response will have a cookie with the specified
|
||||
* cookie name and the value of the session id. The cookie will be marked as a session
|
||||
* cookie, use the context path for the path of the cookie, marked as HTTPOnly, and if
|
||||
* {@link javax.servlet.http.HttpServletRequest#isSecure()} returns true, the cookie will
|
||||
* be marked as secure. For example:
|
||||
*
|
||||
* <pre>
|
||||
* HTTP/1.1 200 OK
|
||||
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly
|
||||
* </pre>
|
||||
*
|
||||
* The client should now include the session in each request by specifying the same cookie
|
||||
* in their request. For example:
|
||||
*
|
||||
* <pre>
|
||||
* GET /messages/ HTTP/1.1
|
||||
* Host: example.com
|
||||
* Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6
|
||||
* </pre>
|
||||
*
|
||||
* When the session is invalidated, the server will send an HTTP response that expires the
|
||||
* cookie. For example:
|
||||
*
|
||||
* <pre>
|
||||
* HTTP/1.1 200 OK
|
||||
* Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
|
||||
* </pre>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 1.0
|
||||
*/
|
||||
public final class CookieHttpSessionIdResolver implements HttpSessionIdResolver {
|
||||
|
||||
private static final String WRITTEN_SESSION_ID_ATTR = CookieHttpSessionIdResolver.class
|
||||
.getName().concat(".WRITTEN_SESSION_ID_ATTR");
|
||||
|
||||
private CookieSerializer cookieSerializer = new DefaultCookieSerializer();
|
||||
|
||||
@Override
|
||||
public List<String> resolveSessionIds(HttpServletRequest request) {
|
||||
return this.cookieSerializer.readCookieValues(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionId(HttpServletRequest request, HttpServletResponse response,
|
||||
String sessionId) {
|
||||
if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {
|
||||
return;
|
||||
}
|
||||
request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);
|
||||
this.cookieSerializer
|
||||
.writeCookieValue(new CookieValue(request, response, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireSession(HttpServletRequest request, HttpServletResponse response) {
|
||||
this.cookieSerializer.writeCookieValue(new CookieValue(request, response, ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link CookieSerializer} to be used.
|
||||
*
|
||||
* @param cookieSerializer the cookieSerializer to set. Cannot be null.
|
||||
*/
|
||||
public void setCookieSerializer(CookieSerializer cookieSerializer) {
|
||||
if (cookieSerializer == null) {
|
||||
throw new IllegalArgumentException("cookieSerializer cannot be null");
|
||||
}
|
||||
this.cookieSerializer = cookieSerializer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Strategy for reading and writing a cookie value to the {@link HttpServletResponse}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface CookieSerializer {
|
||||
|
||||
/**
|
||||
* Writes a given {@link CookieValue} to the provided {@link HttpServletResponse}.
|
||||
*
|
||||
* @param cookieValue the {@link CookieValue} to write to
|
||||
* {@link CookieValue#getResponse()}. Cannot be null.
|
||||
*/
|
||||
void writeCookieValue(CookieValue cookieValue);
|
||||
|
||||
/**
|
||||
* Reads all the matching cookies from the {@link HttpServletRequest}. The result is a
|
||||
* List since there can be multiple {@link Cookie} in a single request with a matching
|
||||
* name. For example, one Cookie may have a path of / and another of /context, but the
|
||||
* path is not transmitted in the request.
|
||||
*
|
||||
* @param request the {@link HttpServletRequest} to read the cookie from. Cannot be
|
||||
* null.
|
||||
* @return the values of all the matching cookies
|
||||
*/
|
||||
List<String> readCookieValues(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* Contains the information necessary to write a value to the
|
||||
* {@link HttpServletResponse}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 1.1
|
||||
*/
|
||||
class CookieValue {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
private final HttpServletResponse response;
|
||||
|
||||
private final String cookieValue;
|
||||
|
||||
private int cookieMaxAge = -1;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param request the {@link HttpServletRequest} to use. Useful for determining
|
||||
* the context in which the cookie is set. Cannot be null.
|
||||
* @param response the {@link HttpServletResponse} to use.
|
||||
* @param cookieValue the value of the cookie to be written. This value may be
|
||||
* modified by the {@link CookieSerializer} when writing to the actual cookie so
|
||||
* long as the original value is returned when the cookie is read.
|
||||
*/
|
||||
public CookieValue(HttpServletRequest request, HttpServletResponse response,
|
||||
String cookieValue) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.cookieValue = cookieValue;
|
||||
if ("".equals(this.cookieValue)) {
|
||||
this.cookieMaxAge = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request to use.
|
||||
* @return the request to use. Cannot be null.
|
||||
*/
|
||||
public HttpServletRequest getRequest() {
|
||||
return this.request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the response to write to.
|
||||
* @return the response to write to. Cannot be null.
|
||||
*/
|
||||
public HttpServletResponse getResponse() {
|
||||
return this.response;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value to be written. This value may be modified by the
|
||||
* {@link CookieSerializer} before written to the cookie. However, the value must
|
||||
* be the same as the original when it is read back in.
|
||||
*
|
||||
* @return the value to be written
|
||||
*/
|
||||
public String getCookieValue() {
|
||||
return this.cookieValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie max age. The default is -1 which signals to delete the cookie
|
||||
* when the browser is closed, or 0 if cookie value is empty.
|
||||
* @return the cookie max age
|
||||
*/
|
||||
public int getCookieMaxAge() {
|
||||
return this.cookieMaxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cookie max age.
|
||||
* @param cookieMaxAge the cookie max age
|
||||
*/
|
||||
public void setCookieMaxAge(int cookieMaxAge) {
|
||||
this.cookieMaxAge = cookieMaxAge;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* The default implementation of {@link CookieSerializer}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @author Eddú Meléndez
|
||||
* @since 1.1
|
||||
*/
|
||||
public class DefaultCookieSerializer implements CookieSerializer {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DefaultCookieSerializer.class);
|
||||
|
||||
private static final BitSet domainValid = new BitSet(128);
|
||||
|
||||
static {
|
||||
for (char c = '0'; c <= '9'; c++) {
|
||||
domainValid.set(c);
|
||||
}
|
||||
for (char c = 'a'; c <= 'z'; c++) {
|
||||
domainValid.set(c);
|
||||
}
|
||||
for (char c = 'A'; c <= 'Z'; c++) {
|
||||
domainValid.set(c);
|
||||
}
|
||||
domainValid.set('.');
|
||||
domainValid.set('-');
|
||||
}
|
||||
|
||||
private String cookieName = "SESSION";
|
||||
|
||||
private Boolean useSecureCookie;
|
||||
|
||||
private boolean useHttpOnlyCookie = true;
|
||||
|
||||
private String cookiePath;
|
||||
|
||||
private Integer cookieMaxAge;
|
||||
|
||||
private String domainName;
|
||||
|
||||
private Pattern domainNamePattern;
|
||||
|
||||
private String jvmRoute;
|
||||
|
||||
private boolean useBase64Encoding = true;
|
||||
|
||||
private String rememberMeRequestAttribute;
|
||||
|
||||
private String sameSite = "Lax";
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.session.web.http.CookieSerializer#readCookieValues(javax.
|
||||
* servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
public List<String> readCookieValues(HttpServletRequest request) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
List<String> matchingCookieValues = new ArrayList<>();
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (this.cookieName.equals(cookie.getName())) {
|
||||
String sessionId = (this.useBase64Encoding
|
||||
? base64Decode(cookie.getValue())
|
||||
: cookie.getValue());
|
||||
if (sessionId == null) {
|
||||
continue;
|
||||
}
|
||||
if (this.jvmRoute != null && sessionId.endsWith(this.jvmRoute)) {
|
||||
sessionId = sessionId.substring(0,
|
||||
sessionId.length() - this.jvmRoute.length());
|
||||
}
|
||||
matchingCookieValues.add(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchingCookieValues;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.session.web.http.CookieWriter#writeCookieValue(org.
|
||||
* springframework.session.web.http.CookieWriter.CookieValue)
|
||||
*/
|
||||
@Override
|
||||
public void writeCookieValue(CookieValue cookieValue) {
|
||||
HttpServletRequest request = cookieValue.getRequest();
|
||||
HttpServletResponse response = cookieValue.getResponse();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(this.cookieName).append('=');
|
||||
String value = getValue(cookieValue);
|
||||
if (value != null && value.length() > 0) {
|
||||
validateValue(value);
|
||||
sb.append(value);
|
||||
}
|
||||
int maxAge = getMaxAge(cookieValue);
|
||||
if (maxAge > -1) {
|
||||
sb.append("; Max-Age=").append(cookieValue.getCookieMaxAge());
|
||||
OffsetDateTime expires = (maxAge != 0)
|
||||
? OffsetDateTime.now().plusSeconds(maxAge)
|
||||
: Instant.EPOCH.atOffset(ZoneOffset.UTC);
|
||||
sb.append("; Expires=")
|
||||
.append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME));
|
||||
}
|
||||
String domain = getDomainName(request);
|
||||
if (domain != null && domain.length() > 0) {
|
||||
validateDomain(domain);
|
||||
sb.append("; Domain=").append(domain);
|
||||
}
|
||||
String path = getCookiePath(request);
|
||||
if (path != null && path.length() > 0) {
|
||||
validatePath(path);
|
||||
sb.append("; Path=").append(path);
|
||||
}
|
||||
if (isSecureCookie(request)) {
|
||||
sb.append("; Secure");
|
||||
}
|
||||
if (this.useHttpOnlyCookie) {
|
||||
sb.append("; HttpOnly");
|
||||
}
|
||||
if (this.sameSite != null) {
|
||||
sb.append("; SameSite=").append(this.sameSite);
|
||||
}
|
||||
|
||||
response.addHeader("Set-Cookie", sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the value using Base64.
|
||||
* @param base64Value the Base64 String to decode
|
||||
* @return the Base64 decoded value
|
||||
* @since 1.2.2
|
||||
*/
|
||||
private String base64Decode(String base64Value) {
|
||||
try {
|
||||
byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);
|
||||
return new String(decodedCookieBytes);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.debug("Unable to Base64 decode value: " + base64Value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the value using Base64.
|
||||
* @param value the String to Base64 encode
|
||||
* @return the Base64 encoded value
|
||||
* @since 1.2.2
|
||||
*/
|
||||
private String base64Encode(String value) {
|
||||
byte[] encodedCookieBytes = Base64.getEncoder().encode(value.getBytes());
|
||||
return new String(encodedCookieBytes);
|
||||
}
|
||||
|
||||
private String getValue(CookieValue cookieValue) {
|
||||
String requestedCookieValue = cookieValue.getCookieValue();
|
||||
String actualCookieValue = requestedCookieValue;
|
||||
if (this.jvmRoute != null) {
|
||||
actualCookieValue = requestedCookieValue + this.jvmRoute;
|
||||
}
|
||||
if (this.useBase64Encoding) {
|
||||
actualCookieValue = base64Encode(actualCookieValue);
|
||||
}
|
||||
return actualCookieValue;
|
||||
}
|
||||
|
||||
private void validateValue(String value) {
|
||||
int start = 0;
|
||||
int end = value.length();
|
||||
if ((end > 1) && (value.charAt(0) == '"') && (value.charAt(end - 1) == '"')) {
|
||||
start = 1;
|
||||
end--;
|
||||
}
|
||||
char[] chars = value.toCharArray();
|
||||
for (int i = start; i < end; i++) {
|
||||
char c = chars[i];
|
||||
if (c < 0x21 || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c
|
||||
|| c == 0x7f) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid character in cookie value: " + Integer.toString(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getMaxAge(CookieValue cookieValue) {
|
||||
int maxAge = cookieValue.getCookieMaxAge();
|
||||
if (maxAge < 0) {
|
||||
if (this.rememberMeRequestAttribute != null && cookieValue.getRequest()
|
||||
.getAttribute(this.rememberMeRequestAttribute) != null) {
|
||||
// the cookie is only written at time of session creation, so we rely on
|
||||
// session expiration rather than cookie expiration if remember me is
|
||||
// enabled
|
||||
cookieValue.setCookieMaxAge(Integer.MAX_VALUE);
|
||||
}
|
||||
else if (this.cookieMaxAge != null) {
|
||||
cookieValue.setCookieMaxAge(this.cookieMaxAge);
|
||||
}
|
||||
}
|
||||
return cookieValue.getCookieMaxAge();
|
||||
}
|
||||
|
||||
private void validateDomain(String domain) {
|
||||
int i = 0;
|
||||
int cur = -1;
|
||||
int prev;
|
||||
char[] chars = domain.toCharArray();
|
||||
while (i < chars.length) {
|
||||
prev = cur;
|
||||
cur = chars[i];
|
||||
if (!domainValid.get(cur)
|
||||
|| ((prev == '.' || prev == -1) && (cur == '.' || cur == '-'))
|
||||
|| (prev == '-' && cur == '.')) {
|
||||
throw new IllegalArgumentException("Invalid cookie domain: " + domain);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (cur == '.' || cur == '-') {
|
||||
throw new IllegalArgumentException("Invalid cookie domain: " + domain);
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePath(String path) {
|
||||
for (char ch : path.toCharArray()) {
|
||||
if (ch < 0x20 || ch > 0x7E || ch == ';') {
|
||||
throw new IllegalArgumentException("Invalid cookie path: " + path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if a Cookie marked as secure should be used. The default is to use the value
|
||||
* of {@link HttpServletRequest#isSecure()}.
|
||||
*
|
||||
* @param useSecureCookie determines if the cookie should be marked as secure.
|
||||
*/
|
||||
public void setUseSecureCookie(boolean useSecureCookie) {
|
||||
this.useSecureCookie = useSecureCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if a Cookie marked as HTTP Only should be used. The default is true.
|
||||
*
|
||||
* @param useHttpOnlyCookie determines if the cookie should be marked as HTTP Only.
|
||||
*/
|
||||
public void setUseHttpOnlyCookie(boolean useHttpOnlyCookie) {
|
||||
this.useHttpOnlyCookie = useHttpOnlyCookie;
|
||||
}
|
||||
|
||||
private boolean isSecureCookie(HttpServletRequest request) {
|
||||
if (this.useSecureCookie == null) {
|
||||
return request.isSecure();
|
||||
}
|
||||
return this.useSecureCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path of the Cookie. The default is to use the context path from the
|
||||
* {@link HttpServletRequest}.
|
||||
*
|
||||
* @param cookiePath the path of the Cookie. If null, the default of the context path
|
||||
* will be used.
|
||||
*/
|
||||
public void setCookiePath(String cookiePath) {
|
||||
this.cookiePath = cookiePath;
|
||||
}
|
||||
|
||||
public void setCookieName(String cookieName) {
|
||||
if (cookieName == null) {
|
||||
throw new IllegalArgumentException("cookieName cannot be null");
|
||||
}
|
||||
this.cookieName = cookieName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maxAge property of the Cookie. The default is to delete the cookie when
|
||||
* the browser is closed.
|
||||
*
|
||||
* @param cookieMaxAge the maxAge property of the Cookie
|
||||
*/
|
||||
public void setCookieMaxAge(int cookieMaxAge) {
|
||||
this.cookieMaxAge = cookieMaxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an explicit Domain Name. This allow the domain of "example.com" to be used
|
||||
* when the request comes from www.example.com. This allows for sharing the cookie
|
||||
* across subdomains. The default is to use the current domain.
|
||||
*
|
||||
* @param domainName the name of the domain to use. (i.e. "example.com")
|
||||
* @throws IllegalStateException if the domainNamePattern is also set
|
||||
*/
|
||||
public void setDomainName(String domainName) {
|
||||
if (this.domainNamePattern != null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot set both domainName and domainNamePattern");
|
||||
}
|
||||
this.domainName = domainName;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets a case insensitive pattern used to extract the domain name from the
|
||||
* {@link HttpServletRequest#getServerName()}. The pattern should provide a single
|
||||
* grouping that defines what the value is that should be matched. User's should be
|
||||
* careful not to output malicious characters like new lines to prevent from things
|
||||
* like <a href= "https://www.owasp.org/index.php/HTTP_Response_Splitting">HTTP
|
||||
* Response Splitting</a>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If the pattern does not match, then no domain will be set. This is useful to ensure
|
||||
* the domain is not set during development when localhost might be used.
|
||||
* </p>
|
||||
* <p>
|
||||
* An example value might be "^.+?\\.(\\w+\\.[a-z]+)$". For the given input, it would
|
||||
* provide the following explicit domain (null means no domain name is set):
|
||||
* </p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>example.com - null</li>
|
||||
* <li>child.sub.example.com - example.com</li>
|
||||
* <li>localhost - null</li>
|
||||
* <li>127.0.1.1 - null</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param domainNamePattern the case insensitive pattern to extract the domain name
|
||||
* with
|
||||
* @throws IllegalStateException if the domainName is also set
|
||||
*/
|
||||
public void setDomainNamePattern(String domainNamePattern) {
|
||||
if (this.domainName != null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot set both domainName and domainNamePattern");
|
||||
}
|
||||
this.domainNamePattern = Pattern.compile(domainNamePattern,
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Used to identify which JVM to route to for session affinity. With some
|
||||
* implementations (i.e. Redis) this provides no performance benefit. However, this
|
||||
* can help with tracing logs of a particular user. This will ensure that the value of
|
||||
* the cookie is formatted as
|
||||
* </p>
|
||||
* <code>
|
||||
* sessionId + "." jvmRoute
|
||||
* </code>
|
||||
* <p>
|
||||
* To use set a custom route on each JVM instance and setup a frontend proxy to
|
||||
* forward all requests to the JVM based on the route.
|
||||
* </p>
|
||||
*
|
||||
* @param jvmRoute the JVM Route to use (i.e. "node01jvmA", "n01ja", etc)
|
||||
*/
|
||||
public void setJvmRoute(String jvmRoute) {
|
||||
this.jvmRoute = "." + jvmRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the Base64 encoding of cookie value should be used. This is valuable in
|
||||
* order to support <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> which
|
||||
* recommends using Base 64 encoding to the cookie value.
|
||||
*
|
||||
* @param useBase64Encoding the flag to indicate whether to use Base64 encoding
|
||||
*/
|
||||
public void setUseBase64Encoding(boolean useBase64Encoding) {
|
||||
this.useBase64Encoding = useBase64Encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the request attribute name that indicates remember-me login. If specified, the
|
||||
* cookie will be written as Integer.MAX_VALUE.
|
||||
* @param rememberMeRequestAttribute the remember-me request attribute name
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public void setRememberMeRequestAttribute(String rememberMeRequestAttribute) {
|
||||
if (rememberMeRequestAttribute == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"rememberMeRequestAttribute cannot be null");
|
||||
}
|
||||
this.rememberMeRequestAttribute = rememberMeRequestAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for the {@code SameSite} cookie directive. The default value is
|
||||
* {@code Lax}.
|
||||
* @param sameSite the SameSite directive value
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public void setSameSite(String sameSite) {
|
||||
this.sameSite = sameSite;
|
||||
}
|
||||
|
||||
private String getDomainName(HttpServletRequest request) {
|
||||
if (this.domainName != null) {
|
||||
return this.domainName;
|
||||
}
|
||||
if (this.domainNamePattern != null) {
|
||||
Matcher matcher = this.domainNamePattern.matcher(request.getServerName());
|
||||
if (matcher.matches()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getCookiePath(HttpServletRequest request) {
|
||||
if (this.cookiePath == null) {
|
||||
return request.getContextPath() + "/";
|
||||
}
|
||||
return this.cookiePath;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* A {@link HttpSessionIdResolver} that uses a header to resolve the session id.
|
||||
* Specifically, this implementation will allow specifying a header name using
|
||||
* {@link #HeaderHttpSessionIdResolver(String)}. Convenience factory methods for creating
|
||||
* instances that use common header names, such as "X-Auth-Token" and
|
||||
* "Authentication-Info", are available as well.
|
||||
* <p>
|
||||
* When a session is created, the HTTP response will have a response header of the
|
||||
* specified name and the value of the session id. For example:
|
||||
*
|
||||
* <pre>
|
||||
* HTTP/1.1 200 OK
|
||||
* X-Auth-Token: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
|
||||
* </pre>
|
||||
*
|
||||
* The client should now include the session in each request by specifying the same header
|
||||
* in their request. For example:
|
||||
*
|
||||
* <pre>
|
||||
* GET /messages/ HTTP/1.1
|
||||
* Host: example.com
|
||||
* X-Auth-Token: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
|
||||
* </pre>
|
||||
*
|
||||
* When the session is invalidated, the server will send an HTTP response that has the
|
||||
* header name and a blank value. For example:
|
||||
*
|
||||
* <pre>
|
||||
* HTTP/1.1 200 OK
|
||||
* X-Auth-Token:
|
||||
* </pre>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 1.0
|
||||
*/
|
||||
public class HeaderHttpSessionIdResolver implements HttpSessionIdResolver {
|
||||
|
||||
private static final String HEADER_X_AUTH_TOKEN = "X-Auth-Token";
|
||||
|
||||
private static final String HEADER_AUTHENTICATION_INFO = "Authentication-Info";
|
||||
|
||||
private final String headerName;
|
||||
|
||||
/**
|
||||
* Convenience factory to create {@link HeaderHttpSessionIdResolver} that uses
|
||||
* "X-Auth-Token" header.
|
||||
* @return the instance configured to use "X-Auth-Token" header
|
||||
*/
|
||||
public static HeaderHttpSessionIdResolver xAuthToken() {
|
||||
return new HeaderHttpSessionIdResolver(HEADER_X_AUTH_TOKEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience factory to create {@link HeaderHttpSessionIdResolver} that uses
|
||||
* "Authentication-Info" header.
|
||||
* @return the instance configured to use "Authentication-Info" header
|
||||
*/
|
||||
public static HeaderHttpSessionIdResolver authenticationInfo() {
|
||||
return new HeaderHttpSessionIdResolver(HEADER_AUTHENTICATION_INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the header to obtain the session id from.
|
||||
* @param headerName the name of the header to obtain the session id from.
|
||||
*/
|
||||
public HeaderHttpSessionIdResolver(String headerName) {
|
||||
if (headerName == null) {
|
||||
throw new IllegalArgumentException("headerName cannot be null");
|
||||
}
|
||||
this.headerName = headerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> resolveSessionIds(HttpServletRequest request) {
|
||||
String headerValue = request.getHeader(this.headerName);
|
||||
return (headerValue != null) ? Collections.singletonList(headerValue)
|
||||
: Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionId(HttpServletRequest request, HttpServletResponse response,
|
||||
String sessionId) {
|
||||
response.setHeader(this.headerName, sessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireSession(HttpServletRequest request, HttpServletResponse response) {
|
||||
response.setHeader(this.headerName, "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionBindingEvent;
|
||||
import javax.servlet.http.HttpSessionBindingListener;
|
||||
import javax.servlet.http.HttpSessionContext;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.session.Session;
|
||||
|
||||
/**
|
||||
* Adapts Spring Session's {@link Session} to an {@link HttpSession}.
|
||||
*
|
||||
* @param <S> the {@link Session} type
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 1.1
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
class HttpSessionAdapter<S extends Session> implements HttpSession {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(HttpSessionAdapter.class);
|
||||
|
||||
private S session;
|
||||
|
||||
private final ServletContext servletContext;
|
||||
|
||||
private boolean invalidated;
|
||||
|
||||
private boolean old;
|
||||
|
||||
HttpSessionAdapter(S session, ServletContext servletContext) {
|
||||
if (session == null) {
|
||||
throw new IllegalArgumentException("session cannot be null");
|
||||
}
|
||||
if (servletContext == null) {
|
||||
throw new IllegalArgumentException("servletContext cannot be null");
|
||||
}
|
||||
this.session = session;
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
public void setSession(S session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public S getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationTime() {
|
||||
checkState();
|
||||
return this.session.getCreationTime().toEpochMilli();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.session.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastAccessedTime() {
|
||||
checkState();
|
||||
return this.session.getLastAccessedTime().toEpochMilli();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletContext getServletContext() {
|
||||
return this.servletContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(int interval) {
|
||||
this.session.setMaxInactiveInterval(Duration.ofSeconds(interval));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxInactiveInterval() {
|
||||
return (int) this.session.getMaxInactiveInterval().getSeconds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpSessionContext getSessionContext() {
|
||||
return NOOP_SESSION_CONTEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
checkState();
|
||||
return this.session.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(String name) {
|
||||
return getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames() {
|
||||
checkState();
|
||||
return Collections.enumeration(this.session.getAttributeNames());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValueNames() {
|
||||
checkState();
|
||||
Set<String> attrs = this.session.getAttributeNames();
|
||||
return attrs.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object value) {
|
||||
checkState();
|
||||
Object oldValue = this.session.getAttribute(name);
|
||||
this.session.setAttribute(name, value);
|
||||
if (value != oldValue) {
|
||||
if (oldValue instanceof HttpSessionBindingListener) {
|
||||
try {
|
||||
((HttpSessionBindingListener) oldValue).valueUnbound(
|
||||
new HttpSessionBindingEvent(this, name, oldValue));
|
||||
}
|
||||
catch (Throwable th) {
|
||||
logger.error("Error invoking session binding event listener", th);
|
||||
}
|
||||
}
|
||||
if (value instanceof HttpSessionBindingListener) {
|
||||
try {
|
||||
((HttpSessionBindingListener) value)
|
||||
.valueBound(new HttpSessionBindingEvent(this, name, value));
|
||||
}
|
||||
catch (Throwable th) {
|
||||
logger.error("Error invoking session binding event listener", th);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putValue(String name, Object value) {
|
||||
setAttribute(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
checkState();
|
||||
Object oldValue = this.session.getAttribute(name);
|
||||
this.session.removeAttribute(name);
|
||||
if (oldValue instanceof HttpSessionBindingListener) {
|
||||
try {
|
||||
((HttpSessionBindingListener) oldValue)
|
||||
.valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
|
||||
}
|
||||
catch (Throwable th) {
|
||||
logger.error("Error invoking session binding event listener", th);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValue(String name) {
|
||||
removeAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
checkState();
|
||||
this.invalidated = true;
|
||||
}
|
||||
|
||||
public void setNew(boolean isNew) {
|
||||
this.old = !isNew;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
checkState();
|
||||
return !this.old;
|
||||
}
|
||||
|
||||
private void checkState() {
|
||||
if (this.invalidated) {
|
||||
throw new IllegalStateException(
|
||||
"The HttpSession has already be invalidated.");
|
||||
}
|
||||
}
|
||||
|
||||
private static final HttpSessionContext NOOP_SESSION_CONTEXT = new HttpSessionContext() {
|
||||
|
||||
@Override
|
||||
public HttpSession getSession(String sessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getIds() {
|
||||
return EMPTY_ENUMERATION;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private static final Enumeration<String> EMPTY_ENUMERATION = new Enumeration<String>() {
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nextElement() {
|
||||
throw new NoSuchElementException("a");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Contract for session id resolution strategies. Allows for session id resolution through
|
||||
* the request and for sending the session id or expiring the session through the
|
||||
* response.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface HttpSessionIdResolver {
|
||||
|
||||
/**
|
||||
* Resolve the session ids associated with the provided {@link HttpServletRequest}.
|
||||
* For example, the session id might come from a cookie or a request header.
|
||||
* @param request the current request
|
||||
* @return the session ids
|
||||
*/
|
||||
List<String> resolveSessionIds(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* Send the given session id to the client. This method is invoked when a new session
|
||||
* is created and should inform a client what the new session id is. For example, it
|
||||
* might create a new cookie with the session id in it or set an HTTP response header
|
||||
* with the value of the new session id.
|
||||
* @param request the current request
|
||||
* @param response the current response
|
||||
* @param sessionId the session id
|
||||
*/
|
||||
void setSessionId(HttpServletRequest request, HttpServletResponse response,
|
||||
String sessionId);
|
||||
|
||||
/**
|
||||
* Instruct the client to end the current session. This method is invoked when a
|
||||
* session is invalidated and should inform a client that the session id is no longer
|
||||
* valid. For example, it might remove a cookie with the session id in it or set an
|
||||
* HTTP response header with an empty value indicating to the client to no longer
|
||||
* submit that session id.
|
||||
* @param request the current request
|
||||
* @param response the current response
|
||||
*/
|
||||
void expireSession(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,638 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Base class for response wrappers which encapsulate the logic for handling an event when
|
||||
* the {@link javax.servlet.http.HttpServletResponse} is committed.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private boolean disableOnCommitted;
|
||||
|
||||
/**
|
||||
* The Content-Length response header. If this is greater than 0, then once
|
||||
* {@link #contentWritten} is larger than or equal the response is considered
|
||||
* committed.
|
||||
*/
|
||||
private long contentLength;
|
||||
|
||||
/**
|
||||
* The size of data written to the response body.
|
||||
*/
|
||||
private long contentWritten;
|
||||
|
||||
/**
|
||||
* Create a new {@link OnCommittedResponseWrapper}.
|
||||
* @param response the response to be wrapped
|
||||
*/
|
||||
OnCommittedResponseWrapper(HttpServletResponse response) {
|
||||
super(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value) {
|
||||
if ("Content-Length".equalsIgnoreCase(name)) {
|
||||
setContentLength(Long.parseLong(value));
|
||||
}
|
||||
super.addHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentLength(int len) {
|
||||
setContentLength((long) len);
|
||||
super.setContentLength(len);
|
||||
}
|
||||
|
||||
private void setContentLength(long len) {
|
||||
this.contentLength = len;
|
||||
checkContentLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke this method to disable invoking
|
||||
* {@link OnCommittedResponseWrapper#onResponseCommitted()} when the
|
||||
* {@link javax.servlet.http.HttpServletResponse} is committed. This can be useful in
|
||||
* the event that Async Web Requests are made.
|
||||
*/
|
||||
public void disableOnResponseCommitted() {
|
||||
this.disableOnCommitted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse}
|
||||
* being committed.
|
||||
*/
|
||||
protected abstract void onResponseCommitted();
|
||||
|
||||
/**
|
||||
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
|
||||
* before calling the superclass <code>sendError()</code>.
|
||||
* @param sc the error status code
|
||||
*/
|
||||
@Override
|
||||
public final void sendError(int sc) throws IOException {
|
||||
doOnResponseCommitted();
|
||||
super.sendError(sc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
|
||||
* before calling the superclass <code>sendError()</code>.
|
||||
* @param sc the error status code
|
||||
*/
|
||||
@Override
|
||||
public final void sendError(int sc, String msg) throws IOException {
|
||||
doOnResponseCommitted();
|
||||
super.sendError(sc, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
|
||||
* before calling the superclass <code>sendRedirect()</code>.
|
||||
* @param location the redirect URL location
|
||||
*/
|
||||
@Override
|
||||
public final void sendRedirect(String location) throws IOException {
|
||||
doOnResponseCommitted();
|
||||
super.sendRedirect(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
|
||||
* before calling the calling <code>getOutputStream().close()</code> or
|
||||
* <code>getOutputStream().flush()</code>.
|
||||
* @throws IOException if an input or output exception occurred
|
||||
*/
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() throws IOException {
|
||||
return new SaveContextServletOutputStream(super.getOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
|
||||
* before calling the <code>getWriter().close()</code> or
|
||||
* <code>getWriter().flush()</code>.
|
||||
* @throws IOException if an input or output exception occurred
|
||||
*/
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException {
|
||||
return new SaveContextPrintWriter(super.getWriter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
|
||||
* before calling the superclass <code>flushBuffer()</code>.
|
||||
* @throws IOException if an input or output exception occurred
|
||||
*/
|
||||
@Override
|
||||
public void flushBuffer() throws IOException {
|
||||
doOnResponseCommitted();
|
||||
super.flushBuffer();
|
||||
}
|
||||
|
||||
private void trackContentLength(boolean content) {
|
||||
checkContentLength(content ? 4 : 5); // TODO Localization
|
||||
}
|
||||
|
||||
private void trackContentLength(char content) {
|
||||
checkContentLength(1);
|
||||
}
|
||||
|
||||
private void trackContentLength(Object content) {
|
||||
trackContentLength(String.valueOf(content));
|
||||
}
|
||||
|
||||
private void trackContentLength(byte[] content) {
|
||||
checkContentLength((content != null) ? content.length : 0);
|
||||
}
|
||||
|
||||
private void trackContentLength(char[] content) {
|
||||
checkContentLength((content != null) ? content.length : 0);
|
||||
}
|
||||
|
||||
private void trackContentLength(int content) {
|
||||
trackContentLength(String.valueOf(content));
|
||||
}
|
||||
|
||||
private void trackContentLength(float content) {
|
||||
trackContentLength(String.valueOf(content));
|
||||
}
|
||||
|
||||
private void trackContentLength(double content) {
|
||||
trackContentLength(String.valueOf(content));
|
||||
}
|
||||
|
||||
private void trackContentLengthLn() {
|
||||
trackContentLength("\r\n");
|
||||
}
|
||||
|
||||
private void trackContentLength(String content) {
|
||||
checkContentLength(content.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the contentLengthToWrite to the total contentWritten size and checks to see if
|
||||
* the response should be written.
|
||||
*
|
||||
* @param contentLengthToWrite the size of the content that is about to be written.
|
||||
*/
|
||||
private void checkContentLength(long contentLengthToWrite) {
|
||||
this.contentWritten += contentLengthToWrite;
|
||||
boolean isBodyFullyWritten = this.contentLength > 0
|
||||
&& this.contentWritten >= this.contentLength;
|
||||
int bufferSize = getBufferSize();
|
||||
boolean requiresFlush = bufferSize > 0 && this.contentWritten >= bufferSize;
|
||||
if (isBodyFullyWritten || requiresFlush) {
|
||||
doOnResponseCommitted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>onResponseCommmitted()</code> with the current contents as long as
|
||||
* {@link #disableOnResponseCommitted()} was not invoked.
|
||||
*/
|
||||
private void doOnResponseCommitted() {
|
||||
if (!this.disableOnCommitted) {
|
||||
onResponseCommitted();
|
||||
disableOnResponseCommitted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before
|
||||
* calling the prior to methods that commit the response. We delegate all methods to
|
||||
* the original {@link java.io.PrintWriter} to ensure that the behavior is as close to
|
||||
* the original {@link java.io.PrintWriter} as possible. See SEC-2039
|
||||
* @author Rob Winch
|
||||
*/
|
||||
private class SaveContextPrintWriter extends PrintWriter {
|
||||
private final PrintWriter delegate;
|
||||
|
||||
SaveContextPrintWriter(PrintWriter delegate) {
|
||||
super(delegate);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
doOnResponseCommitted();
|
||||
this.delegate.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
doOnResponseCommitted();
|
||||
this.delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this.delegate.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "[delegate=" + this.delegate.toString() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkError() {
|
||||
return this.delegate.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int c) {
|
||||
trackContentLength(c);
|
||||
this.delegate.write(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char[] buf, int off, int len) {
|
||||
checkContentLength(len);
|
||||
this.delegate.write(buf, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char[] buf) {
|
||||
trackContentLength(buf);
|
||||
this.delegate.write(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String s, int off, int len) {
|
||||
checkContentLength(len);
|
||||
this.delegate.write(s, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String s) {
|
||||
trackContentLength(s);
|
||||
this.delegate.write(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(boolean b) {
|
||||
trackContentLength(b);
|
||||
this.delegate.print(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(char c) {
|
||||
trackContentLength(c);
|
||||
this.delegate.print(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(int i) {
|
||||
trackContentLength(i);
|
||||
this.delegate.print(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(long l) {
|
||||
trackContentLength(l);
|
||||
this.delegate.print(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(float f) {
|
||||
trackContentLength(f);
|
||||
this.delegate.print(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(double d) {
|
||||
trackContentLength(d);
|
||||
this.delegate.print(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(char[] s) {
|
||||
trackContentLength(s);
|
||||
this.delegate.print(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String s) {
|
||||
trackContentLength(s);
|
||||
this.delegate.print(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(Object obj) {
|
||||
trackContentLength(obj);
|
||||
this.delegate.print(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println() {
|
||||
trackContentLengthLn();
|
||||
this.delegate.println();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(boolean x) {
|
||||
trackContentLength(x);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(char x) {
|
||||
trackContentLength(x);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(int x) {
|
||||
trackContentLength(x);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(long x) {
|
||||
trackContentLength(x);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(float x) {
|
||||
trackContentLength(x);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(double x) {
|
||||
trackContentLength(x);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(char[] x) {
|
||||
trackContentLength(x);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(String x) {
|
||||
trackContentLength(x);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(Object x) {
|
||||
trackContentLength(x);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter printf(String format, Object... args) {
|
||||
return this.delegate.printf(format, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter printf(Locale l, String format, Object... args) {
|
||||
return this.delegate.printf(l, format, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter format(String format, Object... args) {
|
||||
return this.delegate.format(format, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter format(Locale l, String format, Object... args) {
|
||||
return this.delegate.format(l, format, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter append(CharSequence csq) {
|
||||
checkContentLength(csq.length());
|
||||
return this.delegate.append(csq);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter append(CharSequence csq, int start, int end) {
|
||||
checkContentLength(end - start);
|
||||
return this.delegate.append(csq, start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter append(char c) {
|
||||
trackContentLength(c);
|
||||
return this.delegate.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures{@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before
|
||||
* calling methods that commit the response. We delegate all methods to the original
|
||||
* {@link javax.servlet.ServletOutputStream} to ensure that the behavior is as close
|
||||
* to the original {@link javax.servlet.ServletOutputStream} as possible. See SEC-2039
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
private class SaveContextServletOutputStream extends ServletOutputStream {
|
||||
private final ServletOutputStream delegate;
|
||||
|
||||
SaveContextServletOutputStream(ServletOutputStream delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
trackContentLength(b);
|
||||
this.delegate.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
doOnResponseCommitted();
|
||||
this.delegate.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
doOnResponseCommitted();
|
||||
this.delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this.delegate.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(boolean b) throws IOException {
|
||||
trackContentLength(b);
|
||||
this.delegate.print(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(char c) throws IOException {
|
||||
trackContentLength(c);
|
||||
this.delegate.print(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(double d) throws IOException {
|
||||
trackContentLength(d);
|
||||
this.delegate.print(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(float f) throws IOException {
|
||||
trackContentLength(f);
|
||||
this.delegate.print(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(int i) throws IOException {
|
||||
trackContentLength(i);
|
||||
this.delegate.print(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(long l) throws IOException {
|
||||
trackContentLength(l);
|
||||
this.delegate.print(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String s) throws IOException {
|
||||
trackContentLength(s);
|
||||
this.delegate.print(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println() throws IOException {
|
||||
trackContentLengthLn();
|
||||
this.delegate.println();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(boolean b) throws IOException {
|
||||
trackContentLength(b);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(char c) throws IOException {
|
||||
trackContentLength(c);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(double d) throws IOException {
|
||||
trackContentLength(d);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(float f) throws IOException {
|
||||
trackContentLength(f);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(int i) throws IOException {
|
||||
trackContentLength(i);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(long l) throws IOException {
|
||||
trackContentLength(l);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(String s) throws IOException {
|
||||
trackContentLength(s);
|
||||
trackContentLengthLn();
|
||||
this.delegate.println(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
trackContentLength(b);
|
||||
this.delegate.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
checkContentLength(len);
|
||||
this.delegate.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "[delegate=" + this.delegate.toString() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return this.delegate.isReady();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteListener(WriteListener writeListener) {
|
||||
this.delegate.setWriteListener(writeListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Allows for easily ensuring that a request is only invoked once per request. This is a
|
||||
* simplified version of spring-web's OncePerRequestFilter and copied to reduce the foot
|
||||
* print required to use the session support.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
abstract class OncePerRequestFilter implements Filter {
|
||||
/**
|
||||
* Suffix that gets appended to the filter name for the "already filtered" request
|
||||
* attribute.
|
||||
*/
|
||||
public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
|
||||
|
||||
private String alreadyFilteredAttributeName = getClass().getName()
|
||||
.concat(ALREADY_FILTERED_SUFFIX);
|
||||
|
||||
/**
|
||||
* This {@code doFilter} implementation stores a request attribute for
|
||||
* "already filtered", proceeding without filtering again if the attribute is already
|
||||
* there.
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param filterChain the filter chain
|
||||
* @throws ServletException if request is not HTTP request
|
||||
* @throws IOException in case of I/O operation exception
|
||||
*/
|
||||
@Override
|
||||
public final void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
if (!(request instanceof HttpServletRequest)
|
||||
|| !(response instanceof HttpServletResponse)) {
|
||||
throw new ServletException(
|
||||
"OncePerRequestFilter just supports HTTP requests");
|
||||
}
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
String alreadyFilteredAttributeName = this.alreadyFilteredAttributeName;
|
||||
alreadyFilteredAttributeName = updateForErrorDispatch(
|
||||
alreadyFilteredAttributeName, request);
|
||||
boolean hasAlreadyFilteredAttribute = request
|
||||
.getAttribute(alreadyFilteredAttributeName) != null;
|
||||
|
||||
if (hasAlreadyFilteredAttribute) {
|
||||
|
||||
// Proceed without invoking this filter...
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
else {
|
||||
// Do invoke this filter...
|
||||
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
|
||||
try {
|
||||
doFilterInternal(httpRequest, httpResponse, filterChain);
|
||||
}
|
||||
finally {
|
||||
// Remove the "already filtered" request attribute for this request.
|
||||
request.removeAttribute(alreadyFilteredAttributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String updateForErrorDispatch(String alreadyFilteredAttributeName,
|
||||
ServletRequest request) {
|
||||
// Jetty does ERROR dispatch within sendError, so request attribute is still present
|
||||
// Use a separate attribute for ERROR dispatches
|
||||
if (DispatcherType.ERROR.equals(request.getDispatcherType())
|
||||
&& request.getAttribute(alreadyFilteredAttributeName) != null) {
|
||||
return alreadyFilteredAttributeName + ".ERROR";
|
||||
}
|
||||
return alreadyFilteredAttributeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same contract as for {@code doFilter}, but guaranteed to be just invoked once per
|
||||
* request within a single request thread.
|
||||
* <p>
|
||||
* Provides HttpServletRequest and HttpServletResponse arguments instead of the
|
||||
* default ServletRequest and ServletResponse ones.
|
||||
*
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param filterChain the FilterChain
|
||||
* @throws ServletException thrown when a non-I/O exception has occurred
|
||||
* @throws IOException thrown when an I/O exception of some sort has occurred
|
||||
* @see Filter#doFilter
|
||||
*/
|
||||
protected abstract void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionEvent;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
import org.springframework.web.context.ServletContextAware;
|
||||
|
||||
/**
|
||||
* Receives {@link SessionDestroyedEvent} and {@link SessionCreatedEvent} and translates
|
||||
* them into {@link HttpSessionEvent} and submits the {@link HttpSessionEvent} to every
|
||||
* registered {@link HttpSessionListener}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
public class SessionEventHttpSessionListenerAdapter
|
||||
implements ApplicationListener<AbstractSessionEvent>, ServletContextAware {
|
||||
private final List<HttpSessionListener> listeners;
|
||||
|
||||
private ServletContext context;
|
||||
|
||||
public SessionEventHttpSessionListenerAdapter(List<HttpSessionListener> listeners) {
|
||||
super();
|
||||
this.listeners = listeners;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.
|
||||
* springframework.context.ApplicationEvent)
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
if (this.listeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HttpSessionEvent httpSessionEvent = createHttpSessionEvent(event);
|
||||
|
||||
for (HttpSessionListener listener : this.listeners) {
|
||||
if (event instanceof SessionDestroyedEvent) {
|
||||
listener.sessionDestroyed(httpSessionEvent);
|
||||
}
|
||||
else if (event instanceof SessionCreatedEvent) {
|
||||
listener.sessionCreated(httpSessionEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HttpSessionEvent createHttpSessionEvent(AbstractSessionEvent event) {
|
||||
Session session = event.getSession();
|
||||
HttpSession httpSession = new HttpSessionAdapter<>(session,
|
||||
this.context);
|
||||
return new HttpSessionEvent(httpSession);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet
|
||||
* .ServletContext)
|
||||
*/
|
||||
@Override
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
this.context = servletContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
/**
|
||||
* Switches the {@link javax.servlet.http.HttpSession} implementation to be backed by a
|
||||
* {@link org.springframework.session.Session}.
|
||||
*
|
||||
* The {@link SessionRepositoryFilter} wraps the
|
||||
* {@link javax.servlet.http.HttpServletRequest} and overrides the methods to get an
|
||||
* {@link javax.servlet.http.HttpSession} to be backed by a
|
||||
* {@link org.springframework.session.Session} returned by the
|
||||
* {@link org.springframework.session.SessionRepository}.
|
||||
*
|
||||
* The {@link SessionRepositoryFilter} uses a {@link HttpSessionIdResolver} (default
|
||||
* {@link CookieHttpSessionIdResolver}) to bridge logic between an
|
||||
* {@link javax.servlet.http.HttpSession} and the
|
||||
* {@link org.springframework.session.Session} abstraction. Specifically:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The session id is looked up using
|
||||
* {@link HttpSessionIdResolver#resolveSessionIds(javax.servlet.http.HttpServletRequest)}
|
||||
* . The default is to look in a cookie named SESSION.</li>
|
||||
* <li>The session id of newly created {@link org.springframework.session.Session} is sent
|
||||
* to the client using
|
||||
* <li>The client is notified that the session id is no longer valid with
|
||||
* {@link HttpSessionIdResolver#expireSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The SessionRepositoryFilter must be placed before any Filter that access the
|
||||
* HttpSession or that might commit the response to ensure the session is overridden and
|
||||
* persisted properly.
|
||||
* </p>
|
||||
*
|
||||
* @param <S> the {@link Session} type.
|
||||
* @since 1.0
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
|
||||
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
|
||||
|
||||
private static final String SESSION_LOGGER_NAME = SessionRepositoryFilter.class
|
||||
.getName().concat(".SESSION_LOGGER");
|
||||
|
||||
private static final Log SESSION_LOGGER = LogFactory.getLog(SESSION_LOGGER_NAME);
|
||||
|
||||
/**
|
||||
* The session repository request attribute name.
|
||||
*/
|
||||
public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class
|
||||
.getName();
|
||||
|
||||
/**
|
||||
* Invalid session id (not backed by the session repository) request attribute name.
|
||||
*/
|
||||
public static final String INVALID_SESSION_ID_ATTR = SESSION_REPOSITORY_ATTR
|
||||
+ ".invalidSessionId";
|
||||
|
||||
private static final String CURRENT_SESSION_ATTR = SESSION_REPOSITORY_ATTR
|
||||
+ ".CURRENT_SESSION";
|
||||
|
||||
/**
|
||||
* The default filter order.
|
||||
*/
|
||||
public static final int DEFAULT_ORDER = Integer.MIN_VALUE + 50;
|
||||
|
||||
private final SessionRepository<S> sessionRepository;
|
||||
|
||||
private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param sessionRepository the <code>SessionRepository</code> to use. Cannot be null.
|
||||
*/
|
||||
public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
|
||||
if (sessionRepository == null) {
|
||||
throw new IllegalArgumentException("sessionRepository cannot be null");
|
||||
}
|
||||
this.sessionRepository = sessionRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link HttpSessionIdResolver} to be used. The default is a
|
||||
* {@link CookieHttpSessionIdResolver}.
|
||||
*
|
||||
* @param httpSessionIdResolver the {@link HttpSessionIdResolver} to use. Cannot be
|
||||
* null.
|
||||
*/
|
||||
public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
|
||||
if (httpSessionIdResolver == null) {
|
||||
throw new IllegalArgumentException("httpSessionIdResolver cannot be null");
|
||||
}
|
||||
this.httpSessionIdResolver = httpSessionIdResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
|
||||
|
||||
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
|
||||
request, response);
|
||||
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
|
||||
wrappedRequest, response);
|
||||
|
||||
try {
|
||||
filterChain.doFilter(wrappedRequest, wrappedResponse);
|
||||
}
|
||||
finally {
|
||||
wrappedRequest.commitSession();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows ensuring that the session is saved if the response is committed.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
private final class SessionRepositoryResponseWrapper
|
||||
extends OnCommittedResponseWrapper {
|
||||
|
||||
private final SessionRepositoryRequestWrapper request;
|
||||
|
||||
/**
|
||||
* Create a new {@link SessionRepositoryResponseWrapper}.
|
||||
* @param request the request to be wrapped
|
||||
* @param response the response to be wrapped
|
||||
*/
|
||||
SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request,
|
||||
HttpServletResponse response) {
|
||||
super(response);
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("request cannot be null");
|
||||
}
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResponseCommitted() {
|
||||
this.request.commitSession();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link javax.servlet.http.HttpServletRequest} that retrieves the
|
||||
* {@link javax.servlet.http.HttpSession} using a
|
||||
* {@link org.springframework.session.SessionRepository}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
private final class SessionRepositoryRequestWrapper
|
||||
extends HttpServletRequestWrapper {
|
||||
|
||||
private final HttpServletResponse response;
|
||||
|
||||
private S requestedSession;
|
||||
|
||||
private boolean requestedSessionCached;
|
||||
|
||||
private String requestedSessionId;
|
||||
|
||||
private Boolean requestedSessionIdValid;
|
||||
|
||||
private boolean requestedSessionInvalidated;
|
||||
|
||||
private SessionRepositoryRequestWrapper(HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
super(request);
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the {@link HttpSessionIdResolver} to write the session id to the response
|
||||
* and persist the Session.
|
||||
*/
|
||||
private void commitSession() {
|
||||
HttpSessionWrapper wrappedSession = getCurrentSession();
|
||||
if (wrappedSession == null) {
|
||||
if (isInvalidateClientSession()) {
|
||||
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this,
|
||||
this.response);
|
||||
}
|
||||
}
|
||||
else {
|
||||
S session = wrappedSession.getSession();
|
||||
clearRequestedSessionCache();
|
||||
SessionRepositoryFilter.this.sessionRepository.save(session);
|
||||
String sessionId = session.getId();
|
||||
if (!isRequestedSessionIdValid()
|
||||
|| !sessionId.equals(getRequestedSessionId())) {
|
||||
SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this,
|
||||
this.response, sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private HttpSessionWrapper getCurrentSession() {
|
||||
return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
|
||||
}
|
||||
|
||||
private void setCurrentSession(HttpSessionWrapper currentSession) {
|
||||
if (currentSession == null) {
|
||||
removeAttribute(CURRENT_SESSION_ATTR);
|
||||
}
|
||||
else {
|
||||
setAttribute(CURRENT_SESSION_ATTR, currentSession);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unused")
|
||||
public String changeSessionId() {
|
||||
HttpSession session = getSession(false);
|
||||
|
||||
if (session == null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot change session ID. There is no session associated with this request.");
|
||||
}
|
||||
|
||||
return getCurrentSession().getSession().changeSessionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequestedSessionIdValid() {
|
||||
if (this.requestedSessionIdValid == null) {
|
||||
S requestedSession = getRequestedSession();
|
||||
if (requestedSession != null) {
|
||||
requestedSession.setLastAccessedTime(Instant.now());
|
||||
}
|
||||
return isRequestedSessionIdValid(requestedSession);
|
||||
}
|
||||
return this.requestedSessionIdValid;
|
||||
}
|
||||
|
||||
private boolean isRequestedSessionIdValid(S session) {
|
||||
if (this.requestedSessionIdValid == null) {
|
||||
this.requestedSessionIdValid = session != null;
|
||||
}
|
||||
return this.requestedSessionIdValid;
|
||||
}
|
||||
|
||||
private boolean isInvalidateClientSession() {
|
||||
return getCurrentSession() == null && this.requestedSessionInvalidated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpSessionWrapper getSession(boolean create) {
|
||||
HttpSessionWrapper currentSession = getCurrentSession();
|
||||
if (currentSession != null) {
|
||||
return currentSession;
|
||||
}
|
||||
S requestedSession = getRequestedSession();
|
||||
if (requestedSession != null) {
|
||||
if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
|
||||
requestedSession.setLastAccessedTime(Instant.now());
|
||||
this.requestedSessionIdValid = true;
|
||||
currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
|
||||
currentSession.setNew(false);
|
||||
setCurrentSession(currentSession);
|
||||
return currentSession;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This is an invalid session id. No need to ask again if
|
||||
// request.getSession is invoked for the duration of this request
|
||||
if (SESSION_LOGGER.isDebugEnabled()) {
|
||||
SESSION_LOGGER.debug(
|
||||
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
|
||||
}
|
||||
setAttribute(INVALID_SESSION_ID_ATTR, "true");
|
||||
}
|
||||
if (!create) {
|
||||
return null;
|
||||
}
|
||||
if (SESSION_LOGGER.isDebugEnabled()) {
|
||||
SESSION_LOGGER.debug(
|
||||
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
|
||||
+ SESSION_LOGGER_NAME,
|
||||
new RuntimeException(
|
||||
"For debugging purposes only (not an error)"));
|
||||
}
|
||||
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
currentSession = new HttpSessionWrapper(session, getServletContext());
|
||||
setCurrentSession(currentSession);
|
||||
return currentSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpSessionWrapper getSession() {
|
||||
return getSession(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestedSessionId() {
|
||||
if (this.requestedSessionId == null) {
|
||||
getRequestedSession();
|
||||
}
|
||||
return this.requestedSessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestDispatcher getRequestDispatcher(String path) {
|
||||
RequestDispatcher requestDispatcher = super.getRequestDispatcher(path);
|
||||
return new SessionCommittingRequestDispatcher(requestDispatcher);
|
||||
}
|
||||
|
||||
private S getRequestedSession() {
|
||||
if (!this.requestedSessionCached) {
|
||||
List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
|
||||
.resolveSessionIds(this);
|
||||
for (String sessionId : sessionIds) {
|
||||
if (this.requestedSessionId == null) {
|
||||
this.requestedSessionId = sessionId;
|
||||
}
|
||||
S session = SessionRepositoryFilter.this.sessionRepository
|
||||
.findById(sessionId);
|
||||
if (session != null) {
|
||||
this.requestedSession = session;
|
||||
this.requestedSessionId = sessionId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.requestedSessionCached = true;
|
||||
}
|
||||
return this.requestedSession;
|
||||
}
|
||||
|
||||
private void clearRequestedSessionCache() {
|
||||
this.requestedSessionCached = false;
|
||||
this.requestedSession = null;
|
||||
this.requestedSessionId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows creating an HttpSession from a Session instance.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
private final class HttpSessionWrapper extends HttpSessionAdapter<S> {
|
||||
|
||||
HttpSessionWrapper(S session, ServletContext servletContext) {
|
||||
super(session, servletContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
super.invalidate();
|
||||
SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true;
|
||||
setCurrentSession(null);
|
||||
clearRequestedSessionCache();
|
||||
SessionRepositoryFilter.this.sessionRepository.deleteById(getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures session is committed before issuing an include.
|
||||
*
|
||||
* @since 1.3.4
|
||||
*/
|
||||
private final class SessionCommittingRequestDispatcher
|
||||
implements RequestDispatcher {
|
||||
|
||||
private final RequestDispatcher delegate;
|
||||
|
||||
SessionCommittingRequestDispatcher(RequestDispatcher delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forward(ServletRequest request, ServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
this.delegate.forward(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void include(ServletRequest request, ServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
SessionRepositoryRequestWrapper.this.commitSession();
|
||||
this.delegate.include(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.server.session;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.session.ReactiveSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.session.WebSessionStore;
|
||||
|
||||
/**
|
||||
* The {@link WebSessionStore} implementation that provides the {@link WebSession}
|
||||
* implementation backed by a {@link Session} returned by the
|
||||
* {@link ReactiveSessionRepository}.
|
||||
*
|
||||
* @param <S> the {@link Session} type
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SpringSessionWebSessionStore<S extends Session> implements WebSessionStore {
|
||||
|
||||
private final ReactiveSessionRepository<S> sessions;
|
||||
|
||||
private Clock clock = Clock.system(ZoneOffset.UTC);
|
||||
|
||||
public SpringSessionWebSessionStore(ReactiveSessionRepository<S> reactiveSessionRepository) {
|
||||
Assert.notNull(reactiveSessionRepository, "reactiveSessionRepository cannot be null");
|
||||
this.sessions = reactiveSessionRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link Clock} to use to set lastAccessTime on every created
|
||||
* session and to calculate if it is expired.
|
||||
* <p>This may be useful to align to different timezone or to set the clock
|
||||
* back in a test, e.g. {@code Clock.offset(clock, Duration.ofMinutes(-31))}
|
||||
* in order to simulate session expiration.
|
||||
* <p>By default this is {@code Clock.system(ZoneId.of("GMT"))}.
|
||||
* @param clock the clock to use
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> createWebSession() {
|
||||
return this.sessions.createSession().map(this::createSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> updateLastAccessTime(WebSession session) {
|
||||
@SuppressWarnings("unchecked")
|
||||
SpringSessionWebSession springSessionWebSession = (SpringSessionWebSession) session;
|
||||
springSessionWebSession.session.setLastAccessedTime(this.clock.instant());
|
||||
return Mono.just(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> retrieveSession(String sessionId) {
|
||||
return this.sessions.findById(sessionId)
|
||||
.doOnNext((session) -> session.setLastAccessedTime(this.clock.instant()))
|
||||
.map(this::existingSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> removeSession(String sessionId) {
|
||||
return this.sessions.deleteById(sessionId);
|
||||
}
|
||||
|
||||
private SpringSessionWebSession createSession(S session) {
|
||||
return new SpringSessionWebSession(session, State.NEW);
|
||||
}
|
||||
|
||||
private SpringSessionWebSession existingSession(S session) {
|
||||
return new SpringSessionWebSession(session, State.STARTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts Spring Session's {@link Session} to a {@link WebSession}.
|
||||
*/
|
||||
private class SpringSessionWebSession implements WebSession {
|
||||
|
||||
private final S session;
|
||||
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
private AtomicReference<State> state = new AtomicReference<>();
|
||||
|
||||
SpringSessionWebSession(S session, State state) {
|
||||
Assert.notNull(session, "session cannot be null");
|
||||
this.session = session;
|
||||
this.attributes = new SpringSessionMap(session);
|
||||
this.state.set(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.session.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> changeSessionId() {
|
||||
return Mono.defer(() -> {
|
||||
this.session
|
||||
.changeSessionId();
|
||||
return save();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
this.state.compareAndSet(State.NEW, State.STARTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
State value = this.state.get();
|
||||
return (State.STARTED.equals(value)
|
||||
|| (State.NEW.equals(value) && !getAttributes().isEmpty()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> invalidate() {
|
||||
this.state.set(State.EXPIRED);
|
||||
return SpringSessionWebSessionStore.this.sessions.deleteById(this.session.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save() {
|
||||
return SpringSessionWebSessionStore.this.sessions.save(this.session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
if (this.state.get().equals(State.EXPIRED)) {
|
||||
return true;
|
||||
}
|
||||
if (this.session.isExpired()) {
|
||||
this.state.set(State.EXPIRED);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getCreationTime() {
|
||||
return this.session.getCreationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getLastAccessTime() {
|
||||
return this.session.getLastAccessedTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getMaxIdleTime() {
|
||||
return this.session.getMaxInactiveInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxIdleTime(Duration maxIdleTime) {
|
||||
this.session.setMaxInactiveInterval(maxIdleTime);
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {
|
||||
NEW, STARTED, EXPIRED
|
||||
}
|
||||
|
||||
private static class SpringSessionMap implements Map<String, Object> {
|
||||
|
||||
private final Session session;
|
||||
|
||||
private final Collection<Object> values = new SessionValues();
|
||||
|
||||
SpringSessionMap(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.session.getAttributeNames().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.session.getAttributeNames().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return key instanceof String
|
||||
&& this.session.getAttributeNames().contains(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return this.session.getAttributeNames().stream()
|
||||
.anyMatch((attrName) -> this.session.getAttribute(attrName) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object get(Object key) {
|
||||
if (key instanceof String) {
|
||||
return this.session.getAttribute((String) key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(String key, Object value) {
|
||||
Object original = this.session.getAttribute(key);
|
||||
this.session.setAttribute(key, value);
|
||||
return original;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object remove(Object key) {
|
||||
if (key instanceof String) {
|
||||
String attrName = (String) key;
|
||||
Object original = this.session.getAttribute(attrName);
|
||||
this.session.removeAttribute(attrName);
|
||||
return original;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ?> m) {
|
||||
for (Entry<? extends String, ?> entry : m.entrySet()) {
|
||||
put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
for (String attrName : this.session.getAttributeNames()) {
|
||||
remove(attrName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return this.session.getAttributeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return this.values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
Set<String> attrNames = keySet();
|
||||
Set<Entry<String, Object>> entries = new HashSet<>(attrNames.size());
|
||||
for (String attrName : attrNames) {
|
||||
Object value = this.session.getAttribute(attrName);
|
||||
entries.add(new AbstractMap.SimpleEntry<>(attrName, value));
|
||||
}
|
||||
return Collections.unmodifiableSet(entries);
|
||||
}
|
||||
|
||||
private class SessionValues extends AbstractCollection<Object> {
|
||||
|
||||
@Override
|
||||
public Iterator<Object> iterator() {
|
||||
return new Iterator<Object>() {
|
||||
|
||||
private Iterator<Entry<String, Object>> i = entrySet().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.i.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object next() {
|
||||
return this.i.next().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
this.i.remove();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return SpringSessionMap.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return SpringSessionMap.this.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
SpringSessionMap.this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object v) {
|
||||
return SpringSessionMap.this.containsValue(v);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spring Session reactive web support.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.session.web.server.session;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.socket.config.annotation;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.messaging.simp.config.ChannelRegistration;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.web.socket.handler.WebSocketConnectHandlerDecoratorFactory;
|
||||
import org.springframework.session.web.socket.handler.WebSocketRegistryListener;
|
||||
import org.springframework.session.web.socket.server.SessionRepositoryMessageInterceptor;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.StompWebSocketEndpointRegistration;
|
||||
import org.springframework.web.socket.config.annotation.WebMvcStompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
|
||||
import org.springframework.web.socket.messaging.StompSubProtocolErrorHandler;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
/**
|
||||
* Eases configuration of Web Socket and Spring Session integration.
|
||||
*
|
||||
* <p>
|
||||
* The configuration:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Ensures the {@link Session} is kept alive on incoming web socket messages.</li>
|
||||
* <li>Ensures that Web Socket Sessions are destroyed when a {@link Session} is terminated
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Example usage
|
||||
* </p>
|
||||
*
|
||||
* <code>
|
||||
* {@literal @Configuration}
|
||||
* {@literal @EnableScheduling}
|
||||
* {@literal @EnableWebSocketMessageBroker}
|
||||
* {@literal public class WebSocketConfig<S extends Session> extends AbstractSessionWebSocketMessageBrokerConfigurer<S>} {
|
||||
*
|
||||
* {@literal @Override}
|
||||
* protected void configureStompEndpoints(StompEndpointRegistry registry) {
|
||||
* registry.addEndpoint("/messages")
|
||||
* .withSockJS();
|
||||
* }
|
||||
*
|
||||
* {@literal @Override}
|
||||
* public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
* registry.enableSimpleBroker("/queue/", "/topic/");
|
||||
* registry.setApplicationDestinationPrefixes("/app");
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param <S> the type of Session
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
public abstract class AbstractSessionWebSocketMessageBrokerConfigurer<S extends Session>
|
||||
implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Autowired
|
||||
@SuppressWarnings("rawtypes")
|
||||
private SessionRepository sessionRepository;
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@Override
|
||||
public void configureClientInboundChannel(ChannelRegistration registration) {
|
||||
registration.interceptors(sessionRepositoryInterceptor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
if (registry instanceof WebMvcStompEndpointRegistry) {
|
||||
WebMvcStompEndpointRegistry mvcRegistry = (WebMvcStompEndpointRegistry) registry;
|
||||
configureStompEndpoints(new SessionStompEndpointRegistry(mvcRegistry,
|
||||
sessionRepositoryInterceptor()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register STOMP endpoints mapping each to a specific URL and (optionally) enabling
|
||||
* and configuring SockJS fallback options with a
|
||||
* {@link SessionRepositoryMessageInterceptor} automatically added as an interceptor.
|
||||
*
|
||||
* @param registry the {@link StompEndpointRegistry} which automatically has a
|
||||
* {@link SessionRepositoryMessageInterceptor} added to it.
|
||||
*/
|
||||
protected abstract void configureStompEndpoints(StompEndpointRegistry registry);
|
||||
|
||||
@Override
|
||||
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
|
||||
registration.addDecoratorFactory(wsConnectHandlerDecoratorFactory());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketRegistryListener webSocketRegistryListener() {
|
||||
return new WebSocketRegistryListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketConnectHandlerDecoratorFactory wsConnectHandlerDecoratorFactory() {
|
||||
return new WebSocketConnectHandlerDecoratorFactory(this.eventPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
public SessionRepositoryMessageInterceptor<S> sessionRepositoryInterceptor() {
|
||||
return new SessionRepositoryMessageInterceptor<>(this.sessionRepository);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link StompEndpointRegistry} that applies {@link HandshakeInterceptor}.
|
||||
*/
|
||||
static class SessionStompEndpointRegistry implements StompEndpointRegistry {
|
||||
private final WebMvcStompEndpointRegistry registry;
|
||||
private final HandshakeInterceptor interceptor;
|
||||
|
||||
SessionStompEndpointRegistry(WebMvcStompEndpointRegistry registry,
|
||||
HandshakeInterceptor interceptor) {
|
||||
this.registry = registry;
|
||||
this.interceptor = interceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StompWebSocketEndpointRegistration addEndpoint(String... paths) {
|
||||
StompWebSocketEndpointRegistration endpoints = this.registry
|
||||
.addEndpoint(paths);
|
||||
endpoints.addInterceptors(this.interceptor);
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrder(int order) {
|
||||
this.registry.setOrder(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
|
||||
this.registry.setUrlPathHelper(urlPathHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebMvcStompEndpointRegistry setErrorHandler(
|
||||
StompSubProtocolErrorHandler errorHandler) {
|
||||
return this.registry.setErrorHandler(errorHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.socket.events;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.session.web.socket.handler.WebSocketConnectHandlerDecoratorFactory;
|
||||
import org.springframework.session.web.socket.handler.WebSocketRegistryListener;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
/**
|
||||
* Similar to Spring {@link org.springframework.web.socket.messaging.SessionConnectEvent}
|
||||
* except that it provides access to the {@link WebSocketSession} to allow mapping the
|
||||
* Spring Session to the {@link WebSocketSession}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
* @see WebSocketRegistryListener
|
||||
* @see WebSocketConnectHandlerDecoratorFactory
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SessionConnectEvent extends ApplicationEvent {
|
||||
|
||||
private final WebSocketSession webSocketSession;
|
||||
|
||||
public SessionConnectEvent(Object source, WebSocketSession webSocketSession) {
|
||||
super(source);
|
||||
this.webSocketSession = webSocketSession;
|
||||
}
|
||||
|
||||
public WebSocketSession getWebSocketSession() {
|
||||
return this.webSocketSession;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.socket.handler;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.web.socket.events.SessionConnectEvent;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
|
||||
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
|
||||
|
||||
/**
|
||||
* Ensures that a {@link SessionConnectEvent} is published in
|
||||
* {@link WebSocketHandler#afterConnectionEstablished(WebSocketSession)}. This is
|
||||
* necessary so that the {@link WebSocketSession} can be mapped to the corresponding
|
||||
* Spring {@link Session} to terminate any {@link WebSocketSession} associated with a
|
||||
* Spring {@link Session} that was destroyed.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*
|
||||
* @see WebSocketRegistryListener
|
||||
*/
|
||||
public final class WebSocketConnectHandlerDecoratorFactory
|
||||
implements WebSocketHandlerDecoratorFactory {
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(WebSocketConnectHandlerDecoratorFactory.class);
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param eventPublisher the {@link ApplicationEventPublisher} to use. Cannot be null.
|
||||
*/
|
||||
public WebSocketConnectHandlerDecoratorFactory(
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
Assert.notNull(eventPublisher, "eventPublisher cannot be null");
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketHandler decorate(WebSocketHandler handler) {
|
||||
return new SessionWebSocketHandler(handler);
|
||||
}
|
||||
|
||||
private final class SessionWebSocketHandler extends WebSocketHandlerDecorator {
|
||||
|
||||
SessionWebSocketHandler(WebSocketHandler delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession wsSession)
|
||||
throws Exception {
|
||||
super.afterConnectionEstablished(wsSession);
|
||||
|
||||
publishEvent(new SessionConnectEvent(this, wsSession));
|
||||
}
|
||||
|
||||
private void publishEvent(ApplicationEvent event) {
|
||||
try {
|
||||
WebSocketConnectHandlerDecoratorFactory.this.eventPublisher
|
||||
.publishEvent(event);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.error("Error publishing " + event + ".", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.socket.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
import org.springframework.session.web.socket.events.SessionConnectEvent;
|
||||
import org.springframework.session.web.socket.server.SessionRepositoryMessageInterceptor;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Keeps track of mapping the Spring Session ID to the {@link WebSocketSession} and
|
||||
* ensuring when a {@link SessionDestroyedEvent} is fired that the
|
||||
* {@link WebSocketSession} is closed.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Mark Anderson
|
||||
* @since 1.0
|
||||
*/
|
||||
public final class WebSocketRegistryListener
|
||||
implements ApplicationListener<ApplicationEvent> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(WebSocketRegistryListener.class);
|
||||
|
||||
static final CloseStatus SESSION_EXPIRED_STATUS = new CloseStatus(
|
||||
CloseStatus.POLICY_VIOLATION.getCode(),
|
||||
"This connection was established under an authenticated HTTP Session that has expired");
|
||||
|
||||
private final ConcurrentHashMap<String, Map<String, WebSocketSession>> httpSessionIdToWsSessions = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof SessionDestroyedEvent) {
|
||||
SessionDestroyedEvent e = (SessionDestroyedEvent) event;
|
||||
closeWsSessions(e.getSessionId());
|
||||
}
|
||||
else if (event instanceof SessionConnectEvent) {
|
||||
SessionConnectEvent e = (SessionConnectEvent) event;
|
||||
afterConnectionEstablished(e.getWebSocketSession());
|
||||
}
|
||||
else if (event instanceof SessionDisconnectEvent) {
|
||||
SessionDisconnectEvent e = (SessionDisconnectEvent) event;
|
||||
Map<String, Object> sessionAttributes = SimpMessageHeaderAccessor
|
||||
.getSessionAttributes(e.getMessage().getHeaders());
|
||||
String httpSessionId = (sessionAttributes != null)
|
||||
? SessionRepositoryMessageInterceptor.getSessionId(sessionAttributes)
|
||||
: null;
|
||||
afterConnectionClosed(httpSessionId, e.getSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
private void afterConnectionEstablished(WebSocketSession wsSession) {
|
||||
Principal principal = wsSession.getPrincipal();
|
||||
if (principal == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String httpSessionId = getHttpSessionId(wsSession);
|
||||
registerWsSession(httpSessionId, wsSession);
|
||||
}
|
||||
|
||||
private String getHttpSessionId(WebSocketSession wsSession) {
|
||||
Map<String, Object> attributes = wsSession.getAttributes();
|
||||
return SessionRepositoryMessageInterceptor.getSessionId(attributes);
|
||||
}
|
||||
|
||||
private void afterConnectionClosed(String httpSessionId, String wsSessionId) {
|
||||
if (httpSessionId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, WebSocketSession> sessions = this.httpSessionIdToWsSessions
|
||||
.get(httpSessionId);
|
||||
if (sessions != null) {
|
||||
boolean result = sessions.remove(wsSessionId) != null;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Removal of " + wsSessionId + " was " + result);
|
||||
}
|
||||
if (sessions.isEmpty()) {
|
||||
this.httpSessionIdToWsSessions.remove(httpSessionId);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Removed the corresponding HTTP Session for "
|
||||
+ wsSessionId + " since it contained no WebSocket mappings");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerWsSession(String httpSessionId, WebSocketSession wsSession) {
|
||||
Map<String, WebSocketSession> sessions = this.httpSessionIdToWsSessions
|
||||
.get(httpSessionId);
|
||||
if (sessions == null) {
|
||||
sessions = new ConcurrentHashMap<>();
|
||||
this.httpSessionIdToWsSessions.putIfAbsent(httpSessionId, sessions);
|
||||
sessions = this.httpSessionIdToWsSessions.get(httpSessionId);
|
||||
}
|
||||
sessions.put(wsSession.getId(), wsSession);
|
||||
}
|
||||
|
||||
private void closeWsSessions(String httpSessionId) {
|
||||
Map<String, WebSocketSession> sessionsToClose = this.httpSessionIdToWsSessions
|
||||
.remove(httpSessionId);
|
||||
if (sessionsToClose == null) {
|
||||
return;
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(
|
||||
"Closing WebSocket connections associated to expired HTTP Session "
|
||||
+ httpSessionId);
|
||||
}
|
||||
for (WebSocketSession toClose : sessionsToClose.values()) {
|
||||
try {
|
||||
toClose.close(SESSION_EXPIRED_STATUS);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.debug(
|
||||
"Failed to close WebSocketSession (this is nothing to worry about but for debugging only)",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.socket.server;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Acts as a {@link ChannelInterceptor} and a {@link HandshakeInterceptor} to ensure the
|
||||
* {@link Session#getLastAccessedTime()} is up to date.
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Associates the {@link Session#getId()} with the WebSocket Session attributes when
|
||||
* the handshake is performed. This is later used when intercepting messages to ensure the
|
||||
* {@link Session#getLastAccessedTime()} is updated.</li>
|
||||
* <li>Intercepts {@link Message}'s that are have {@link SimpMessageType} that corresponds
|
||||
* to {@link #setMatchingMessageTypes(Set)} and updates the last accessed time of the
|
||||
* {@link Session}. If the {@link Session} is expired, the {@link Message} is prevented
|
||||
* from proceeding.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* In order to work {@link SessionRepositoryMessageInterceptor} must be registered as a
|
||||
* {@link ChannelInterceptor} and a {@link HandshakeInterceptor} .
|
||||
* </p>
|
||||
*
|
||||
* @param <S> the {@link Session} type
|
||||
* @author Rob Winch
|
||||
* @since 1.0
|
||||
*/
|
||||
public final class SessionRepositoryMessageInterceptor<S extends Session>
|
||||
implements ChannelInterceptor, HandshakeInterceptor {
|
||||
|
||||
private static final String SPRING_SESSION_ID_ATTR_NAME = "SPRING.SESSION.ID";
|
||||
|
||||
private final SessionRepository<S> sessionRepository;
|
||||
|
||||
private Set<SimpMessageType> matchingMessageTypes;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param sessionRepository the {@link SessionRepository} to use. Cannot be null.
|
||||
*/
|
||||
public SessionRepositoryMessageInterceptor(SessionRepository<S> sessionRepository) {
|
||||
Assert.notNull(sessionRepository, "sessionRepository cannot be null");
|
||||
this.sessionRepository = sessionRepository;
|
||||
this.matchingMessageTypes = EnumSet.of(SimpMessageType.CONNECT,
|
||||
SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE,
|
||||
SimpMessageType.UNSUBSCRIBE);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the {@link SimpMessageType} to match on. If the {@link Message} matches, then
|
||||
* {@link #preSend(Message, MessageChannel)} ensures the {@link Session} is not
|
||||
* expired and updates the {@link Session#getLastAccessedTime()}
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The default is: SimpMessageType.CONNECT, SimpMessageType.MESSAGE,
|
||||
* SimpMessageType.SUBSCRIBE, SimpMessageType.UNSUBSCRIBE.
|
||||
* </p>
|
||||
*
|
||||
* @param matchingMessageTypes the {@link SimpMessageType} to match on in
|
||||
* {@link #preSend(Message, MessageChannel)}, else the {@link Message} is continued
|
||||
* without accessing or updating the {@link Session}
|
||||
*/
|
||||
public void setMatchingMessageTypes(Set<SimpMessageType> matchingMessageTypes) {
|
||||
Assert.notEmpty(matchingMessageTypes,
|
||||
"matchingMessageTypes cannot be null or empty");
|
||||
this.matchingMessageTypes = matchingMessageTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
if (message == null) {
|
||||
return message;
|
||||
}
|
||||
SimpMessageType messageType = SimpMessageHeaderAccessor
|
||||
.getMessageType(message.getHeaders());
|
||||
if (!this.matchingMessageTypes.contains(messageType)) {
|
||||
return message;
|
||||
}
|
||||
Map<String, Object> sessionHeaders = SimpMessageHeaderAccessor
|
||||
.getSessionAttributes(message.getHeaders());
|
||||
String sessionId = (sessionHeaders != null)
|
||||
? (String) sessionHeaders.get(SPRING_SESSION_ID_ATTR_NAME)
|
||||
: null;
|
||||
if (sessionId != null) {
|
||||
S session = this.sessionRepository.findById(sessionId);
|
||||
if (session != null) {
|
||||
// update the last accessed time
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
this.sessionRepository.save(session);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
if (request instanceof ServletServerHttpRequest) {
|
||||
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
|
||||
HttpSession session = servletRequest.getServletRequest().getSession(false);
|
||||
if (session != null) {
|
||||
setSessionId(attributes, session.getId());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler wsHandler, Exception exception) {
|
||||
}
|
||||
|
||||
public static String getSessionId(Map<String, Object> attributes) {
|
||||
return (String) attributes.get(SPRING_SESSION_ID_ATTR_NAME);
|
||||
}
|
||||
|
||||
public static void setSessionId(Map<String, Object> attributes, String sessionId) {
|
||||
attributes.put(SPRING_SESSION_ID_ATTR_NAME, sessionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link MapSessionRepository}.
|
||||
*/
|
||||
public class MapSessionRepositoryTests {
|
||||
|
||||
private MapSessionRepository repository;
|
||||
|
||||
private MapSession session;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.repository = new MapSessionRepository(new ConcurrentHashMap<>());
|
||||
this.session = new MapSession();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionExpired() {
|
||||
this.session.setMaxInactiveInterval(Duration.ofSeconds(1));
|
||||
this.session.setLastAccessedTime(Instant.now().minus(5, ChronoUnit.MINUTES));
|
||||
this.repository.save(this.session);
|
||||
|
||||
assertThat(this.repository.findById(this.session.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionDefaultExpiration() {
|
||||
Session session = this.repository.createSession();
|
||||
|
||||
assertThat(session).isInstanceOf(MapSession.class);
|
||||
assertThat(session.getMaxInactiveInterval())
|
||||
.isEqualTo(new MapSession().getMaxInactiveInterval());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionCustomDefaultExpiration() {
|
||||
final Duration expectedMaxInterval = new MapSession().getMaxInactiveInterval()
|
||||
.plusSeconds(10);
|
||||
this.repository.setDefaultMaxInactiveInterval(
|
||||
(int) expectedMaxInterval.getSeconds());
|
||||
|
||||
Session session = this.repository.createSession();
|
||||
|
||||
assertThat(session.getMaxInactiveInterval())
|
||||
.isEqualTo(expectedMaxInterval);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenNotYetSaved() {
|
||||
MapSession createSession = this.repository.createSession();
|
||||
|
||||
String originalId = createSession.getId();
|
||||
createSession.changeSessionId();
|
||||
|
||||
this.repository.save(createSession);
|
||||
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
assertThat(this.repository.findById(createSession.getId())).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenSaved() {
|
||||
MapSession createSession = this.repository.createSession();
|
||||
|
||||
this.repository.save(createSession);
|
||||
|
||||
String originalId = createSession.getId();
|
||||
createSession.changeSessionId();
|
||||
|
||||
this.repository.save(createSession);
|
||||
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
assertThat(this.repository.findById(createSession.getId())).isNotNull();
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
MapSession session = this.repository.createSession();
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
public class MapSessionTests {
|
||||
|
||||
private MapSession session;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.session = new MapSession();
|
||||
this.session.setLastAccessedTime(Instant.ofEpochMilli(1413258262962L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullSession() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new MapSession((Session) null))
|
||||
.withMessage("session cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAttributeWhenNullThenNull() {
|
||||
String result = this.session.getAttribute("attrName");
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAttributeOrDefaultWhenNullThenDefaultValue() {
|
||||
String defaultValue = "default";
|
||||
String result = this.session.getAttributeOrDefault("attrName", defaultValue);
|
||||
assertThat(result).isEqualTo(defaultValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAttributeOrDefaultWhenNotNullThenDefaultValue() {
|
||||
String defaultValue = "default";
|
||||
String attrValue = "value";
|
||||
String attrName = "attrName";
|
||||
this.session.setAttribute(attrName, attrValue);
|
||||
|
||||
String result = this.session.getAttributeOrDefault(attrName, defaultValue);
|
||||
|
||||
assertThat(result).isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequiredAttributeWhenNullThenException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.session.getRequiredAttribute("attrName"))
|
||||
.withMessage("Required attribute 'attrName' is missing.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequiredAttributeWhenNotNullThenReturns() {
|
||||
String attrValue = "value";
|
||||
String attrName = "attrName";
|
||||
this.session.setAttribute(attrName, attrValue);
|
||||
|
||||
String result = this.session.getRequiredAttribute("attrName");
|
||||
|
||||
assertThat(result).isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure conforms to the javadoc of {@link Session}
|
||||
*/
|
||||
@Test
|
||||
public void setAttributeNullObjectRemoves() {
|
||||
String attr = "attr";
|
||||
this.session.setAttribute(attr, new Object());
|
||||
this.session.setAttribute(attr, null);
|
||||
assertThat(this.session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsNonSessionFalse() {
|
||||
assertThat(this.session.equals(new Object())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsCustomSession() {
|
||||
CustomSession other = new CustomSession();
|
||||
this.session.setId(other.getId());
|
||||
assertThat(this.session.equals(other)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hashCodeEqualsIdHashCode() {
|
||||
this.session.setId("constantId");
|
||||
assertThat(this.session.hashCode()).isEqualTo(this.session.getId().hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isExpiredExact() {
|
||||
Instant now = Instant.ofEpochMilli(1413260062962L);
|
||||
assertThat(this.session.isExpired(now)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isExpiredOneMsTooSoon() {
|
||||
Instant now = Instant.ofEpochMilli(1413260062961L);
|
||||
assertThat(this.session.isExpired(now)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isExpiredOneMsAfter() {
|
||||
Instant now = Instant.ofEpochMilli(1413260062963L);
|
||||
assertThat(this.session.isExpired(now)).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
this.session.setAttribute("attribute1", "value1");
|
||||
this.session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : this.session.getAttributeNames()) {
|
||||
this.session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(this.session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
static class CustomSession implements Session {
|
||||
|
||||
@Override
|
||||
public Instant getCreationTime() {
|
||||
return Instant.EPOCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String changeSessionId() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getLastAccessedTime() {
|
||||
return Instant.EPOCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(Duration interval) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getMaxInactiveInterval() {
|
||||
return Duration.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNames() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String attributeName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReactiveMapSessionRepository}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ReactiveMapSessionRepositoryTests {
|
||||
|
||||
private ReactiveMapSessionRepository repository;
|
||||
|
||||
private MapSession session;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.repository = new ReactiveMapSessionRepository(new HashMap<>());
|
||||
this.session = new MapSession("session-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorMapThenFound() {
|
||||
Map<String, Session> sessions = new HashMap<>();
|
||||
sessions.put(this.session.getId(), this.session);
|
||||
this.repository = new ReactiveMapSessionRepository(sessions);
|
||||
|
||||
Session findByIdSession = this.repository.findById(this.session.getId()).block();
|
||||
assertThat(findByIdSession).isNotNull();
|
||||
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorMapWhenNullThenThrowsIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new ReactiveMapSessionRepository(null))
|
||||
.withMessage("sessions cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveWhenNoSubscribersThenNotFound() {
|
||||
this.repository.save(this.session);
|
||||
|
||||
assertThat(this.repository.findById(this.session.getId()).block()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveWhenSubscriberThenFound() {
|
||||
this.repository.save(this.session).block();
|
||||
|
||||
Session findByIdSession = this.repository.findById(this.session.getId()).block();
|
||||
assertThat(findByIdSession).isNotNull();
|
||||
assertThat(findByIdSession.getId()).isEqualTo(this.session.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIdWhenExpiredRemovesFromSessionMap() {
|
||||
this.session.setMaxInactiveInterval(Duration.ofMinutes(1));
|
||||
this.session.setLastAccessedTime(Instant.now().minus(5, ChronoUnit.MINUTES));
|
||||
|
||||
Map<String, Session> sessions = new ConcurrentHashMap<>();
|
||||
sessions.put("session-id", this.session);
|
||||
this.repository = new ReactiveMapSessionRepository(sessions);
|
||||
|
||||
assertThat(this.repository.findById(this.session.getId()).block()).isNull();
|
||||
assertThat(sessions).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenDefaultMaxInactiveIntervalThenDefaultMaxInactiveInterval() {
|
||||
Session session = this.repository.createSession().block();
|
||||
|
||||
assertThat(session).isInstanceOf(MapSession.class);
|
||||
assertThat(session.getMaxInactiveInterval())
|
||||
.isEqualTo(new MapSession().getMaxInactiveInterval());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenCustomMaxInactiveIntervalThenCustomMaxInactiveInterval() {
|
||||
final Duration expectedMaxInterval = new MapSession().getMaxInactiveInterval()
|
||||
.plusSeconds(10);
|
||||
this.repository
|
||||
.setDefaultMaxInactiveInterval((int) expectedMaxInterval.getSeconds());
|
||||
|
||||
Session session = this.repository.createSession().block();
|
||||
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(expectedMaxInterval);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenNotYetSaved() {
|
||||
MapSession createSession = this.repository.createSession().block();
|
||||
|
||||
String originalId = createSession.getId();
|
||||
createSession.changeSessionId();
|
||||
|
||||
this.repository.save(createSession).block();
|
||||
|
||||
assertThat(this.repository.findById(originalId).block()).isNull();
|
||||
assertThat(this.repository.findById(createSession.getId()).block()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenSaved() {
|
||||
MapSession createSession = this.repository.createSession().block();
|
||||
|
||||
this.repository.save(createSession).block();
|
||||
|
||||
String originalId = createSession.getId();
|
||||
createSession.changeSessionId();
|
||||
|
||||
this.repository.save(createSession).block();
|
||||
|
||||
assertThat(this.repository.findById(originalId).block()).isNull();
|
||||
assertThat(this.repository.findById(createSession.getId()).block()).isNotNull();
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
MapSession session = this.repository.createSession().block();
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.config.annotation.web.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.web.http.CookieSerializer;
|
||||
import org.springframework.session.web.http.CookieSerializer.CookieValue;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringHttpSessionConfiguration} using a custom
|
||||
* {@link CookieSerializer}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class EnableSpringHttpSessionCustomCookieSerializerTests {
|
||||
|
||||
@Autowired
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
@Autowired
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private MockFilterChain chain;
|
||||
|
||||
@Autowired
|
||||
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
|
||||
|
||||
@Autowired
|
||||
private SessionRepository sessionRepository;
|
||||
|
||||
@Autowired
|
||||
private CookieSerializer cookieSerializer;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.chain = new MockFilterChain();
|
||||
|
||||
reset(this.sessionRepository);
|
||||
reset(this.cookieSerializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesReadSessionIds() throws Exception {
|
||||
String sessionId = "sessionId";
|
||||
given(this.cookieSerializer.readCookieValues(any(HttpServletRequest.class)))
|
||||
.willReturn(Collections.singletonList(sessionId));
|
||||
given(this.sessionRepository.findById(anyString()))
|
||||
.willReturn(new MapSession(sessionId));
|
||||
|
||||
this.sessionRepositoryFilter.doFilter(this.request, this.response, this.chain);
|
||||
|
||||
assertThat(getRequest().getRequestedSessionId()).isEqualTo(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesWrite() throws Exception {
|
||||
given(this.sessionRepository.createSession()).willReturn(new MapSession());
|
||||
|
||||
this.sessionRepositoryFilter.doFilter(this.request, this.response,
|
||||
new MockFilterChain() {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response)
|
||||
throws IOException, ServletException {
|
||||
((HttpServletRequest) request).getSession();
|
||||
super.doFilter(request, response);
|
||||
}
|
||||
});
|
||||
|
||||
verify(this.cookieSerializer).writeCookieValue(any(CookieValue.class));
|
||||
}
|
||||
|
||||
private HttpServletRequest getRequest() {
|
||||
return (HttpServletRequest) this.chain.getRequest();
|
||||
}
|
||||
|
||||
@EnableSpringHttpSession
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public SessionRepository sessionRepository() {
|
||||
return mock(SessionRepository.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CookieSerializer cookieSerializer() {
|
||||
return mock(CookieSerializer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.config.annotation.web.http;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.session.MapSessionRepository;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
|
||||
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
|
||||
import org.springframework.session.web.http.DefaultCookieSerializer;
|
||||
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringHttpSessionConfiguration}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public class SpringHttpSessionConfigurationTests {
|
||||
|
||||
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
|
||||
@AfterEach
|
||||
public void closeContext() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
this.context.register(annotatedClasses);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noSessionRepositoryConfiguration() {
|
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
|
||||
.isThrownBy(() -> registerAndRefresh(EmptyConfiguration.class))
|
||||
.withMessageContaining("org.springframework.session.SessionRepository");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultConfiguration() {
|
||||
registerAndRefresh(DefaultConfiguration.class);
|
||||
|
||||
assertThat(this.context.getBean(SessionEventHttpSessionListenerAdapter.class))
|
||||
.isNotNull();
|
||||
assertThat(this.context.getBean(SessionRepositoryFilter.class)).isNotNull();
|
||||
assertThat(this.context.getBean(SessionRepository.class)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sessionCookieConfigConfiguration() {
|
||||
registerAndRefresh(SessionCookieConfigConfiguration.class);
|
||||
|
||||
SessionRepositoryFilter sessionRepositoryFilter = this.context
|
||||
.getBean(SessionRepositoryFilter.class);
|
||||
assertThat(sessionRepositoryFilter).isNotNull();
|
||||
CookieHttpSessionIdResolver httpSessionIdResolver = (CookieHttpSessionIdResolver) ReflectionTestUtils
|
||||
.getField(sessionRepositoryFilter, "httpSessionIdResolver");
|
||||
assertThat(httpSessionIdResolver).isNotNull();
|
||||
DefaultCookieSerializer cookieSerializer = (DefaultCookieSerializer) ReflectionTestUtils
|
||||
.getField(httpSessionIdResolver, "cookieSerializer");
|
||||
assertThat(cookieSerializer).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(cookieSerializer, "cookieName"))
|
||||
.isEqualTo("test-name");
|
||||
assertThat(ReflectionTestUtils.getField(cookieSerializer, "cookiePath"))
|
||||
.isEqualTo("test-path");
|
||||
assertThat(ReflectionTestUtils.getField(cookieSerializer, "cookieMaxAge"))
|
||||
.isEqualTo(600);
|
||||
assertThat(ReflectionTestUtils.getField(cookieSerializer, "domainName"))
|
||||
.isEqualTo("test-domain");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rememberMeServicesConfiguration() {
|
||||
registerAndRefresh(RememberMeServicesConfiguration.class);
|
||||
|
||||
SessionRepositoryFilter sessionRepositoryFilter = this.context
|
||||
.getBean(SessionRepositoryFilter.class);
|
||||
assertThat(sessionRepositoryFilter).isNotNull();
|
||||
CookieHttpSessionIdResolver httpSessionIdResolver = (CookieHttpSessionIdResolver) ReflectionTestUtils
|
||||
.getField(sessionRepositoryFilter, "httpSessionIdResolver");
|
||||
assertThat(httpSessionIdResolver).isNotNull();
|
||||
DefaultCookieSerializer cookieSerializer = (DefaultCookieSerializer) ReflectionTestUtils
|
||||
.getField(httpSessionIdResolver, "cookieSerializer");
|
||||
assertThat(cookieSerializer).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(cookieSerializer,
|
||||
"rememberMeRequestAttribute")).isEqualTo(
|
||||
SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableSpringHttpSession
|
||||
static class EmptyConfiguration {
|
||||
}
|
||||
|
||||
static class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public MapSessionRepository sessionRepository() {
|
||||
return new MapSessionRepository(new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableSpringHttpSession
|
||||
static class DefaultConfiguration extends BaseConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableSpringHttpSession
|
||||
static class SessionCookieConfigConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public ServletContext servletContext() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.getSessionCookieConfig().setName("test-name");
|
||||
servletContext.getSessionCookieConfig().setDomain("test-domain");
|
||||
servletContext.getSessionCookieConfig().setPath("test-path");
|
||||
servletContext.getSessionCookieConfig().setMaxAge(600);
|
||||
return servletContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableSpringHttpSession
|
||||
static class RememberMeServicesConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public SpringSessionRememberMeServices rememberMeServices() {
|
||||
return new SpringSessionRememberMeServices();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.config.annotation.web.server;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.session.ReactiveMapSessionRepository;
|
||||
import org.springframework.session.ReactiveSessionRepository;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
import org.springframework.web.server.session.CookieWebSessionIdResolver;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.HeaderWebSessionIdResolver;
|
||||
import org.springframework.web.server.session.WebSessionIdResolver;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Verify various configurations through {@link EnableSpringWebSession}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class SpringWebSessionConfigurationTests {
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableSpringWebSessionConfiguresThings() {
|
||||
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(GoodConfig.class);
|
||||
this.context.refresh();
|
||||
|
||||
WebSessionManager webSessionManagerFoundByType = this.context.getBean(WebSessionManager.class);
|
||||
Object webSessionManagerFoundByName = this.context.getBean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME);
|
||||
|
||||
assertThat(webSessionManagerFoundByType).isNotNull();
|
||||
assertThat(webSessionManagerFoundByName).isNotNull();
|
||||
assertThat(webSessionManagerFoundByType).isEqualTo(webSessionManagerFoundByName);
|
||||
|
||||
assertThat(this.context.getBean(ReactiveSessionRepository.class)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingReactiveSessionRepositoryBreaksAppContext() {
|
||||
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(BadConfig.class);
|
||||
|
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
|
||||
.isThrownBy(this.context::refresh)
|
||||
.withMessageContaining("Error creating bean with name 'webSessionManager'")
|
||||
.withMessageContaining("No qualifying bean of type '" + ReactiveSessionRepository.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultSessionIdResolverShouldBeCookieBased() {
|
||||
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(GoodConfig.class);
|
||||
this.context.refresh();
|
||||
|
||||
DefaultWebSessionManager manager = this.context.getBean(DefaultWebSessionManager.class);
|
||||
assertThat(manager.getSessionIdResolver().getClass()).isAssignableFrom(CookieWebSessionIdResolver.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void providedSessionIdResolverShouldBePickedUpAutomatically() {
|
||||
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(OverrideSessionIdResolver.class);
|
||||
this.context.refresh();
|
||||
|
||||
DefaultWebSessionManager manager = this.context.getBean(DefaultWebSessionManager.class);
|
||||
assertThat(manager.getSessionIdResolver().getClass()).isAssignableFrom(HeaderWebSessionIdResolver.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration with all the right parts.
|
||||
*/
|
||||
@EnableSpringWebSession
|
||||
static class GoodConfig {
|
||||
|
||||
/**
|
||||
* Use Reactor-friendly, {@link java.util.Map}-backed {@link ReactiveSessionRepository} for test purposes.
|
||||
*/
|
||||
@Bean
|
||||
ReactiveSessionRepository<?> reactiveSessionRepository() {
|
||||
return new ReactiveMapSessionRepository(new HashMap<>());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration where no {@link ReactiveSessionRepository} is defined. It's BAD!
|
||||
*/
|
||||
@EnableSpringWebSession
|
||||
static class BadConfig {
|
||||
|
||||
}
|
||||
|
||||
@EnableSpringWebSession
|
||||
static class OverrideSessionIdResolver {
|
||||
|
||||
@Bean
|
||||
ReactiveSessionRepository<?> reactiveSessionRepository() {
|
||||
return new ReactiveMapSessionRepository(new HashMap<>());
|
||||
}
|
||||
|
||||
@Bean
|
||||
WebSessionIdResolver alternateWebSessionIdResolver() {
|
||||
return new HeaderWebSessionIdResolver();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.security;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringSessionBackedSessionRegistry}.
|
||||
*/
|
||||
public class SpringSessionBackedSessionRegistryTest {
|
||||
|
||||
private static final String SESSION_ID = "sessionId";
|
||||
|
||||
private static final String SESSION_ID2 = "otherSessionId";
|
||||
|
||||
private static final String USER_NAME = "userName";
|
||||
|
||||
private static final User PRINCIPAL = new User(USER_NAME, "password",
|
||||
Collections.emptyList());
|
||||
|
||||
private static final Instant NOW = Instant.now();
|
||||
|
||||
@Mock
|
||||
private FindByIndexNameSessionRepository<Session> sessionRepository;
|
||||
|
||||
@InjectMocks
|
||||
private SpringSessionBackedSessionRegistry<Session> sessionRegistry;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sessionInformationForExistingSession() {
|
||||
Session session = createSession(SESSION_ID, USER_NAME, NOW);
|
||||
when(this.sessionRepository.findById(SESSION_ID)).thenReturn(session);
|
||||
|
||||
SessionInformation sessionInfo = this.sessionRegistry
|
||||
.getSessionInformation(SESSION_ID);
|
||||
|
||||
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
|
||||
assertThat(
|
||||
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
|
||||
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
|
||||
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
|
||||
assertThat(sessionInfo.isExpired()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sessionInformationForExpiredSession() {
|
||||
Session session = createSession(SESSION_ID, USER_NAME, NOW);
|
||||
session.setAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR,
|
||||
Boolean.TRUE);
|
||||
when(this.sessionRepository.findById(SESSION_ID)).thenReturn(session);
|
||||
|
||||
SessionInformation sessionInfo = this.sessionRegistry
|
||||
.getSessionInformation(SESSION_ID);
|
||||
|
||||
assertThat(sessionInfo.getSessionId()).isEqualTo(SESSION_ID);
|
||||
assertThat(
|
||||
sessionInfo.getLastRequest().toInstant().truncatedTo(ChronoUnit.MILLIS))
|
||||
.isEqualTo(NOW.truncatedTo(ChronoUnit.MILLIS));
|
||||
assertThat(sessionInfo.getPrincipal()).isEqualTo(USER_NAME);
|
||||
assertThat(sessionInfo.isExpired()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noSessionInformationForMissingSession() {
|
||||
assertThat(this.sessionRegistry.getSessionInformation("nonExistingSessionId"))
|
||||
.isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllSessions() {
|
||||
setUpSessions();
|
||||
|
||||
List<SessionInformation> allSessionInfos = this.sessionRegistry
|
||||
.getAllSessions(PRINCIPAL, true);
|
||||
|
||||
assertThat(allSessionInfos).extracting("sessionId").containsExactly(SESSION_ID,
|
||||
SESSION_ID2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNonExpiredSessions() {
|
||||
setUpSessions();
|
||||
|
||||
List<SessionInformation> nonExpiredSessionInfos = this.sessionRegistry
|
||||
.getAllSessions(PRINCIPAL, false);
|
||||
|
||||
assertThat(nonExpiredSessionInfos).extracting("sessionId")
|
||||
.containsExactly(SESSION_ID2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expireNow() {
|
||||
Session session = createSession(SESSION_ID, USER_NAME, NOW);
|
||||
when(this.sessionRepository.findById(SESSION_ID)).thenReturn(session);
|
||||
|
||||
SessionInformation sessionInfo = this.sessionRegistry
|
||||
.getSessionInformation(SESSION_ID);
|
||||
assertThat(sessionInfo.isExpired()).isFalse();
|
||||
|
||||
sessionInfo.expireNow();
|
||||
|
||||
assertThat(sessionInfo.isExpired()).isTrue();
|
||||
ArgumentCaptor<Session> captor = ArgumentCaptor.forClass(Session.class);
|
||||
verify(this.sessionRepository).save(captor.capture());
|
||||
assertThat(captor.getValue().<Boolean>getAttribute(
|
||||
SpringSessionBackedSessionInformation.EXPIRED_ATTR))
|
||||
.isEqualTo(Boolean.TRUE);
|
||||
}
|
||||
|
||||
private Session createSession(String sessionId, String userName,
|
||||
Instant lastAccessed) {
|
||||
MapSession session = new MapSession(sessionId);
|
||||
session.setLastAccessedTime(lastAccessed);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(authentication.getName()).thenReturn(userName);
|
||||
SecurityContextImpl securityContext = new SecurityContextImpl();
|
||||
securityContext.setAuthentication(authentication);
|
||||
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
|
||||
return session;
|
||||
}
|
||||
|
||||
private void setUpSessions() {
|
||||
Session session1 = createSession(SESSION_ID, USER_NAME, NOW);
|
||||
session1.setAttribute(SpringSessionBackedSessionInformation.EXPIRED_ATTR,
|
||||
Boolean.TRUE);
|
||||
Session session2 = createSession(SESSION_ID2, USER_NAME, NOW);
|
||||
Map<String, Session> sessions = new LinkedHashMap<>();
|
||||
sessions.put(session1.getId(), session1);
|
||||
sessions.put(session2.getId(), session2);
|
||||
when(this.sessionRepository.findByPrincipalName(USER_NAME))
|
||||
.thenReturn(sessions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.security.web.authentication;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringSessionRememberMeServices}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public class SpringSessionRememberMeServicesTests {
|
||||
|
||||
private SpringSessionRememberMeServices rememberMeServices;
|
||||
|
||||
@Test
|
||||
public void create() {
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
assertThat(ReflectionTestUtils.getField(this.rememberMeServices,
|
||||
"rememberMeParameterName")).isEqualTo("remember-me");
|
||||
assertThat(
|
||||
ReflectionTestUtils.getField(this.rememberMeServices, "alwaysRemember"))
|
||||
.isEqualTo(false);
|
||||
assertThat(
|
||||
ReflectionTestUtils.getField(this.rememberMeServices, "validitySeconds"))
|
||||
.isEqualTo(2592000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWithCustomParameter() {
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
this.rememberMeServices.setRememberMeParameterName("test-param");
|
||||
assertThat(ReflectionTestUtils.getField(this.rememberMeServices,
|
||||
"rememberMeParameterName")).isEqualTo("test-param");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWithNullParameter() {
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(
|
||||
() -> this.rememberMeServices.setRememberMeParameterName(null))
|
||||
.withMessage("rememberMeParameterName cannot be empty or null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWithAlwaysRemember() {
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
this.rememberMeServices.setAlwaysRemember(true);
|
||||
assertThat(
|
||||
ReflectionTestUtils.getField(this.rememberMeServices, "alwaysRemember"))
|
||||
.isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWithCustomValidity() {
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
this.rememberMeServices.setValiditySeconds(100000);
|
||||
assertThat(
|
||||
ReflectionTestUtils.getField(this.rememberMeServices, "validitySeconds"))
|
||||
.isEqualTo(100000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoLogin() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
this.rememberMeServices.autoLogin(request, response);
|
||||
verifyZeroInteractions(request, response);
|
||||
}
|
||||
|
||||
// gh-752
|
||||
@Test
|
||||
public void loginFailRemoveSecurityContext() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
HttpSession session = mock(HttpSession.class);
|
||||
given(request.getSession(eq(false))).willReturn(session);
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
this.rememberMeServices.loginFail(request, response);
|
||||
verify(request, times(1)).getSession(eq(false));
|
||||
verify(session, times(1)).removeAttribute(
|
||||
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
||||
verifyZeroInteractions(request, response, session);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginSuccess() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
HttpSession session = mock(HttpSession.class);
|
||||
given(request.getParameter(eq("remember-me"))).willReturn("true");
|
||||
given(request.getSession()).willReturn(session);
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
this.rememberMeServices.loginSuccess(request, response, authentication);
|
||||
verify(request, times(1)).getParameter(eq("remember-me"));
|
||||
verify(request, times(1)).getSession();
|
||||
verify(request, times(1)).setAttribute(
|
||||
eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
||||
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
|
||||
verifyZeroInteractions(request, response, session, authentication);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginSuccessWithCustomParameter() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
HttpSession session = mock(HttpSession.class);
|
||||
given(request.getParameter(eq("test-param"))).willReturn("true");
|
||||
given(request.getSession()).willReturn(session);
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
this.rememberMeServices.setRememberMeParameterName("test-param");
|
||||
this.rememberMeServices.loginSuccess(request, response, authentication);
|
||||
verify(request, times(1)).getParameter(eq("test-param"));
|
||||
verify(request, times(1)).getSession();
|
||||
verify(request, times(1)).setAttribute(
|
||||
eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
||||
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
|
||||
verifyZeroInteractions(request, response, session, authentication);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginSuccessWithAlwaysRemember() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
HttpSession session = mock(HttpSession.class);
|
||||
given(request.getSession()).willReturn(session);
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
this.rememberMeServices.setAlwaysRemember(true);
|
||||
this.rememberMeServices.loginSuccess(request, response, authentication);
|
||||
verify(request, times(1)).getSession();
|
||||
verify(request, times(1)).setAttribute(
|
||||
eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
||||
verify(session, times(1)).setMaxInactiveInterval(eq(2592000));
|
||||
verifyZeroInteractions(request, response, session, authentication);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginSuccessWithCustomValidity() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
HttpSession session = mock(HttpSession.class);
|
||||
given(request.getParameter(eq("remember-me"))).willReturn("true");
|
||||
given(request.getSession()).willReturn(session);
|
||||
this.rememberMeServices = new SpringSessionRememberMeServices();
|
||||
this.rememberMeServices.setValiditySeconds(100000);
|
||||
this.rememberMeServices.loginSuccess(request, response, authentication);
|
||||
verify(request, times(1)).getParameter(eq("remember-me"));
|
||||
verify(request, times(1)).getSession();
|
||||
verify(request, times(1)).setAttribute(
|
||||
eq(SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR), eq(true));
|
||||
verify(session, times(1)).setMaxInactiveInterval(eq(100000));
|
||||
verifyZeroInteractions(request, response, session, authentication);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CookieHttpSessionIdResolver}.
|
||||
*/
|
||||
public class CookieHttpSessionIdResolverTests {
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private CookieHttpSessionIdResolver strategy;
|
||||
private String cookieName;
|
||||
private Session session;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.cookieName = "SESSION";
|
||||
this.session = new MapSession();
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.strategy = new CookieHttpSessionIdResolver();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequestedSessionIdNull() throws Exception {
|
||||
assertThat(this.strategy.resolveSessionIds(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequestedSessionIdNotNull() throws Exception {
|
||||
setSessionCookie(this.session.getId());
|
||||
assertThat(this.strategy.resolveSessionIds(this.request))
|
||||
.isEqualTo(Collections.singletonList(this.session.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequestedSessionIdNotNullCustomCookieName() throws Exception {
|
||||
setCookieName("CUSTOM");
|
||||
setSessionCookie(this.session.getId());
|
||||
assertThat(this.strategy.resolveSessionIds(this.request))
|
||||
.isEqualTo(Collections.singletonList(this.session.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onNewSession() throws Exception {
|
||||
this.strategy.setSessionId(this.request, this.response, this.session.getId());
|
||||
assertThat(getSessionId()).isEqualTo(this.session.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onNewSessionTwiceSameId() throws Exception {
|
||||
this.strategy.setSessionId(this.request, this.response, this.session.getId());
|
||||
this.strategy.setSessionId(this.request, this.response, this.session.getId());
|
||||
|
||||
assertThat(this.response.getCookies()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onNewSessionTwiceNewId() throws Exception {
|
||||
Session newSession = new MapSession();
|
||||
|
||||
this.strategy.setSessionId(this.request, this.response, this.session.getId());
|
||||
this.strategy.setSessionId(this.request, this.response, newSession.getId());
|
||||
|
||||
Cookie[] cookies = this.response.getCookies();
|
||||
assertThat(cookies).hasSize(2);
|
||||
|
||||
assertThat(base64Decode(cookies[0].getValue())).isEqualTo(this.session.getId());
|
||||
assertThat(base64Decode(cookies[1].getValue())).isEqualTo(newSession.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onNewSessionCookiePath() throws Exception {
|
||||
this.request.setContextPath("/somethingunique");
|
||||
this.strategy.setSessionId(this.request, this.response, this.session.getId());
|
||||
|
||||
Cookie sessionCookie = this.response.getCookie(this.cookieName);
|
||||
assertThat(sessionCookie.getPath())
|
||||
.isEqualTo(this.request.getContextPath() + "/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onNewSessionCustomCookieName() throws Exception {
|
||||
setCookieName("CUSTOM");
|
||||
this.strategy.setSessionId(this.request, this.response, this.session.getId());
|
||||
assertThat(getSessionId()).isEqualTo(this.session.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeleteSession() throws Exception {
|
||||
this.strategy.expireSession(this.request, this.response);
|
||||
assertThat(getSessionId()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeleteSessionCookiePath() throws Exception {
|
||||
this.request.setContextPath("/somethingunique");
|
||||
this.strategy.expireSession(this.request, this.response);
|
||||
|
||||
Cookie sessionCookie = this.response.getCookie(this.cookieName);
|
||||
assertThat(sessionCookie.getPath())
|
||||
.isEqualTo(this.request.getContextPath() + "/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeleteSessionCustomCookieName() throws Exception {
|
||||
setCookieName("CUSTOM");
|
||||
this.strategy.expireSession(this.request, this.response);
|
||||
assertThat(getSessionId()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionCookieValue() {
|
||||
assertThat(createSessionCookieValue(17)).isEqualToIgnoringCase(
|
||||
"0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 a 10 b 11 c 12 d 13 e 14 f 15 10 16");
|
||||
}
|
||||
|
||||
private String createSessionCookieValue(long size) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (long i = 0; i < size; i++) {
|
||||
String hex = Long.toHexString(i);
|
||||
sb.append(hex);
|
||||
sb.append(" ");
|
||||
sb.append(i);
|
||||
if (i < size - 1) {
|
||||
sb.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void setCookieName(String cookieName) {
|
||||
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
|
||||
cookieSerializer.setCookieName(cookieName);
|
||||
this.strategy.setCookieSerializer(cookieSerializer);
|
||||
this.cookieName = cookieName;
|
||||
}
|
||||
|
||||
private void setSessionCookie(String value) {
|
||||
this.request.setCookies(new Cookie(this.cookieName, base64Encode(value)));
|
||||
}
|
||||
|
||||
private String getSessionId() {
|
||||
return base64Decode(this.response.getCookie(this.cookieName).getValue());
|
||||
}
|
||||
|
||||
private static String base64Encode(String value) {
|
||||
return Base64.getEncoder().encodeToString(value.getBytes());
|
||||
}
|
||||
|
||||
private static String base64Decode(String value) {
|
||||
return new String(Base64.getDecoder().decode(value));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.mock.web.MockCookie;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.session.web.http.CookieSerializer.CookieValue;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultCookieSerializer}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @author Eddú Meléndez
|
||||
*/
|
||||
public class DefaultCookieSerializerTests {
|
||||
|
||||
private String cookieName;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private DefaultCookieSerializer serializer;
|
||||
|
||||
private String sessionId;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.cookieName = "SESSION";
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.sessionId = "sessionId";
|
||||
this.serializer = new DefaultCookieSerializer();
|
||||
}
|
||||
|
||||
// --- readCookieValues ---
|
||||
|
||||
@Test
|
||||
public void readCookieValuesNull() {
|
||||
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieValuesSingle(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.request.setCookies(
|
||||
createCookie(this.cookieName, this.sessionId, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request))
|
||||
.containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readCookieSerializerUseBase64EncodingTrueValuesNotBase64() {
|
||||
this.sessionId = "&^%$*";
|
||||
this.serializer.setUseBase64Encoding(true);
|
||||
this.request.setCookies(new Cookie(this.cookieName, this.sessionId));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieValuesSingleAndInvalidName(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.request.setCookies(
|
||||
createCookie(this.cookieName, this.sessionId, useBase64Encoding),
|
||||
createCookie(this.cookieName + "INVALID", this.sessionId + "INVALID",
|
||||
useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request))
|
||||
.containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieValuesMulti(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String secondSession = "secondSessionId";
|
||||
this.request.setCookies(
|
||||
createCookie(this.cookieName, this.sessionId, useBase64Encoding),
|
||||
createCookie(this.cookieName, secondSession, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request))
|
||||
.containsExactly(this.sessionId, secondSession);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieValuesMultiCustomSessionCookieName(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
setCookieName("JSESSIONID");
|
||||
String secondSession = "secondSessionId";
|
||||
this.request.setCookies(
|
||||
createCookie(this.cookieName, this.sessionId, useBase64Encoding),
|
||||
createCookie(this.cookieName, secondSession, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request))
|
||||
.containsExactly(this.sessionId, secondSession);
|
||||
}
|
||||
|
||||
// gh-392
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieValuesNullCookieValue(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.request.setCookies(createCookie(this.cookieName, null, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieValuesNullCookieValueAndJvmRoute(boolean useBase64Encoding) {
|
||||
this.serializer.setJvmRoute("123");
|
||||
this.request.setCookies(createCookie(this.cookieName, null, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieValuesNullCookieValueAndNotNullCookie(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.serializer.setJvmRoute("123");
|
||||
this.request.setCookies(createCookie(this.cookieName, null, useBase64Encoding),
|
||||
createCookie(this.cookieName, this.sessionId, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request))
|
||||
.containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
// --- writeCookie ---
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void writeCookie(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookieValue(useBase64Encoding)).isEqualTo(this.sessionId);
|
||||
}
|
||||
|
||||
// --- httpOnly ---
|
||||
|
||||
@Test
|
||||
public void writeCookieHttpOnlyDefault() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().isHttpOnly()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieHttpOnlySetTrue() {
|
||||
this.serializer.setUseHttpOnlyCookie(true);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().isHttpOnly()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieHttpOnlySetFalse() {
|
||||
this.serializer.setUseHttpOnlyCookie(false);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().isHttpOnly()).isFalse();
|
||||
}
|
||||
|
||||
// --- domainName ---
|
||||
|
||||
@Test
|
||||
public void writeCookieDomainNameDefault() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getDomain()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieDomainNameCustom() {
|
||||
String domainName = "example.com";
|
||||
this.serializer.setDomainName(domainName);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getDomain()).isEqualTo(domainName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDomainNameAndDomainNamePatternThrows() {
|
||||
this.serializer.setDomainName("example.com");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.serializer.setDomainNamePattern(".*"))
|
||||
.withMessage("Cannot set both domainName and domainNamePattern");
|
||||
}
|
||||
|
||||
// --- domainNamePattern ---
|
||||
|
||||
@Test
|
||||
public void writeCookieDomainNamePattern() {
|
||||
String domainNamePattern = "^.+?\\.(\\w+\\.[a-z]+)$";
|
||||
this.serializer.setDomainNamePattern(domainNamePattern);
|
||||
|
||||
String[] matchingDomains = { "child.sub.example.com", "www.example.com" };
|
||||
for (String domain : matchingDomains) {
|
||||
this.request.setServerName(domain);
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
assertThat(getCookie().getDomain()).isEqualTo("example.com");
|
||||
|
||||
this.response = new MockHttpServletResponse();
|
||||
}
|
||||
|
||||
String[] notMatchingDomains = { "example.com", "localhost", "127.0.0.1" };
|
||||
for (String domain : notMatchingDomains) {
|
||||
this.request.setServerName(domain);
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
assertThat(getCookie().getDomain()).isNull();
|
||||
|
||||
this.response = new MockHttpServletResponse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDomainNamePatternAndDomainNameThrows() {
|
||||
this.serializer.setDomainNamePattern(".*");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.serializer.setDomainName("example.com"))
|
||||
.withMessage("Cannot set both domainName and domainNamePattern");
|
||||
}
|
||||
|
||||
// --- cookieName ---
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieNameDefault() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getName()).isEqualTo("SESSION");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieNameCustom() {
|
||||
String cookieName = "JSESSIONID";
|
||||
setCookieName(cookieName);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getName()).isEqualTo(cookieName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setCookieNameNullThrows() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.serializer.setCookieName(null))
|
||||
.withMessage("cookieName cannot be null");
|
||||
}
|
||||
|
||||
// --- cookiePath ---
|
||||
|
||||
@Test
|
||||
public void writeCookieCookiePathDefaultEmptyContextPathUsed() {
|
||||
this.request.setContextPath("");
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookiePathDefaultContextPathUsed() {
|
||||
this.request.setContextPath("/context");
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/context/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookiePathExplicitNullCookiePathContextPathUsed() {
|
||||
this.request.setContextPath("/context");
|
||||
this.serializer.setCookiePath(null);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/context/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookiePathExplicitCookiePath() {
|
||||
this.request.setContextPath("/context");
|
||||
this.serializer.setCookiePath("/");
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getPath()).isEqualTo("/");
|
||||
}
|
||||
|
||||
// --- cookieMaxAge ---
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieMaxAgeDefault() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieMaxAgeExplicit() {
|
||||
this.serializer.setCookieMaxAge(100);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieMaxAgeExplicitEmptyCookie() {
|
||||
this.serializer.setCookieMaxAge(100);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(""));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieCookieMaxAgeExplicitCookieValue() {
|
||||
CookieValue cookieValue = cookieValue(this.sessionId);
|
||||
cookieValue.setCookieMaxAge(100);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue);
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(100);
|
||||
}
|
||||
|
||||
// --- secure ---
|
||||
|
||||
@Test
|
||||
public void writeCookieDefaultInsecureRequest() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieSecureSecureRequest() {
|
||||
this.request.setSecure(true);
|
||||
this.serializer.setUseSecureCookie(true);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieSecureInsecureRequest() {
|
||||
this.serializer.setUseSecureCookie(true);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieInsecureSecureRequest() {
|
||||
this.request.setSecure(true);
|
||||
this.serializer.setUseSecureCookie(false);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieInecureInsecureRequest() {
|
||||
this.serializer.setUseSecureCookie(false);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSecure()).isFalse();
|
||||
}
|
||||
|
||||
// --- jvmRoute ---
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void writeCookieJvmRoute(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String jvmRoute = "route";
|
||||
this.serializer.setJvmRoute(jvmRoute);
|
||||
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookieValue(useBase64Encoding))
|
||||
.isEqualTo(this.sessionId + "." + jvmRoute);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieJvmRoute(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String jvmRoute = "route";
|
||||
this.serializer.setJvmRoute(jvmRoute);
|
||||
this.request.setCookies(createCookie(this.cookieName,
|
||||
this.sessionId + "." + jvmRoute, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request))
|
||||
.containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieJvmRouteRouteMissing(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String jvmRoute = "route";
|
||||
this.serializer.setJvmRoute(jvmRoute);
|
||||
this.request.setCookies(
|
||||
createCookie(this.cookieName, this.sessionId, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request))
|
||||
.containsOnly(this.sessionId);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "true", "false" })
|
||||
public void readCookieJvmRouteOnlyRoute(boolean useBase64Encoding) {
|
||||
this.serializer.setUseBase64Encoding(useBase64Encoding);
|
||||
String jvmRoute = "route";
|
||||
this.serializer.setJvmRoute(jvmRoute);
|
||||
this.request.setCookies(
|
||||
createCookie(this.cookieName, "." + jvmRoute, useBase64Encoding));
|
||||
|
||||
assertThat(this.serializer.readCookieValues(this.request)).containsOnly("");
|
||||
}
|
||||
|
||||
// --- rememberMe ---
|
||||
|
||||
@Test
|
||||
public void writeCookieRememberMeCookieMaxAgeDefault() {
|
||||
this.request.setAttribute("rememberMe", true);
|
||||
this.serializer.setRememberMeRequestAttribute("rememberMe");
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieRememberMeCookieMaxAgeOverride() {
|
||||
this.request.setAttribute("rememberMe", true);
|
||||
this.serializer.setRememberMeRequestAttribute("rememberMe");
|
||||
CookieValue cookieValue = cookieValue(this.sessionId);
|
||||
cookieValue.setCookieMaxAge(100);
|
||||
this.serializer.writeCookieValue(cookieValue);
|
||||
|
||||
assertThat(getCookie().getMaxAge()).isEqualTo(100);
|
||||
}
|
||||
|
||||
// --- sameSite ---
|
||||
|
||||
@Test
|
||||
public void writeCookieDefaultSameSiteLax() {
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieSetSameSiteLax() {
|
||||
this.serializer.setSameSite("Lax");
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSameSite()).isEqualTo("Lax");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieSetSameSiteStrict() {
|
||||
this.serializer.setSameSite("Strict");
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSameSite()).isEqualTo("Strict");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCookieSetSameSiteNull() {
|
||||
this.serializer.setSameSite(null);
|
||||
this.serializer.writeCookieValue(cookieValue(this.sessionId));
|
||||
|
||||
assertThat(getCookie().getSameSite()).isNull();
|
||||
}
|
||||
|
||||
public void setCookieName(String cookieName) {
|
||||
this.cookieName = cookieName;
|
||||
this.serializer.setCookieName(cookieName);
|
||||
}
|
||||
|
||||
private Cookie createCookie(String name, String value, boolean useBase64Encoding) {
|
||||
if (useBase64Encoding && StringUtils.hasLength(value)) {
|
||||
value = new String(Base64.getEncoder().encode(value.getBytes()));
|
||||
}
|
||||
return new Cookie(name, value);
|
||||
}
|
||||
|
||||
private MockCookie getCookie() {
|
||||
return (MockCookie) this.response.getCookie(this.cookieName);
|
||||
}
|
||||
|
||||
private String getCookieValue(boolean useBase64Encoding) {
|
||||
String value = getCookie().getValue();
|
||||
if (!useBase64Encoding) {
|
||||
return value;
|
||||
}
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return new String(Base64.getDecoder().decode(value));
|
||||
}
|
||||
|
||||
private CookieValue cookieValue(String cookieValue) {
|
||||
return new CookieValue(this.request, this.response, cookieValue);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link HeaderHttpSessionIdResolver}.
|
||||
*/
|
||||
public class HeaderHttpSessionIdResolverTests {
|
||||
|
||||
private static final String HEADER_X_AUTH_TOKEN = "X-Auth-Token";
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
private HeaderHttpSessionIdResolver resolver;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.resolver = HeaderHttpSessionIdResolver.xAuthToken();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createResolverWithXAuthTokenHeader() {
|
||||
HeaderHttpSessionIdResolver resolver = HeaderHttpSessionIdResolver.xAuthToken();
|
||||
assertThat(ReflectionTestUtils.getField(resolver, "headerName"))
|
||||
.isEqualTo("X-Auth-Token");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createResolverWithAuthenticationInfoHeader() {
|
||||
HeaderHttpSessionIdResolver resolver = HeaderHttpSessionIdResolver
|
||||
.authenticationInfo();
|
||||
assertThat(ReflectionTestUtils.getField(resolver, "headerName"))
|
||||
.isEqualTo("Authentication-Info");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createResolverWithCustomHeaderName() {
|
||||
HeaderHttpSessionIdResolver resolver = new HeaderHttpSessionIdResolver(
|
||||
"Custom-Header");
|
||||
assertThat(ReflectionTestUtils.getField(resolver, "headerName"))
|
||||
.isEqualTo("Custom-Header");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createResolverWithNullHeaderName() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new HeaderHttpSessionIdResolver(null))
|
||||
.withMessage("headerName cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequestedSessionIdNull() {
|
||||
assertThat(this.resolver.resolveSessionIds(this.request)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequestedSessionIdNotNull() {
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
setSessionId(sessionId);
|
||||
assertThat(this.resolver.resolveSessionIds(this.request))
|
||||
.isEqualTo(Collections.singletonList(sessionId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onNewSession() {
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
this.resolver.setSessionId(this.request, this.response, sessionId);
|
||||
assertThat(getSessionId()).isEqualTo(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeleteSession() {
|
||||
this.resolver.expireSession(this.request, this.response);
|
||||
assertThat(getSessionId()).isEmpty();
|
||||
}
|
||||
|
||||
// the header is set as apposed to added
|
||||
@Test
|
||||
public void onNewSessionMulti() {
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
this.resolver.setSessionId(this.request, this.response, sessionId);
|
||||
this.resolver.setSessionId(this.request, this.response, sessionId);
|
||||
assertThat(this.response.getHeaders(HEADER_X_AUTH_TOKEN).size()).isEqualTo(1);
|
||||
assertThat(this.response.getHeaders(HEADER_X_AUTH_TOKEN)).containsOnly(sessionId);
|
||||
}
|
||||
|
||||
// the header is set as apposed to added
|
||||
@Test
|
||||
public void onDeleteSessionMulti() {
|
||||
this.resolver.expireSession(this.request, this.response);
|
||||
this.resolver.expireSession(this.request, this.response);
|
||||
assertThat(this.response.getHeaders(HEADER_X_AUTH_TOKEN).size()).isEqualTo(1);
|
||||
assertThat(getSessionId()).isEmpty();
|
||||
}
|
||||
|
||||
private void setSessionId(String sessionId) {
|
||||
this.request.addHeader(HEADER_X_AUTH_TOKEN, sessionId);
|
||||
}
|
||||
|
||||
private String getSessionId() {
|
||||
return this.response.getHeader(HEADER_X_AUTH_TOKEN);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class OncePerRequestFilterTests {
|
||||
private MockHttpServletRequest request;
|
||||
private MockHttpServletResponse response;
|
||||
private MockFilterChain chain;
|
||||
private OncePerRequestFilter filter;
|
||||
private HttpServlet servlet;
|
||||
|
||||
private List<OncePerRequestFilter> invocations;
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("serial")
|
||||
public void setup() {
|
||||
this.servlet = new HttpServlet() {
|
||||
};
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.chain = new MockFilterChain();
|
||||
this.invocations = new ArrayList<>();
|
||||
this.filter = new OncePerRequestFilter() {
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
OncePerRequestFilterTests.this.invocations.add(this);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterOnce() throws ServletException, IOException {
|
||||
this.filter.doFilter(this.request, this.response, this.chain);
|
||||
|
||||
assertThat(this.invocations).containsOnly(this.filter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterMultiOnlyIvokesOnce() throws ServletException, IOException {
|
||||
this.filter.doFilter(this.request, this.response,
|
||||
new MockFilterChain(this.servlet, this.filter));
|
||||
|
||||
assertThat(this.invocations).containsOnly(this.filter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterOtherSubclassInvoked() throws ServletException, IOException {
|
||||
OncePerRequestFilter filter2 = new OncePerRequestFilter() {
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
OncePerRequestFilterTests.this.invocations.add(this);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
};
|
||||
this.filter.doFilter(this.request, this.response,
|
||||
new MockFilterChain(this.servlet, filter2));
|
||||
|
||||
assertThat(this.invocations).containsOnly(this.filter, filter2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.http;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpSessionEvent;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link SessionEventHttpSessionListenerAdapter}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1
|
||||
*/
|
||||
public class SessionEventHttpSessionListenerAdapterTests {
|
||||
|
||||
@Mock
|
||||
private HttpSessionListener listener1;
|
||||
|
||||
@Mock
|
||||
private HttpSessionListener listener2;
|
||||
|
||||
@Mock
|
||||
private ServletContext servletContext;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<HttpSessionEvent> sessionEvent;
|
||||
|
||||
private SessionDestroyedEvent destroyed;
|
||||
|
||||
private SessionCreatedEvent created;
|
||||
|
||||
private SessionEventHttpSessionListenerAdapter listener;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.listener = new SessionEventHttpSessionListenerAdapter(
|
||||
Arrays.asList(this.listener1, this.listener2));
|
||||
this.listener.setServletContext(new MockServletContext());
|
||||
|
||||
Session session = new MapSession();
|
||||
this.destroyed = new SessionDestroyedEvent(this, session);
|
||||
this.created = new SessionCreatedEvent(this, session);
|
||||
}
|
||||
|
||||
// We want relaxed constructor that will allow for an empty listeners to
|
||||
// make configuration easier (i.e. autowire all HttpSessionListeners and might get
|
||||
// none)
|
||||
@Test
|
||||
public void constructorEmptyWorks() {
|
||||
new SessionEventHttpSessionListenerAdapter(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that we short circuit onApplicationEvent as early as possible if no
|
||||
* listeners
|
||||
*/
|
||||
@Test
|
||||
public void onApplicationEventEmptyListenersDoesNotUseEvent() {
|
||||
this.listener = new SessionEventHttpSessionListenerAdapter(
|
||||
Collections.emptyList());
|
||||
this.destroyed = mock(SessionDestroyedEvent.class);
|
||||
|
||||
this.listener.onApplicationEvent(this.destroyed);
|
||||
|
||||
verifyZeroInteractions(this.destroyed, this.listener1, this.listener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventDestroyed() {
|
||||
this.listener.onApplicationEvent(this.destroyed);
|
||||
|
||||
verify(this.listener1).sessionDestroyed(this.sessionEvent.capture());
|
||||
verify(this.listener2).sessionDestroyed(this.sessionEvent.capture());
|
||||
|
||||
assertThat(this.sessionEvent.getValue().getSession().getId())
|
||||
.isEqualTo(this.destroyed.getSessionId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventCreated() {
|
||||
this.listener.onApplicationEvent(this.created);
|
||||
|
||||
verify(this.listener1).sessionCreated(this.sessionEvent.capture());
|
||||
verify(this.listener2).sessionCreated(this.sessionEvent.capture());
|
||||
|
||||
assertThat(this.sessionEvent.getValue().getSession().getId())
|
||||
.isEqualTo(this.created.getSessionId());
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.server.session;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.session.ReactiveSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.web.server.WebSession;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringSessionWebSessionStore}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public class SpringSessionWebSessionStoreTests<S extends Session> {
|
||||
|
||||
@Mock
|
||||
private ReactiveSessionRepository<S> sessionRepository;
|
||||
|
||||
@Mock
|
||||
private S createSession;
|
||||
|
||||
@Mock
|
||||
private S findByIdSession;
|
||||
|
||||
private SpringSessionWebSessionStore<S> webSessionStore;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.webSessionStore = new SpringSessionWebSessionStore<>(this.sessionRepository);
|
||||
given(this.sessionRepository.findById(any()))
|
||||
.willReturn(Mono.just(this.findByIdSession));
|
||||
given(this.sessionRepository.createSession())
|
||||
.willReturn(Mono.just(this.createSession));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenNullRepositoryThenThrowsIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new SpringSessionWebSessionStore<S>(null))
|
||||
.withMessage("reactiveSessionRepository cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenNoAttributesThenNotStarted() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
assertThat(createdWebSession.isStarted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenAddAttributeThenStarted() {
|
||||
given(this.createSession.getAttributeNames())
|
||||
.willReturn(Collections.singleton("a"));
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
assertThat(createdWebSession.isStarted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndSizeThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.size()).isEqualTo(0);
|
||||
|
||||
given(this.createSession.getAttributeNames())
|
||||
.willReturn(Collections.singleton("a"));
|
||||
|
||||
assertThat(attributes.size()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndIsEmptyThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.isEmpty()).isTrue();
|
||||
|
||||
given(this.createSession.getAttributeNames())
|
||||
.willReturn(Collections.singleton("a"));
|
||||
|
||||
assertThat(attributes.isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndContainsKeyAndNotStringThenFalse() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.containsKey(1L)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndContainsKeyAndNotFoundThenFalse() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.containsKey("a")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndContainsKeyAndFoundThenTrue() {
|
||||
given(this.createSession.getAttributeNames())
|
||||
.willReturn(Collections.singleton("a"));
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.containsKey("a")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndPutThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.put("a", "b");
|
||||
|
||||
verify(this.createSession).setAttribute("a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndPutNullThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.put("a", null);
|
||||
|
||||
verify(this.createSession).setAttribute("a", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndRemoveThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.remove("a");
|
||||
|
||||
verify(this.createSession).removeAttribute("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndPutAllThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.putAll(Collections.singletonMap("a", "b"));
|
||||
|
||||
verify(this.createSession).setAttribute("a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndClearThenDelegatesToCreateSession() {
|
||||
given(this.createSession.getAttributeNames())
|
||||
.willReturn(Collections.singleton("a"));
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.clear();
|
||||
|
||||
verify(this.createSession).removeAttribute("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndKeySetThenDelegatesToCreateSession() {
|
||||
given(this.createSession.getAttributeNames())
|
||||
.willReturn(Collections.singleton("a"));
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.keySet()).containsExactly("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndValuesThenDelegatesToCreateSession() {
|
||||
given(this.createSession.getAttributeNames())
|
||||
.willReturn(Collections.singleton("a"));
|
||||
given(this.createSession.getAttribute("a")).willReturn("b");
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.values()).containsExactly("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndEntrySetThenDelegatesToCreateSession() {
|
||||
String attrName = "attrName";
|
||||
given(this.createSession.getAttributeNames())
|
||||
.willReturn(Collections.singleton(attrName));
|
||||
String attrValue = "attrValue";
|
||||
given(this.createSession.getAttribute(attrName)).willReturn(attrValue);
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
Set<Map.Entry<String, Object>> entries = attributes.entrySet();
|
||||
|
||||
assertThat(entries)
|
||||
.containsExactly(new AbstractMap.SimpleEntry<>(attrName, attrValue));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveSessionThenStarted() {
|
||||
String id = "id";
|
||||
WebSession retrievedWebSession = this.webSessionStore.retrieveSession(id).block();
|
||||
|
||||
assertThat(retrievedWebSession.isStarted()).isTrue();
|
||||
verify(this.findByIdSession).setLastAccessedTime(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeSessionWhenInvokedThenSessionSaved() {
|
||||
String sessionId = "session-id";
|
||||
given(this.sessionRepository.deleteById(sessionId)).willReturn(Mono.empty());
|
||||
|
||||
this.webSessionStore.removeSession(sessionId).block();
|
||||
|
||||
verify(this.sessionRepository).deleteById(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClockWhenNullThenException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.webSessionStore.setClock(null))
|
||||
.withMessage("clock cannot be null");
|
||||
}
|
||||
|
||||
@Test // gh-1114
|
||||
public void createSessionThenSessionIsNotExpired() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
assertThat(createdWebSession.isExpired()).isFalse();
|
||||
}
|
||||
|
||||
@Test // gh-1114
|
||||
public void invalidateSessionThenSessionIsExpired() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
given(createdWebSession.invalidate()).willReturn(Mono.empty());
|
||||
|
||||
createdWebSession.invalidate().block();
|
||||
|
||||
assertThat(createdWebSession.isExpired()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.socket.handler;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.session.web.socket.events.SessionConnectEvent;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class WebSocketConnectHandlerDecoratorFactoryTests {
|
||||
@Mock
|
||||
ApplicationEventPublisher eventPublisher;
|
||||
@Mock
|
||||
WebSocketHandler delegate;
|
||||
@Mock
|
||||
WebSocketSession session;
|
||||
@Captor
|
||||
ArgumentCaptor<SessionConnectEvent> event;
|
||||
|
||||
WebSocketConnectHandlerDecoratorFactory factory;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.factory = new WebSocketConnectHandlerDecoratorFactory(this.eventPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullEventPublisher() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new WebSocketConnectHandlerDecoratorFactory(null))
|
||||
.withMessage("eventPublisher cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decorateAfterConnectionEstablished() throws Exception {
|
||||
WebSocketHandler decorated = this.factory.decorate(this.delegate);
|
||||
|
||||
decorated.afterConnectionEstablished(this.session);
|
||||
|
||||
verify(this.eventPublisher).publishEvent(this.event.capture());
|
||||
assertThat(this.event.getValue().getWebSocketSession()).isSameAs(this.session);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decorateAfterConnectionEstablishedEventError() throws Exception {
|
||||
WebSocketHandler decorated = this.factory.decorate(this.delegate);
|
||||
willThrow(new IllegalStateException("Test throw on publishEvent"))
|
||||
.given(this.eventPublisher).publishEvent(any(ApplicationEvent.class));
|
||||
|
||||
decorated.afterConnectionEstablished(this.session);
|
||||
|
||||
verify(this.eventPublisher).publishEvent(any(SessionConnectEvent.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.socket.handler;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.session.web.socket.events.SessionConnectEvent;
|
||||
import org.springframework.session.web.socket.server.SessionRepositoryMessageInterceptor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class WebSocketRegistryListenerTests {
|
||||
|
||||
@Mock
|
||||
private WebSocketSession wsSession;
|
||||
|
||||
@Mock
|
||||
private WebSocketSession wsSession2;
|
||||
|
||||
@Mock
|
||||
private Message<byte[]> message;
|
||||
|
||||
@Mock
|
||||
private Principal principal;
|
||||
|
||||
private SessionConnectEvent connect;
|
||||
|
||||
private SessionConnectEvent connect2;
|
||||
|
||||
private SessionDisconnectEvent disconnect;
|
||||
|
||||
private SessionDeletedEvent deleted;
|
||||
|
||||
private SessionExpiredEvent expired;
|
||||
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private WebSocketRegistryListener listener;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
String sessionId = "session-id";
|
||||
MapSession session = new MapSession(sessionId);
|
||||
|
||||
this.attributes = new HashMap<>();
|
||||
SessionRepositoryMessageInterceptor.setSessionId(this.attributes, sessionId);
|
||||
|
||||
given(this.wsSession.getAttributes()).willReturn(this.attributes);
|
||||
given(this.wsSession.getPrincipal()).willReturn(this.principal);
|
||||
given(this.wsSession.getId()).willReturn("wsSession-id");
|
||||
|
||||
given(this.wsSession2.getAttributes()).willReturn(this.attributes);
|
||||
given(this.wsSession2.getPrincipal()).willReturn(this.principal);
|
||||
given(this.wsSession2.getId()).willReturn("wsSession-id2");
|
||||
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put(SimpMessageHeaderAccessor.SESSION_ATTRIBUTES, this.attributes);
|
||||
given(this.message.getHeaders()).willReturn(new MessageHeaders(headers));
|
||||
|
||||
this.listener = new WebSocketRegistryListener();
|
||||
this.connect = new SessionConnectEvent(this.listener, this.wsSession);
|
||||
this.connect2 = new SessionConnectEvent(this.listener, this.wsSession2);
|
||||
this.disconnect = new SessionDisconnectEvent(this.listener, this.message,
|
||||
this.wsSession.getId(), CloseStatus.NORMAL);
|
||||
this.deleted = new SessionDeletedEvent(this.listener, session);
|
||||
this.expired = new SessionExpiredEvent(this.listener, session);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventConnectSessionDeleted() throws Exception {
|
||||
this.listener.onApplicationEvent(this.connect);
|
||||
|
||||
this.listener.onApplicationEvent(this.deleted);
|
||||
|
||||
verify(this.wsSession).close(WebSocketRegistryListener.SESSION_EXPIRED_STATUS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventConnectSessionExpired() throws Exception {
|
||||
this.listener.onApplicationEvent(this.connect);
|
||||
|
||||
this.listener.onApplicationEvent(this.expired);
|
||||
|
||||
verify(this.wsSession).close(WebSocketRegistryListener.SESSION_EXPIRED_STATUS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventConnectSessionDeletedNullPrincipal() throws Exception {
|
||||
given(this.wsSession.getPrincipal()).willReturn(null);
|
||||
this.listener.onApplicationEvent(this.connect);
|
||||
|
||||
this.listener.onApplicationEvent(this.deleted);
|
||||
|
||||
verify(this.wsSession, times(0)).close(any(CloseStatus.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventConnectDisconnect() throws Exception {
|
||||
this.listener.onApplicationEvent(this.connect);
|
||||
this.listener.onApplicationEvent(this.disconnect);
|
||||
|
||||
this.listener.onApplicationEvent(this.deleted);
|
||||
|
||||
verify(this.wsSession, times(0)).close(any(CloseStatus.class));
|
||||
}
|
||||
|
||||
// gh-76
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onApplicationEventConnectDisconnectCleanup() {
|
||||
this.listener.onApplicationEvent(this.connect);
|
||||
|
||||
this.listener.onApplicationEvent(this.disconnect);
|
||||
|
||||
Map<String, Map<String, WebSocketSession>> httpSessionIdToWsSessions = (Map<String, Map<String, WebSocketSession>>) ReflectionTestUtils
|
||||
.getField(this.listener, "httpSessionIdToWsSessions");
|
||||
assertThat(httpSessionIdToWsSessions).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventConnectDisconnectNullSession() throws Exception {
|
||||
this.listener.onApplicationEvent(this.connect);
|
||||
this.attributes.clear();
|
||||
|
||||
this.listener.onApplicationEvent(this.disconnect);
|
||||
|
||||
// no exception
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onApplicationEventConnectConnectDisonnect() throws Exception {
|
||||
this.listener.onApplicationEvent(this.connect);
|
||||
this.listener.onApplicationEvent(this.connect2);
|
||||
this.listener.onApplicationEvent(this.disconnect);
|
||||
|
||||
this.listener.onApplicationEvent(this.deleted);
|
||||
|
||||
verify(this.wsSession2).close(WebSocketRegistryListener.SESSION_EXPIRED_STATUS);
|
||||
verify(this.wsSession, times(0)).close(any(CloseStatus.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.web.socket.server;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
public class SessionRepositoryMessageInterceptorTests {
|
||||
@Mock
|
||||
SessionRepository<Session> sessionRepository;
|
||||
@Mock
|
||||
MessageChannel channel;
|
||||
@Mock
|
||||
Session session;
|
||||
|
||||
Message<?> createMessage;
|
||||
|
||||
SimpMessageHeaderAccessor headers;
|
||||
|
||||
SessionRepositoryMessageInterceptor<Session> interceptor;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.interceptor = new SessionRepositoryMessageInterceptor<>(
|
||||
this.sessionRepository);
|
||||
this.headers = SimpMessageHeaderAccessor.create();
|
||||
this.headers.setSessionId("session");
|
||||
this.headers.setSessionAttributes(new HashMap<>());
|
||||
setMessageType(SimpMessageType.MESSAGE);
|
||||
String sessionId = "http-session";
|
||||
setSessionId(sessionId);
|
||||
given(this.sessionRepository.findById(sessionId)).willReturn(this.session);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendconstructorNullRepository() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new SessionRepositoryMessageInterceptor<>(null))
|
||||
.withMessage("sessionRepository cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendNullMessage() {
|
||||
assertThat(this.interceptor.preSend(null, this.channel)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendConnectAckDoesNotInvokeSessionRepository() {
|
||||
setMessageType(SimpMessageType.CONNECT_ACK);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verifyZeroInteractions(this.sessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendHeartbeatDoesNotInvokeSessionRepository() {
|
||||
setMessageType(SimpMessageType.HEARTBEAT);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verifyZeroInteractions(this.sessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendDisconnectDoesNotInvokeSessionRepository() {
|
||||
setMessageType(SimpMessageType.DISCONNECT);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verifyZeroInteractions(this.sessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendOtherDoesNotInvokeSessionRepository() {
|
||||
setMessageType(SimpMessageType.OTHER);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verifyZeroInteractions(this.sessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setMatchingMessageTypesNull() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.interceptor.setMatchingMessageTypes(null))
|
||||
.withMessage("matchingMessageTypes cannot be null or empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setMatchingMessageTypesEmpty() {
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> this.interceptor.setMatchingMessageTypes(Collections.emptySet()))
|
||||
.withMessage("matchingMessageTypes cannot be null or empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendSetMatchingMessageTypes() {
|
||||
this.interceptor.setMatchingMessageTypes(EnumSet.of(SimpMessageType.DISCONNECT));
|
||||
setMessageType(SimpMessageType.DISCONNECT);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verify(this.sessionRepository).findById(anyString());
|
||||
verify(this.sessionRepository).save(this.session);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendConnectUpdatesLastUpdateTime() {
|
||||
setMessageType(SimpMessageType.CONNECT);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
|
||||
verify(this.sessionRepository).save(this.session);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendMessageUpdatesLastUpdateTime() {
|
||||
setMessageType(SimpMessageType.MESSAGE);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
|
||||
verify(this.sessionRepository).save(this.session);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendSubscribeUpdatesLastUpdateTime() {
|
||||
setMessageType(SimpMessageType.SUBSCRIBE);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
|
||||
verify(this.sessionRepository).save(this.session);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendUnsubscribeUpdatesLastUpdateTime() {
|
||||
setMessageType(SimpMessageType.UNSUBSCRIBE);
|
||||
this.session.setLastAccessedTime(Instant.EPOCH);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verify(this.session).setLastAccessedTime(argThat(isAlmostNow()));
|
||||
verify(this.sessionRepository).save(this.session);
|
||||
}
|
||||
|
||||
// This will updated when SPR-12288 is resolved
|
||||
@Test
|
||||
public void preSendExpiredSession() {
|
||||
setSessionId("expired");
|
||||
|
||||
this.interceptor.preSend(createMessage(), this.channel);
|
||||
|
||||
verify(this.sessionRepository, times(0)).save(any(Session.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendNullSessionId() {
|
||||
setSessionId(null);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verifyZeroInteractions(this.sessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preSendNullSessionAttributes() {
|
||||
this.headers.setSessionAttributes(null);
|
||||
|
||||
assertThat(this.interceptor.preSend(createMessage(), this.channel))
|
||||
.isSameAs(this.createMessage);
|
||||
|
||||
verifyZeroInteractions(this.sessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeHandshakeNotServletServerHttpRequest() throws Exception {
|
||||
assertThat(this.interceptor.beforeHandshake(null, null, null, null)).isTrue();
|
||||
|
||||
verifyZeroInteractions(this.sessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeHandshakeNullSession() throws Exception {
|
||||
ServletServerHttpRequest request = new ServletServerHttpRequest(
|
||||
new MockHttpServletRequest());
|
||||
assertThat(this.interceptor.beforeHandshake(request, null, null, null)).isTrue();
|
||||
|
||||
verifyZeroInteractions(this.sessionRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeHandshakeSession() throws Exception {
|
||||
MockHttpServletRequest httpRequest = new MockHttpServletRequest();
|
||||
HttpSession httpSession = httpRequest.getSession();
|
||||
ServletServerHttpRequest request = new ServletServerHttpRequest(httpRequest);
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
assertThat(this.interceptor.beforeHandshake(request, null, null, attributes))
|
||||
.isTrue();
|
||||
|
||||
assertThat(attributes.size()).isEqualTo(1);
|
||||
assertThat(SessionRepositoryMessageInterceptor.getSessionId(attributes))
|
||||
.isEqualTo(httpSession.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* At the moment there is no need for afterHandshake to do anything.
|
||||
*/
|
||||
@Test
|
||||
public void afterHandshakeDoesNothing() {
|
||||
this.interceptor.afterHandshake(null, null, null, null);
|
||||
|
||||
verifyZeroInteractions(this.sessionRepository);
|
||||
}
|
||||
|
||||
private void setSessionId(String id) {
|
||||
SessionRepositoryMessageInterceptor
|
||||
.setSessionId(this.headers.getSessionAttributes(), id);
|
||||
}
|
||||
|
||||
private Message<?> createMessage() {
|
||||
this.createMessage = MessageBuilder.createMessage("",
|
||||
this.headers.getMessageHeaders());
|
||||
return this.createMessage;
|
||||
}
|
||||
|
||||
private void setMessageType(SimpMessageType type) {
|
||||
this.headers.setHeader(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER, type);
|
||||
}
|
||||
|
||||
static AlmostNowMatcher isAlmostNow() {
|
||||
return new AlmostNowMatcher();
|
||||
}
|
||||
|
||||
static class AlmostNowMatcher implements ArgumentMatcher<Instant> {
|
||||
|
||||
@Override
|
||||
public boolean matches(Instant argument) {
|
||||
long now = System.currentTimeMillis();
|
||||
long delta = now - argument.toEpochMilli();
|
||||
return delta >= 0 && delta < TimeUnit.SECONDS.toMillis(3);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
24
spring-session-data-redis/spring-session-data-redis.gradle
Normal file
24
spring-session-data-redis/spring-session-data-redis.gradle
Normal file
@@ -0,0 +1,24 @@
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
description = "Spring Session Redis implementation"
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-core')
|
||||
compile ("org.springframework.data:spring-data-redis") {
|
||||
exclude group: "org.slf4j", module: 'slf4j-api'
|
||||
exclude group: "org.slf4j", module: 'jcl-over-slf4j'
|
||||
}
|
||||
|
||||
optional "io.projectreactor:reactor-core"
|
||||
optional "org.springframework:spring-web"
|
||||
|
||||
testCompile "io.projectreactor:reactor-test"
|
||||
testCompile "javax.servlet:javax.servlet-api"
|
||||
testCompile "org.springframework:spring-web"
|
||||
testCompile "org.springframework.security:spring-security-core"
|
||||
testCompile "org.junit.jupiter:junit-jupiter-api"
|
||||
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
||||
|
||||
integrationTestCompile "io.lettuce:lettuce-core"
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
|
||||
public class SessionEventRegistry implements ApplicationListener<AbstractSessionEvent> {
|
||||
private Map<String, AbstractSessionEvent> events = new HashMap<>();
|
||||
private ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
String sessionId = event.getSessionId();
|
||||
this.events.put(sessionId, event);
|
||||
Object lock = getLock(sessionId);
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.events.clear();
|
||||
this.locks.clear();
|
||||
}
|
||||
|
||||
public boolean receivedEvent(String sessionId) throws InterruptedException {
|
||||
return waitForEvent(sessionId) != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends AbstractSessionEvent> E getEvent(String sessionId)
|
||||
throws InterruptedException {
|
||||
return (E) waitForEvent(sessionId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends AbstractSessionEvent> E waitForEvent(String sessionId)
|
||||
throws InterruptedException {
|
||||
Object lock = getLock(sessionId);
|
||||
synchronized (lock) {
|
||||
if (!this.events.containsKey(sessionId)) {
|
||||
lock.wait(10000);
|
||||
}
|
||||
}
|
||||
return (E) this.events.get(sessionId);
|
||||
}
|
||||
|
||||
private Object getLock(String sessionId) {
|
||||
return this.locks.computeIfAbsent(sessionId, (k) -> new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis;
|
||||
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
|
||||
/**
|
||||
* Base class for {@link RedisOperationsSessionRepository} integration tests.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public abstract class AbstractRedisITests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:5.0.5";
|
||||
|
||||
protected static class BaseConfig {
|
||||
|
||||
@Bean
|
||||
public GenericContainer redisContainer() {
|
||||
GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
.withExposedPorts(6379);
|
||||
redisContainer.start();
|
||||
return redisContainer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(
|
||||
redisContainer().getContainerIpAddress(),
|
||||
redisContainer().getFirstMappedPort());
|
||||
return new LettuceConnectionFactory(configuration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ReactiveRedisOperationsSessionRepository}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedisITests {
|
||||
|
||||
@Autowired
|
||||
private ReactiveRedisOperationsSessionRepository repository;
|
||||
|
||||
@Test
|
||||
public void saves() throws InterruptedException {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
|
||||
String expectedAttributeName = "a";
|
||||
String expectedAttributeValue = "b";
|
||||
|
||||
toSave.setAttribute(expectedAttributeName, expectedAttributeValue);
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
Session session = this.repository.findById(toSave.getId()).block();
|
||||
|
||||
assertThat(session.getId()).isEqualTo(toSave.getId());
|
||||
assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames());
|
||||
assertThat(session.<String>getAttribute(expectedAttributeName))
|
||||
.isEqualTo(toSave.getAttribute(expectedAttributeName));
|
||||
|
||||
this.repository.deleteById(toSave.getId()).block();
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId()).block()).isNull();
|
||||
}
|
||||
|
||||
@Test // gh-1399
|
||||
public void saveMultipleTimes() {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository
|
||||
.createSession().block();
|
||||
session.setAttribute("attribute1", "value1");
|
||||
Mono<Void> save1 = this.repository.save(session);
|
||||
session.setAttribute("attribute2", "value2");
|
||||
Mono<Void> save2 = this.repository.save(session);
|
||||
Mono.zip(save1, save2).block();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putAllOnSingleAttrDoesNotRemoveOld() {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
toSave.setAttribute("a", "b");
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
toSave = this.repository.findById(toSave.getId()).block();
|
||||
|
||||
toSave.setAttribute("1", "2");
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
toSave = this.repository.findById(toSave.getId()).block();
|
||||
|
||||
Session session = this.repository.findById(toSave.getId()).block();
|
||||
assertThat(session.getAttributeNames().size()).isEqualTo(2);
|
||||
assertThat(session.<String>getAttribute("a")).isEqualTo("b");
|
||||
assertThat(session.<String>getAttribute("1")).isEqualTo("2");
|
||||
|
||||
this.repository.deleteById(toSave.getId()).block();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenOnlyChangeId() throws Exception {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
toSave.setAttribute(attrName, attrValue);
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession findById = this.repository
|
||||
.findById(toSave.getId()).block();
|
||||
|
||||
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById).block();
|
||||
|
||||
assertThat(this.repository.findById(originalFindById).block()).isNull();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession findByChangeSessionId = this.repository
|
||||
.findById(changeSessionId).block();
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName))
|
||||
.isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenChangeTwice() throws Exception {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
String originalId = toSave.getId();
|
||||
String changeId1 = toSave.changeSessionId();
|
||||
String changeId2 = toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
assertThat(this.repository.findById(originalId).block()).isNull();
|
||||
assertThat(this.repository.findById(changeId1).block()).isNull();
|
||||
assertThat(this.repository.findById(changeId2).block()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenSetAttributeOnChangedSession() throws Exception {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession findById = this.repository
|
||||
.findById(toSave.getId()).block();
|
||||
|
||||
findById.setAttribute(attrName, attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById).block();
|
||||
|
||||
assertThat(this.repository.findById(originalFindById).block()).isNull();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession findByChangeSessionId = this.repository
|
||||
.findById(changeSessionId).block();
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName))
|
||||
.isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenHasNotSaved() throws Exception {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
String originalId = toSave.getId();
|
||||
toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId()).block()).isNotNull();
|
||||
assertThat(this.repository.findById(originalId).block()).isNull();
|
||||
}
|
||||
|
||||
// gh-954
|
||||
@Test
|
||||
public void changeSessionIdSaveTwice() {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
String originalId = toSave.getId();
|
||||
toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId()).block()).isNotNull();
|
||||
assertThat(this.repository.findById(originalId).block()).isNull();
|
||||
}
|
||||
|
||||
// gh-1111
|
||||
@Test
|
||||
public void changeSessionSaveOldSessionInstance() {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
String sessionId = toSave.getId();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository
|
||||
.findById(sessionId).block();
|
||||
session.changeSessionId();
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
this.repository.save(session).block();
|
||||
|
||||
toSave.setLastAccessedTime(Instant.now());
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.repository.save(toSave).block())
|
||||
.withMessage("Session was invalidated");
|
||||
|
||||
assertThat(this.repository.findById(sessionId).block()).isNull();
|
||||
assertThat(this.repository.findById(session.getId()).block()).isNotNull();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisWebSession
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,641 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.DefaultMessage;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.data.SessionEventRegistry;
|
||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDestroyedEvent;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests {
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
|
||||
|
||||
@Autowired
|
||||
private RedisOperationsSessionRepository repository;
|
||||
|
||||
@Autowired
|
||||
private SessionEventRegistry registry;
|
||||
|
||||
@SpringSessionRedisOperations
|
||||
private RedisOperations<Object, Object> redis;
|
||||
|
||||
private SecurityContext context;
|
||||
|
||||
private SecurityContext changedContext;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
if (this.registry != null) {
|
||||
this.registry.clear();
|
||||
}
|
||||
this.context = SecurityContextHolder.createEmptyContext();
|
||||
this.context.setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(),
|
||||
"na", AuthorityUtils.createAuthorityList("ROLE_USER")));
|
||||
|
||||
this.changedContext = SecurityContextHolder.createEmptyContext();
|
||||
this.changedContext.setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||
"changedContext-" + UUID.randomUUID(), "na",
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saves() throws InterruptedException {
|
||||
String username = "saves-" + System.currentTimeMillis();
|
||||
|
||||
String usernameSessionKey = "RedisOperationsSessionRepositoryITests:index:"
|
||||
+ INDEX_NAME + ":" + username;
|
||||
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
String expectedAttributeName = "a";
|
||||
String expectedAttributeValue = "b";
|
||||
toSave.setAttribute(expectedAttributeName, expectedAttributeValue);
|
||||
Authentication toSaveToken = new UsernamePasswordAuthenticationToken(username,
|
||||
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
|
||||
toSaveContext.setAuthentication(toSaveToken);
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, toSaveContext);
|
||||
toSave.setAttribute(INDEX_NAME, username);
|
||||
this.registry.clear();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.registry.receivedEvent(toSave.getId())).isTrue();
|
||||
assertThat(this.registry.<SessionCreatedEvent>getEvent(toSave.getId()))
|
||||
.isInstanceOf(SessionCreatedEvent.class);
|
||||
assertThat(this.redis.boundSetOps(usernameSessionKey).members())
|
||||
.contains(toSave.getId());
|
||||
|
||||
Session session = this.repository.findById(toSave.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(toSave.getId());
|
||||
assertThat(session.getAttributeNames()).isEqualTo(toSave.getAttributeNames());
|
||||
assertThat(session.<String>getAttribute(expectedAttributeName))
|
||||
.isEqualTo(toSave.getAttribute(expectedAttributeName));
|
||||
|
||||
this.registry.clear();
|
||||
|
||||
this.repository.deleteById(toSave.getId());
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId())).isNull();
|
||||
assertThat(this.registry.<SessionDestroyedEvent>getEvent(toSave.getId()))
|
||||
.isInstanceOf(SessionDestroyedEvent.class);
|
||||
assertThat(this.redis.boundSetOps(usernameSessionKey).members())
|
||||
.doesNotContain(toSave.getId());
|
||||
|
||||
assertThat(this.registry.getEvent(toSave.getId()).getSession()
|
||||
.<String>getAttribute(expectedAttributeName))
|
||||
.isEqualTo(expectedAttributeValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putAllOnSingleAttrDoesNotRemoveOld() {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute("a", "b");
|
||||
|
||||
this.repository.save(toSave);
|
||||
toSave = this.repository.findById(toSave.getId());
|
||||
|
||||
toSave.setAttribute("1", "2");
|
||||
|
||||
this.repository.save(toSave);
|
||||
toSave = this.repository.findById(toSave.getId());
|
||||
|
||||
Session session = this.repository.findById(toSave.getId());
|
||||
assertThat(session.getAttributeNames().size()).isEqualTo(2);
|
||||
assertThat(session.<String>getAttribute("a")).isEqualTo("b");
|
||||
assertThat(session.<String>getAttribute("1")).isEqualTo("2");
|
||||
|
||||
this.repository.deleteById(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByPrincipalName() throws Exception {
|
||||
String principalName = "findByPrincipalName" + UUID.randomUUID();
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(INDEX_NAME, principalName);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
|
||||
this.repository.deleteById(toSave.getId());
|
||||
assertThat(this.registry.receivedEvent(toSave.getId())).isTrue();
|
||||
|
||||
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
|
||||
principalName);
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(0);
|
||||
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByPrincipalNameExpireRemovesIndex() throws Exception {
|
||||
String principalName = "findByPrincipalNameExpireRemovesIndex"
|
||||
+ UUID.randomUUID();
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(INDEX_NAME, principalName);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
|
||||
+ toSave.getId();
|
||||
String channel = "__keyevent@0__:expired";
|
||||
DefaultMessage message = new DefaultMessage(
|
||||
channel.getBytes(StandardCharsets.UTF_8),
|
||||
body.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] pattern = new byte[] {};
|
||||
this.repository.onMessage(message, pattern);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(0);
|
||||
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByPrincipalNameNoPrincipalNameChange() throws Exception {
|
||||
String principalName = "findByPrincipalNameNoPrincipalNameChange"
|
||||
+ UUID.randomUUID();
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(INDEX_NAME, principalName);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
toSave.setAttribute("other", "value");
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByPrincipalNameNoPrincipalNameChangeReload() throws Exception {
|
||||
String principalName = "findByPrincipalNameNoPrincipalNameChangeReload"
|
||||
+ UUID.randomUUID();
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(INDEX_NAME, principalName);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
toSave = this.repository.findById(toSave.getId());
|
||||
|
||||
toSave.setAttribute("other", "value");
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByDeletedPrincipalName() throws Exception {
|
||||
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(INDEX_NAME, principalName);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
toSave.setAttribute(INDEX_NAME, null);
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
|
||||
|
||||
assertThat(findByPrincipalName).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByChangedPrincipalName() throws Exception {
|
||||
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
|
||||
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(INDEX_NAME, principalName);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
toSave.setAttribute(INDEX_NAME, principalNameChanged);
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
|
||||
assertThat(findByPrincipalName).isEmpty();
|
||||
|
||||
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
|
||||
principalNameChanged);
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByDeletedPrincipalNameReload() throws Exception {
|
||||
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(INDEX_NAME, principalName);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
RedisSession getSession = this.repository.findById(toSave.getId());
|
||||
getSession.setAttribute(INDEX_NAME, null);
|
||||
this.repository.save(getSession);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
|
||||
|
||||
assertThat(findByPrincipalName).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByChangedPrincipalNameReload() throws Exception {
|
||||
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
|
||||
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(INDEX_NAME, principalName);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
RedisSession getSession = this.repository.findById(toSave.getId());
|
||||
|
||||
getSession.setAttribute(INDEX_NAME, principalNameChanged);
|
||||
this.repository.save(getSession);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
|
||||
assertThat(findByPrincipalName).isEmpty();
|
||||
|
||||
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
|
||||
principalNameChanged);
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBySecurityPrincipalName() throws Exception {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
|
||||
this.repository.deleteById(toSave.getId());
|
||||
assertThat(this.registry.receivedEvent(toSave.getId())).isTrue();
|
||||
|
||||
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
|
||||
getSecurityName());
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(0);
|
||||
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBySecurityPrincipalNameExpireRemovesIndex() throws Exception {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
|
||||
+ toSave.getId();
|
||||
String channel = "__keyevent@0__:expired";
|
||||
DefaultMessage message = new DefaultMessage(
|
||||
channel.getBytes(StandardCharsets.UTF_8),
|
||||
body.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] pattern = new byte[] {};
|
||||
this.repository.onMessage(message, pattern);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(0);
|
||||
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByPrincipalNameNoSecurityPrincipalNameChange() throws Exception {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
toSave.setAttribute("other", "value");
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByPrincipalNameNoSecurityPrincipalNameChangeReload()
|
||||
throws Exception {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
toSave = this.repository.findById(toSave.getId());
|
||||
|
||||
toSave.setAttribute("other", "value");
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByDeletedSecurityPrincipalName() throws Exception {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, null);
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
|
||||
|
||||
assertThat(findByPrincipalName).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByChangedSecurityPrincipalName() throws Exception {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
|
||||
this.repository.save(toSave);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
|
||||
assertThat(findByPrincipalName).isEmpty();
|
||||
|
||||
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
|
||||
getChangedSecurityName());
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByDeletedSecurityPrincipalNameReload() throws Exception {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
RedisSession getSession = this.repository.findById(toSave.getId());
|
||||
getSession.setAttribute(INDEX_NAME, null);
|
||||
this.repository.save(getSession);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName());
|
||||
|
||||
assertThat(findByPrincipalName).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByChangedSecurityPrincipalNameReload() throws Exception {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
RedisSession getSession = this.repository.findById(toSave.getId());
|
||||
|
||||
getSession.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
|
||||
this.repository.save(getSession);
|
||||
|
||||
Map<String, RedisSession> findByPrincipalName = this.repository
|
||||
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
|
||||
assertThat(findByPrincipalName).isEmpty();
|
||||
|
||||
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
|
||||
getChangedSecurityName());
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenOnlyChangeId() throws Exception {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
toSave.setAttribute(attrName, attrValue);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
RedisSession findById = this.repository.findById(toSave.getId());
|
||||
|
||||
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById);
|
||||
|
||||
assertThat(this.repository.findById(originalFindById)).isNull();
|
||||
|
||||
RedisSession findByChangeSessionId = this.repository.findById(changeSessionId);
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenChangeTwice() throws Exception {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
String originalId = toSave.getId();
|
||||
String changeId1 = toSave.changeSessionId();
|
||||
String changeId2 = toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
assertThat(this.repository.findById(changeId1)).isNull();
|
||||
assertThat(this.repository.findById(changeId2)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenSetAttributeOnChangedSession() throws Exception {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
RedisSession findById = this.repository.findById(toSave.getId());
|
||||
|
||||
findById.setAttribute(attrName, attrValue);
|
||||
|
||||
String originalFindById = findById.getId();
|
||||
String changeSessionId = findById.changeSessionId();
|
||||
|
||||
this.repository.save(findById);
|
||||
|
||||
assertThat(this.repository.findById(originalFindById)).isNull();
|
||||
|
||||
RedisSession findByChangeSessionId = this.repository.findById(changeSessionId);
|
||||
|
||||
assertThat(findByChangeSessionId.<String>getAttribute(attrName)).isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenHasNotSaved() throws Exception {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
String originalId = toSave.getId();
|
||||
toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId())).isNotNull();
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
}
|
||||
|
||||
// gh-962
|
||||
@Test
|
||||
public void changeSessionIdSaveTwice() {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
String originalId = toSave.getId();
|
||||
toSave.changeSessionId();
|
||||
|
||||
this.repository.save(toSave);
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId())).isNotNull();
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
}
|
||||
|
||||
// gh-1137
|
||||
@Test
|
||||
public void changeSessionIdWhenSessionIsDeleted() {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
String sessionId = toSave.getId();
|
||||
this.repository.save(toSave);
|
||||
|
||||
this.repository.deleteById(sessionId);
|
||||
|
||||
toSave.changeSessionId();
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId())).isNull();
|
||||
assertThat(this.repository.findById(sessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test // gh-1270
|
||||
public void changeSessionIdSaveConcurrently() {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
String originalId = toSave.getId();
|
||||
this.repository.save(toSave);
|
||||
|
||||
RedisSession copy1 = this.repository.findById(originalId);
|
||||
RedisSession copy2 = this.repository.findById(originalId);
|
||||
|
||||
copy1.changeSessionId();
|
||||
this.repository.save(copy1);
|
||||
copy2.changeSessionId();
|
||||
this.repository.save(copy2);
|
||||
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
assertThat(this.repository.findById(copy1.getId())).isNotNull();
|
||||
assertThat(this.repository.findById(copy2.getId())).isNull();
|
||||
}
|
||||
|
||||
private String getSecurityName() {
|
||||
return this.context.getAuthentication().getName();
|
||||
}
|
||||
|
||||
private String getChangedSecurityName() {
|
||||
return this.changedContext.getAuthentication().getName();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisOperationsSessionRepositoryITests")
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
public SessionEventRegistry sessionEventRegistry() {
|
||||
return new SessionEventRegistry();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
import org.springframework.session.data.redis.SimpleRedisOperationsSessionRepository.RedisSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link SimpleRedisOperationsSessionRepository}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class SimpleRedisOperationsSessionRepositoryITests extends AbstractRedisITests {
|
||||
|
||||
@Autowired
|
||||
private SimpleRedisOperationsSessionRepository sessionRepository;
|
||||
|
||||
@Test
|
||||
void save_NewSession_ShouldSaveSession() {
|
||||
RedisSession session = createAndSaveSession(Instant.now());
|
||||
assertThat(session.getMaxInactiveInterval()).isEqualTo(
|
||||
Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
|
||||
assertThat(session.getAttributeNames())
|
||||
.isEqualTo(Collections.singleton("attribute1"));
|
||||
assertThat(session.<String>getAttribute("attribute1")).isEqualTo("value1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_LastAccessedTimeInPast_ShouldExpireSession() {
|
||||
assertThat(createAndSaveSession(Instant.EPOCH)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_DeletedSession_ShouldThrowException() {
|
||||
RedisSession session = createAndSaveSession(Instant.now());
|
||||
this.sessionRepository.deleteById(session.getId());
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.sessionRepository.save(session))
|
||||
.withMessage("Session was invalidated");
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_ConcurrentUpdates_ShouldSaveSession() {
|
||||
RedisSession copy1 = createAndSaveSession(Instant.now());
|
||||
String sessionId = copy1.getId();
|
||||
RedisSession copy2 = this.sessionRepository.findById(sessionId);
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.MILLIS);
|
||||
updateSession(copy1, now.plusSeconds(1L), "attribute2", "value2");
|
||||
this.sessionRepository.save(copy1);
|
||||
updateSession(copy2, now.plusSeconds(2L), "attribute3", "value3");
|
||||
this.sessionRepository.save(copy2);
|
||||
RedisSession session = this.sessionRepository.findById(sessionId);
|
||||
assertThat(session.getLastAccessedTime()).isEqualTo(now.plusSeconds(2L));
|
||||
assertThat(session.getAttributeNames()).hasSize(3);
|
||||
assertThat(session.<String>getAttribute("attribute1")).isEqualTo("value1");
|
||||
assertThat(session.<String>getAttribute("attribute2")).isEqualTo("value2");
|
||||
assertThat(session.<String>getAttribute("attribute3")).isEqualTo("value3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_ChangeSessionIdAndUpdateAttribute_ShouldChangeSessionId() {
|
||||
RedisSession session = createAndSaveSession(Instant.now());
|
||||
String originalSessionId = session.getId();
|
||||
updateSession(session, Instant.now(), "attribute1", "value2");
|
||||
String newSessionId = session.changeSessionId();
|
||||
this.sessionRepository.save(session);
|
||||
RedisSession loaded = this.sessionRepository.findById(newSessionId);
|
||||
assertThat(loaded).isNotNull();
|
||||
assertThat(loaded.getAttributeNames()).hasSize(1);
|
||||
assertThat(loaded.<String>getAttribute("attribute1")).isEqualTo("value2");
|
||||
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_OnlyChangeSessionId_ShouldChangeSessionId() {
|
||||
RedisSession session = createAndSaveSession(Instant.now());
|
||||
String originalSessionId = session.getId();
|
||||
String newSessionId = session.changeSessionId();
|
||||
this.sessionRepository.save(session);
|
||||
assertThat(this.sessionRepository.findById(newSessionId)).isNotNull();
|
||||
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_ChangeSessionIdTwice_ShouldChangeSessionId() {
|
||||
RedisSession session = createAndSaveSession(Instant.now());
|
||||
String originalSessionId = session.getId();
|
||||
updateSession(session, Instant.now(), "attribute1", "value2");
|
||||
String newSessionId1 = session.changeSessionId();
|
||||
updateSession(session, Instant.now(), "attribute1", "value3");
|
||||
String newSessionId2 = session.changeSessionId();
|
||||
this.sessionRepository.save(session);
|
||||
assertThat(this.sessionRepository.findById(newSessionId1)).isNull();
|
||||
assertThat(this.sessionRepository.findById(newSessionId2)).isNotNull();
|
||||
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_ChangeSessionIdOnNewSession_ShouldChangeSessionId() {
|
||||
RedisSession session = this.sessionRepository.createSession();
|
||||
String originalSessionId = session.getId();
|
||||
updateSession(session, Instant.now(), "attribute1", "value1");
|
||||
String newSessionId = session.changeSessionId();
|
||||
this.sessionRepository.save(session);
|
||||
assertThat(this.sessionRepository.findById(newSessionId)).isNotNull();
|
||||
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_ChangeSessionIdSaveTwice_ShouldChangeSessionId() {
|
||||
RedisSession session = createAndSaveSession(Instant.now());
|
||||
String originalSessionId;
|
||||
originalSessionId = session.getId();
|
||||
updateSession(session, Instant.now(), "attribute1", "value1");
|
||||
String newSessionId = session.changeSessionId();
|
||||
this.sessionRepository.save(session);
|
||||
this.sessionRepository.save(session);
|
||||
assertThat(this.sessionRepository.findById(newSessionId)).isNotNull();
|
||||
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_ChangeSessionIdOnDeletedSession_ShouldThrowException() {
|
||||
RedisSession session = createAndSaveSession(Instant.now());
|
||||
String originalSessionId = session.getId();
|
||||
this.sessionRepository.deleteById(originalSessionId);
|
||||
updateSession(session, Instant.now(), "attribute1", "value1");
|
||||
String newSessionId = session.changeSessionId();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.sessionRepository.save(session))
|
||||
.withMessage("Session was invalidated");
|
||||
assertThat(this.sessionRepository.findById(newSessionId)).isNull();
|
||||
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_ChangeSessionIdConcurrent_ShouldThrowException() {
|
||||
RedisSession copy1 = createAndSaveSession(Instant.now());
|
||||
String originalSessionId = copy1.getId();
|
||||
RedisSession copy2 = this.sessionRepository.findById(originalSessionId);
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.MILLIS);
|
||||
updateSession(copy1, now.plusSeconds(1L), "attribute2", "value2");
|
||||
String newSessionId1 = copy1.changeSessionId();
|
||||
this.sessionRepository.save(copy1);
|
||||
updateSession(copy2, now.plusSeconds(2L), "attribute3", "value3");
|
||||
String newSessionId2 = copy2.changeSessionId();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.sessionRepository.save(copy2))
|
||||
.withMessage("Session was invalidated");
|
||||
assertThat(this.sessionRepository.findById(newSessionId1)).isNotNull();
|
||||
assertThat(this.sessionRepository.findById(newSessionId2)).isNull();
|
||||
assertThat(this.sessionRepository.findById(originalSessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteById_ValidSession_ShouldDeleteSession() {
|
||||
RedisSession session = createAndSaveSession(Instant.now());
|
||||
this.sessionRepository.deleteById(session.getId());
|
||||
assertThat(this.sessionRepository.findById(session.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteById_DeletedSession_ShouldDoNothing() {
|
||||
RedisSession session = createAndSaveSession(Instant.now());
|
||||
this.sessionRepository.deleteById(session.getId());
|
||||
this.sessionRepository.deleteById(session.getId());
|
||||
assertThat(this.sessionRepository.findById(session.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteById_NonexistentSession_ShouldDoNothing() {
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
this.sessionRepository.deleteById(sessionId);
|
||||
assertThat(this.sessionRepository.findById(sessionId)).isNull();
|
||||
}
|
||||
|
||||
private RedisSession createAndSaveSession(Instant lastAccessedTime) {
|
||||
RedisSession session = this.sessionRepository.createSession();
|
||||
session.setLastAccessedTime(lastAccessedTime);
|
||||
session.setAttribute("attribute1", "value1");
|
||||
this.sessionRepository.save(session);
|
||||
return this.sessionRepository.findById(session.getId());
|
||||
}
|
||||
|
||||
private static void updateSession(RedisSession session, Instant lastAccessedTime,
|
||||
String attributeName, Object attributeValue) {
|
||||
session.setLastAccessedTime(lastAccessedTime);
|
||||
session.setAttribute(attributeName, attributeValue);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableSpringHttpSession
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
public SimpleRedisOperationsSessionRepository sessionRepository(
|
||||
RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return new SimpleRedisOperationsSessionRepository(redisTemplate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis.config.annotation.web.http;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.data.redis.AbstractRedisITests;
|
||||
import org.springframework.session.events.SessionExpiredEvent;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Session>
|
||||
extends AbstractRedisITests {
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> repository;
|
||||
|
||||
@Autowired
|
||||
private SessionExpiredEventRegistry registry;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.registry.setLock(this.lock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expireFiresSessionExpiredEvent() throws InterruptedException {
|
||||
S toSave = this.repository.createSession();
|
||||
toSave.setAttribute("a", "b");
|
||||
Authentication toSaveToken = new UsernamePasswordAuthenticationToken("user",
|
||||
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContext toSaveContext = SecurityContextHolder.createEmptyContext();
|
||||
toSaveContext.setAuthentication(toSaveToken);
|
||||
toSave.setAttribute("SPRING_SECURITY_CONTEXT", toSaveContext);
|
||||
|
||||
this.repository.save(toSave);
|
||||
|
||||
synchronized (this.lock) {
|
||||
this.lock.wait(toSave.getMaxInactiveInterval().plusMillis(1).toMillis());
|
||||
}
|
||||
if (!this.registry.receivedEvent()) {
|
||||
// Redis makes no guarantees on when an expired event will be fired
|
||||
// we can ensure it gets fired by trying to get the session
|
||||
this.repository.findById(toSave.getId());
|
||||
synchronized (this.lock) {
|
||||
if (!this.registry.receivedEvent()) {
|
||||
// wait at most a minute
|
||||
this.lock.wait(TimeUnit.MINUTES.toMillis(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
assertThat(this.registry.receivedEvent()).isTrue();
|
||||
}
|
||||
|
||||
static class SessionExpiredEventRegistry
|
||||
implements ApplicationListener<SessionExpiredEvent> {
|
||||
private boolean receivedEvent;
|
||||
private Object lock;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(SessionExpiredEvent event) {
|
||||
synchronized (this.lock) {
|
||||
this.receivedEvent = true;
|
||||
this.lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean receivedEvent() {
|
||||
return this.receivedEvent;
|
||||
}
|
||||
|
||||
public void setLock(Object lock) {
|
||||
this.lock = lock;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1)
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
public SessionExpiredEventRegistry sessionDestroyedEventRegistry() {
|
||||
return new SessionExpiredEventRegistry();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis.flushimmediately;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.data.redis.AbstractRedisITests;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class RedisOperationsSessionRepositoryFlushImmediatelyITests<S extends Session>
|
||||
extends AbstractRedisITests {
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> sessionRepository;
|
||||
|
||||
@Test
|
||||
public void savesOnCreate() throws InterruptedException {
|
||||
S created = this.sessionRepository.createSession();
|
||||
|
||||
S getSession = this.sessionRepository.findById(created.getId());
|
||||
|
||||
assertThat(getSession).isNotNull();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE)
|
||||
static class RedisHttpSessionConfig extends BaseConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.data.redis.taskexecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.data.redis.core.BoundSetOperations;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.session.data.redis.AbstractRedisITests;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Vladimir Tsanev
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class RedisListenerContainerTaskExecutorITests extends AbstractRedisITests {
|
||||
|
||||
@Autowired
|
||||
private SessionTaskExecutor executor;
|
||||
|
||||
@SpringSessionRedisOperations
|
||||
private RedisOperations<Object, Object> redis;
|
||||
|
||||
@Test
|
||||
public void testRedisDelEventsAreDispatchedInSessionTaskExecutor()
|
||||
throws InterruptedException {
|
||||
BoundSetOperations<Object, Object> ops = this.redis.boundSetOps(
|
||||
"spring:session:RedisListenerContainerTaskExecutorITests:expirations:dummy");
|
||||
ops.add("value");
|
||||
ops.remove("value");
|
||||
assertThat(this.executor.taskDispatched()).isTrue();
|
||||
|
||||
}
|
||||
|
||||
static class SessionTaskExecutor implements TaskExecutor {
|
||||
private Object lock = new Object();
|
||||
|
||||
private final Executor executor;
|
||||
|
||||
private Boolean taskDispatched;
|
||||
|
||||
SessionTaskExecutor(Executor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
synchronized (this.lock) {
|
||||
try {
|
||||
this.executor.execute(task);
|
||||
}
|
||||
finally {
|
||||
this.taskDispatched = true;
|
||||
this.lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean taskDispatched() throws InterruptedException {
|
||||
if (this.taskDispatched != null) {
|
||||
return this.taskDispatched;
|
||||
}
|
||||
synchronized (this.lock) {
|
||||
this.lock.wait(TimeUnit.SECONDS.toMillis(1));
|
||||
}
|
||||
return (this.taskDispatched != null) ? this.taskDispatched : Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests")
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
public Executor springSessionRedisTaskExecutor() {
|
||||
return new SessionTaskExecutor(Executors.newSingleThreadExecutor());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Executor springSessionRedisSubscriptionExecutor() {
|
||||
return new SimpleAsyncTaskExecutor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ryuk.container.timeout=120
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user