Compare commits
817
Commits
2.0.0.CR1
...
2.3.0.Final
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
046c670d76 | ||
|
|
cefb81fbca | ||
|
|
d598e1de1e | ||
|
|
ab09b3d1ae | ||
|
|
68e0f56dd2 | ||
|
|
2b5e850ffb | ||
|
|
06bc99d52a | ||
|
|
05c931773e | ||
|
|
bc4728a76b | ||
|
|
f5bd3af14e | ||
|
|
5877d265b4 | ||
|
|
bd6efc3838 | ||
|
|
72d38779ab | ||
|
|
839c4e8ede | ||
|
|
9c7e9da9fa | ||
|
|
3abb44c1da | ||
|
|
584d2e700d | ||
|
|
acbb17a331 | ||
|
|
9f5acccc4f | ||
|
|
e39d28517c | ||
|
|
8b5d171cb5 | ||
|
|
cddacab297 | ||
|
|
dfc09b69a8 | ||
|
|
d2e0432afb | ||
|
|
34d80c9083 | ||
|
|
06c08a9cff | ||
|
|
9f00f693c6 | ||
|
|
1f00625930 | ||
|
|
58d4d0596e | ||
|
|
ad59cd618e | ||
|
|
ac80f99e8c | ||
|
|
cdf7dd3a6c | ||
|
|
97dd10883c | ||
|
|
e6de2e7792 | ||
|
|
7f825eb415 | ||
|
|
a7f2130eae | ||
|
|
85db8ea44f | ||
|
|
2acea2b2ee | ||
|
|
2391a00aa5 | ||
|
|
934ea1c33c | ||
|
|
84bf546911 | ||
|
|
473bd5fc78 | ||
|
|
9d098e9068 | ||
|
|
8c1e5c5aae | ||
|
|
af5e8f7b09 | ||
|
|
681cb39862 | ||
|
|
7dee39bbaa | ||
|
|
e8ce9704c1 | ||
|
|
ffce2023c0 | ||
|
|
320110abaa | ||
|
|
f9a8f805c8 | ||
|
|
3779bfb6b4 | ||
|
|
4136d76b7e | ||
|
|
964cd50f1d | ||
|
|
61fa152e62 | ||
|
|
7f04dd20b3 | ||
|
|
9b5277d83d | ||
|
|
b2ea49212c | ||
|
|
bd8e435164 | ||
|
|
f8f9ebf588 | ||
|
|
79f53c5513 | ||
|
|
c2692cc0ac | ||
|
|
fdb8c04ac9 | ||
|
|
46e32b36d2 | ||
|
|
36c367a3bc | ||
|
|
1b24d2edd8 | ||
|
|
32df5225cf | ||
|
|
116f5f5795 | ||
|
|
bbc1d26b72 | ||
|
|
6f43c1f09f | ||
|
|
3c8aaaa61f | ||
|
|
e2b42b24d0 | ||
|
|
4efe12cb93 | ||
|
|
49d3d3f6e3 | ||
|
|
53fcc1bec2 | ||
|
|
490b7f6ba4 | ||
|
|
218065a349 | ||
|
|
98aa516d74 | ||
|
|
24715eabec | ||
|
|
f2f508ac2e | ||
|
|
13220e1d38 | ||
|
|
116027bd7b | ||
|
|
14a51e589d | ||
|
|
36433bb8ff | ||
|
|
edb66d6fd3 | ||
|
|
a0bbb1e7a2 | ||
|
|
a33997976f | ||
|
|
3250562d28 | ||
|
|
0a8d1e28f1 | ||
|
|
29538332d9 | ||
|
|
d941e07169 | ||
|
|
ba1fa34c56 | ||
|
|
5cef34d61a | ||
|
|
a87c08416d | ||
|
|
4f695cfe76 | ||
|
|
e41d11877f | ||
|
|
3c08153e33 | ||
|
|
c9507aa5c7 | ||
|
|
b62e6e2751 | ||
|
|
74dad004e3 | ||
|
|
2199df71bf | ||
|
|
4182e4d92a | ||
|
|
1583937295 | ||
|
|
940237ee78 | ||
|
|
3986ce2ce0 | ||
|
|
4b56743788 | ||
|
|
658988ccd0 | ||
|
|
a7287aad36 | ||
|
|
c3b577de11 | ||
|
|
c912f941e7 | ||
|
|
041413d8de | ||
|
|
386bf8d39e | ||
|
|
2ea93b8a8b | ||
|
|
dfcd598912 | ||
|
|
e157a60a23 | ||
|
|
2fd680092a | ||
|
|
37affb1e9c | ||
|
|
9df3091b22 | ||
|
|
728e68ede5 | ||
|
|
4cbc665592 | ||
|
|
d295ec709b | ||
|
|
90097e9aea | ||
|
|
07dcfeb330 | ||
|
|
3feec3ee68 | ||
|
|
f66eb8b1ef | ||
|
|
00879b39b7 | ||
|
|
77499be8d2 | ||
|
|
4ce5f9e087 | ||
|
|
f62e66681e | ||
|
|
06ad1537c8 | ||
|
|
64339aaca7 | ||
|
|
9193142bb9 | ||
|
|
2ed6067de0 | ||
|
|
98a9c50d53 | ||
|
|
144898c0d2 | ||
|
|
3b2f7630bb | ||
|
|
7c09b0c7b4 | ||
|
|
af508232dc | ||
|
|
d22f45f0d2 | ||
|
|
cefb1742a1 | ||
|
|
b320eb8fc7 | ||
|
|
5d836fefc5 | ||
|
|
dc13c701f5 | ||
|
|
e61191edb5 | ||
|
|
95f62c6aeb | ||
|
|
160e26b699 | ||
|
|
4593d89a05 | ||
|
|
8ffb1ecc7d | ||
|
|
a9ce6b9f81 | ||
|
|
c62c60c723 | ||
|
|
5d57c29fae | ||
|
|
c2684a2b36 | ||
|
|
5732b2c58f | ||
|
|
b0448d1b6f | ||
|
|
4a19d4cdc1 | ||
|
|
acdc33b2fa | ||
|
|
8a7983bf5a | ||
|
|
18e0c0277f | ||
|
|
8bdd8f4274 | ||
|
|
1c0abbd722 | ||
|
|
7a6324e02c | ||
|
|
f256e2b102 | ||
|
|
3d47ab3665 | ||
|
|
e08725e730 | ||
|
|
422805b511 | ||
|
|
1f3d29964f | ||
|
|
8c8a39c833 | ||
|
|
0938390654 | ||
|
|
635e20a510 | ||
|
|
4e245d428c | ||
|
|
cec13dae1f | ||
|
|
2d0866118e | ||
|
|
3bbbf30e64 | ||
|
|
e344dddf4b | ||
|
|
6578ce73a1 | ||
|
|
a006e1fad1 | ||
|
|
d2cae0f8c3 | ||
|
|
1e51952006 | ||
|
|
f868aa7fbe | ||
|
|
fb99f673fc | ||
|
|
9dbd1149cf | ||
|
|
455db1add4 | ||
|
|
b6e0cb374d | ||
|
|
bc2eb2b9ea | ||
|
|
fbaa731dfa | ||
|
|
03cf9bad2e | ||
|
|
f5a5fc3458 | ||
|
|
0e33e4035f | ||
|
|
c4f0053dd9 | ||
|
|
2b589186ed | ||
|
|
4bd38bb9c1 | ||
|
|
ac245d3c4b | ||
|
|
a1fd85c52a | ||
|
|
06dcf99b42 | ||
|
|
6acd9cdf1d | ||
|
|
68ca1849bf | ||
|
|
7413f46e1b | ||
|
|
db05dc6ee4 | ||
|
|
fbb65fa072 | ||
|
|
0b5bcad5f6 | ||
|
|
74325fe133 | ||
|
|
42e93742fa | ||
|
|
cfbc9cf14b | ||
|
|
67d3730c6d | ||
|
|
36aa3da3d0 | ||
|
|
c5600e888d | ||
|
|
38d1dea65e | ||
|
|
d653437830 | ||
|
|
b5a1b0bc50 | ||
|
|
a89dbabc92 | ||
|
|
4af0976194 | ||
|
|
46038b457c | ||
|
|
7af125247c | ||
|
|
442e337668 | ||
|
|
e31c2b92a9 | ||
|
|
d27a23cbe3 | ||
|
|
b1e5bc8a30 | ||
|
|
bc916a1909 | ||
|
|
f8c236afa8 | ||
|
|
c32cf51808 | ||
|
|
a60dd48300 | ||
|
|
90b5ad5bb3 | ||
|
|
cab9740221 | ||
|
|
5b1b63e364 | ||
|
|
e2094065b2 | ||
|
|
abb0bbd815 | ||
|
|
9b10110dcb | ||
|
|
ab8803a5d6 | ||
|
|
c15a425317 | ||
|
|
81e773688f | ||
|
|
d71fadabeb | ||
|
|
7447ca7b58 | ||
|
|
0f9798a10d | ||
|
|
d12f157299 | ||
|
|
f1156a49cf | ||
|
|
c852d6d817 | ||
|
|
7d038e72c9 | ||
|
|
654601c92c | ||
|
|
a743600b34 | ||
|
|
d4c3fae546 | ||
|
|
6a4e413bf4 | ||
|
|
b0da7dc72f | ||
|
|
110f6ad549 | ||
|
|
f9a0abcfc4 | ||
|
|
6e00587468 | ||
|
|
5093dd94e2 | ||
|
|
550ec2ff51 | ||
|
|
ab0c075242 | ||
|
|
bcf5ac56cd | ||
|
|
aa1ad029bb | ||
|
|
e27cbac0b5 | ||
|
|
5d34b7e682 | ||
|
|
69f66edb5f | ||
|
|
eed71a4b84 | ||
|
|
8967ca4066 | ||
|
|
a5b5e78eba | ||
|
|
a58c985934 | ||
|
|
34f62eb31d | ||
|
|
afee7d7403 | ||
|
|
d0eb26b8bb | ||
|
|
d65cc830eb | ||
|
|
6c3479665c | ||
|
|
ee2daeb364 | ||
|
|
fc6d6ff7f7 | ||
|
|
98d2fe15e8 | ||
|
|
af49d184a2 | ||
|
|
ecc104719d | ||
|
|
e8fe9bce11 | ||
|
|
3b455d1735 | ||
|
|
103fa975a1 | ||
|
|
9ee58909ab | ||
|
|
6f5dadedd5 | ||
|
|
033d1f564a | ||
|
|
bb7080584d | ||
|
|
370a6c1631 | ||
|
|
27e86e36c4 | ||
|
|
ff1326fe35 | ||
|
|
5fc7149aac | ||
|
|
a1bcd0651d | ||
|
|
22aaa4cb52 | ||
|
|
04f05c0cd1 | ||
|
|
8e65356891 | ||
|
|
f91c517a9c | ||
|
|
7209a95dce | ||
|
|
98bf095471 | ||
|
|
33963f8aa7 | ||
|
|
a9a759c4dc | ||
|
|
06c48a2830 | ||
|
|
affc62460c | ||
|
|
cd3cd4f506 | ||
|
|
8e113384aa | ||
|
|
59eec8d5b4 | ||
|
|
0ab53c5d88 | ||
|
|
4977527f60 | ||
|
|
0addfeaa75 | ||
|
|
c6a6848483 | ||
|
|
9d0c9046ad | ||
|
|
992268a8e6 | ||
|
|
5514a0c968 | ||
|
|
df1995636a | ||
|
|
ba0d71704d | ||
|
|
f69eb5503b | ||
|
|
9bc80cc323 | ||
|
|
39fa49f765 | ||
|
|
80cc9b0585 | ||
|
|
9f4cf07489 | ||
|
|
47b742a16e | ||
|
|
c26471faa8 | ||
|
|
4b1b3a0dda | ||
|
|
6bdc9dc133 | ||
|
|
12919223cf | ||
|
|
b32b222b02 | ||
|
|
44c47431a1 | ||
|
|
b2fd429749 | ||
|
|
bd2f220736 | ||
|
|
0348e427de | ||
|
|
b97908fb02 | ||
|
|
1aeec2a83c | ||
|
|
05792516f6 | ||
|
|
5afe93552a | ||
|
|
036b24378f | ||
|
|
bde45eaa07 | ||
|
|
f9b1cd191d | ||
|
|
8c518a8d38 | ||
|
|
6d5dc673d4 | ||
|
|
55e07bcde2 | ||
|
|
868f8b166f | ||
|
|
4b3d3bf55b | ||
|
|
cb1b34eee5 | ||
|
|
eb80d59cd8 | ||
|
|
04d03452bd | ||
|
|
a88e975a3a | ||
|
|
bf6246f5c1 | ||
|
|
d31d2859ae | ||
|
|
44f57b8273 | ||
|
|
5eb36f4e0f | ||
|
|
05ae84d533 | ||
|
|
59674e44bf | ||
|
|
efaa88b4e3 | ||
|
|
f55a526ebb | ||
|
|
fa02277e6e | ||
|
|
4780a3453a | ||
|
|
ccb9433e5c | ||
|
|
9d79a847c8 | ||
|
|
1630b9a20c | ||
|
|
65befb16fd | ||
|
|
e8f99a2109 | ||
|
|
7af16fc747 | ||
|
|
84f5c0926b | ||
|
|
11245701d2 | ||
|
|
5c45c5e6e4 | ||
|
|
04ee5ca7c1 | ||
|
|
df5d37a8d9 | ||
|
|
55a86dc0f4 | ||
|
|
fa8f60a5f0 | ||
|
|
2a5c778af5 | ||
|
|
f24d73e8d6 | ||
|
|
6d40e0dd07 | ||
|
|
529ef3a425 | ||
|
|
36bb94afb8 | ||
|
|
4fd0238ca9 | ||
|
|
76e1160b36 | ||
|
|
df3079852e | ||
|
|
71b648a9b6 | ||
|
|
ab7bb21991 | ||
|
|
348e0b6226 | ||
|
|
44b7210af7 | ||
|
|
f726caea9b | ||
|
|
16282aeb7b | ||
|
|
d2c546bdc2 | ||
|
|
1f27fc9e4b | ||
|
|
7c292b1213 | ||
|
|
3b9a6b32e1 | ||
|
|
b0bb0d36ab | ||
|
|
cdda19d1f8 | ||
|
|
759747ab5c | ||
|
|
01e48dc4b8 | ||
|
|
61b93c318f | ||
|
|
3f35234cf5 | ||
|
|
da135389c7 | ||
|
|
b3d7d48ec8 | ||
|
|
5a015a6518 | ||
|
|
7912bd4dae | ||
|
|
c860d03a60 | ||
|
|
1b2a5eda32 | ||
|
|
517413d38e | ||
|
|
fd25dbcb5c | ||
|
|
bdbc4e1f6d | ||
|
|
eebd496c2f | ||
|
|
15d31a202f | ||
|
|
6714c1a136 | ||
|
|
ca6d1f3c48 | ||
|
|
c5a149a892 | ||
|
|
b33648cc29 | ||
|
|
4a583712b8 | ||
|
|
5d20651c66 | ||
|
|
a49a288bbe | ||
|
|
fbace351b3 | ||
|
|
fbb2dfcf59 | ||
|
|
05813832fc | ||
|
|
e67f8c7a61 | ||
|
|
7a66b055be | ||
|
|
e3ddc5a639 | ||
|
|
8c5b1e4892 | ||
|
|
ad1d3adb9e | ||
|
|
e18f3edbcd | ||
|
|
6ede760725 | ||
|
|
db0f05fb86 | ||
|
|
03c05bd72b | ||
|
|
c867fcf9e7 | ||
|
|
22e85b11eb | ||
|
|
39fe439573 | ||
|
|
fc6b7ea8ee | ||
|
|
0e1edb3d7a | ||
|
|
7a77055e58 | ||
|
|
d9e95455a2 | ||
|
|
f5b4efc080 | ||
|
|
b1a233e41f | ||
|
|
4d6e656b02 | ||
|
|
3354e7c98e | ||
|
|
05fe17be4f | ||
|
|
2e9f14dfd8 | ||
|
|
404aa69adb | ||
|
|
a24a43c4be | ||
|
|
892d5fd1b7 | ||
|
|
258f7c8d60 | ||
|
|
b17bd9e660 | ||
|
|
43d23835d0 | ||
|
|
d11efa363c | ||
|
|
8f0afb2551 | ||
|
|
c874f96228 | ||
|
|
ce78cc1d1c | ||
|
|
599c69a2a3 | ||
|
|
9daced953e | ||
|
|
1b085d3e13 | ||
|
|
1bea93b1e1 | ||
|
|
16bd2fb8f8 | ||
|
|
e8efe10fc8 | ||
|
|
73bcfba5f3 | ||
|
|
02f28a7e8e | ||
|
|
5a4bb5f3f0 | ||
|
|
235623301e | ||
|
|
f4aee129e4 | ||
|
|
a7f9a6e095 | ||
|
|
539d9863aa | ||
|
|
4f51b7b34c | ||
|
|
0e5af3710e | ||
|
|
a462dd78bb | ||
|
|
ec5289b5c8 | ||
|
|
2a29f2a9c6 | ||
|
|
9ffd6548ce | ||
|
|
cfd41fbc8b | ||
|
|
4c2cca4672 | ||
|
|
53557d9cfd | ||
|
|
a0054bd77f | ||
|
|
099de9e6e3 | ||
|
|
cfd61dc359 | ||
|
|
99f453b25c | ||
|
|
d138b19adb | ||
|
|
345163994a | ||
|
|
09090cec24 | ||
|
|
3abcf713e5 | ||
|
|
e4d97485ec | ||
|
|
1489bd734a | ||
|
|
ef442cae92 | ||
|
|
3493aa4ab7 | ||
|
|
3be47dee37 | ||
|
|
494d9973a6 | ||
|
|
e28a3750cd | ||
|
|
014c8e068f | ||
|
|
3890867383 | ||
|
|
d499b8a489 | ||
|
|
352602da39 | ||
|
|
c333d1eaa9 | ||
|
|
c522a20ab9 | ||
|
|
ee66cb51da | ||
|
|
93d65fa895 | ||
|
|
6aafde7819 | ||
|
|
365a1b66c8 | ||
|
|
fa1fb3a3a9 | ||
|
|
b109ce14b0 | ||
|
|
561661d726 | ||
|
|
6592014707 | ||
|
|
4cd0a8e894 | ||
|
|
6c44e9befa | ||
|
|
8e9cfc4a28 | ||
|
|
d20d5ce2d7 | ||
|
|
243c8cc2cd | ||
|
|
7d444c8b13 | ||
|
|
be83e2fb6b | ||
|
|
b34acd4cc2 | ||
|
|
85ac784fd0 | ||
|
|
ab2368be34 | ||
|
|
ba076ad159 | ||
|
|
a8d2b810cf | ||
|
|
285a99d903 | ||
|
|
fd983690e2 | ||
|
|
006a0f856e | ||
|
|
bfe10e34e7 | ||
|
|
2cba13db9c | ||
|
|
3eb9134e02 | ||
|
|
b7f2e0b5ff | ||
|
|
27187c11f1 | ||
|
|
0030df060b | ||
|
|
f6f587e472 | ||
|
|
841c6b619a | ||
|
|
e0d70a35d6 | ||
|
|
0520d465c1 | ||
|
|
a8fb988e31 | ||
|
|
c34d175572 | ||
|
|
9c7442c75b | ||
|
|
de888fc8a1 | ||
|
|
4d8e19eb7c | ||
|
|
2df7d6252e | ||
|
|
d52e043322 | ||
|
|
6c8d55d534 | ||
|
|
26bc07b2c4 | ||
|
|
7f6fc170dc | ||
|
|
741bbf471f | ||
|
|
70eb27ec83 | ||
|
|
96b729886f | ||
|
|
530870f05e | ||
|
|
1633f062d5 | ||
|
|
ff703f935f | ||
|
|
4974079794 | ||
|
|
d94515cdae | ||
|
|
f838c697d1 | ||
|
|
d14de43ac7 | ||
|
|
e198e7e0b9 | ||
|
|
83306963e8 | ||
|
|
65e2f127c9 | ||
|
|
64c2077c0b | ||
|
|
9be6777685 | ||
|
|
f14f303dfe | ||
|
|
33d7d89ad9 | ||
|
|
65c49c39f4 | ||
|
|
6c0f685c39 | ||
|
|
e0a59baaf2 | ||
|
|
6a797a61a9 | ||
|
|
e49afb2d83 | ||
|
|
0d225f44de | ||
|
|
586f6eeece | ||
|
|
f91907c8f9 | ||
|
|
9e0667bc11 | ||
|
|
a3ffb32532 | ||
|
|
1e7cf9fb3f | ||
|
|
f75c0d3e61 | ||
|
|
534ee2e50c | ||
|
|
70722d0d3d | ||
|
|
0292acd5e2 | ||
|
|
bf3541414c | ||
|
|
9242e972c0 | ||
|
|
57a96fa6aa | ||
|
|
7f08717dfb | ||
|
|
9365a40426 | ||
|
|
ae58d64741 | ||
|
|
4a8b4bcdec | ||
|
|
e3aec098a2 | ||
|
|
3b9b673e5e | ||
|
|
17e75950fe | ||
|
|
1c75b03e59 | ||
|
|
27814dab65 | ||
|
|
b3a55df1e7 | ||
|
|
7ec3813a07 | ||
|
|
62b96143a1 | ||
|
|
ec6b81e42d | ||
|
|
3a5d4a3723 | ||
|
|
1d695237b7 | ||
|
|
09693eb108 | ||
|
|
3b3368eead | ||
|
|
a5cbe113fd | ||
|
|
9688880837 | ||
|
|
98be930027 | ||
|
|
83b4495e69 | ||
|
|
bd73b0aee6 | ||
|
|
ae1a7542d8 | ||
|
|
7d1bdf6e81 | ||
|
|
d64e7394a7 | ||
|
|
059011e82f | ||
|
|
eaf28f72c1 | ||
|
|
aa177dd9f9 | ||
|
|
a8a77add39 | ||
|
|
5facec73e4 | ||
|
|
91a267a0d8 | ||
|
|
0147fbe489 | ||
|
|
a9ccabdce5 | ||
|
|
e04ad9d7b6 | ||
|
|
c5d40a76ff | ||
|
|
79c7203fe8 | ||
|
|
0d99b797b6 | ||
|
|
59f228be2b | ||
|
|
159b752fb0 | ||
|
|
b0c7746eeb | ||
|
|
bbeac6d741 | ||
|
|
a8eb3ca65a | ||
|
|
fea277a7f5 | ||
|
|
cd94778dec | ||
|
|
bd5b434894 | ||
|
|
26f0f6509a | ||
|
|
7983ed064f | ||
|
|
607b305c2f | ||
|
|
8ad3c3fb03 | ||
|
|
824890323d | ||
|
|
5011960cac | ||
|
|
3c8ed8e3d8 | ||
|
|
e6ce2e138d | ||
|
|
8cfa50f134 | ||
|
|
2b3859f462 | ||
|
|
5d9fe09599 | ||
|
|
2219cd363e | ||
|
|
b08800fbd8 | ||
|
|
7e1b97888a | ||
|
|
322597b420 | ||
|
|
bd499e1c55 | ||
|
|
46b4bb0909 | ||
|
|
59e0570cdf | ||
|
|
c925033944 | ||
|
|
94f583e935 | ||
|
|
69f051fb0a | ||
|
|
3973aed57d | ||
|
|
73d3db6d00 | ||
|
|
535a0763fc | ||
|
|
283581f920 | ||
|
|
488dee71d2 | ||
|
|
b6811b96b9 | ||
|
|
db852f7e11 | ||
|
|
01830fd7f3 | ||
|
|
00d439b6f0 | ||
|
|
06fca9c897 | ||
|
|
72d134748c | ||
|
|
e2ad7608c8 | ||
|
|
a73dd537f3 | ||
|
|
9169bcd88d | ||
|
|
94af2e1789 | ||
|
|
56e011dce4 | ||
|
|
079026a7be | ||
|
|
7c6cf8f3b7 | ||
|
|
981ff90c81 | ||
|
|
484d5d6e08 | ||
|
|
0315bd0b87 | ||
|
|
2a05f00d0c | ||
|
|
05a804b972 | ||
|
|
f4ddfe4a52 | ||
|
|
76c4fbb241 | ||
|
|
3b0fd71155 | ||
|
|
6f92bac782 | ||
|
|
8b535c5da6 | ||
|
|
e719722e32 | ||
|
|
c709598fdd | ||
|
|
3ef0527a91 | ||
|
|
eb12a307f7 | ||
|
|
f36a68308d | ||
|
|
7571dc07f9 | ||
|
|
e03bf6eef6 | ||
|
|
0e470d75e9 | ||
|
|
187f4a49ad | ||
|
|
0a5e061ca6 | ||
|
|
1b517a461e | ||
|
|
a913b8a158 | ||
|
|
f574173ed3 | ||
|
|
a6bdf81e6d | ||
|
|
a42e4af78d | ||
|
|
b1ae0a9000 | ||
|
|
07d7141d98 | ||
|
|
0632bc8939 | ||
|
|
e708c53730 | ||
|
|
243105f952 | ||
|
|
1ce17c459d | ||
|
|
daa4d23ac0 | ||
|
|
30d651bcee | ||
|
|
fda0a79e27 | ||
|
|
021c2ec701 | ||
|
|
fbf00ba1cd | ||
|
|
a2ead8743f | ||
|
|
5a0b86f814 | ||
|
|
970c89dd6a | ||
|
|
f9bfb5d3e5 | ||
|
|
f384c2340e | ||
|
|
2f14fb7fb7 | ||
|
|
fe367d207a | ||
|
|
13a21e5fda | ||
|
|
dcc4ea3aea | ||
|
|
7bc34c0ba4 | ||
|
|
aacf2e9390 | ||
|
|
966a580895 | ||
|
|
bffbc9e198 | ||
|
|
ee3ac3fdaf | ||
|
|
9ddec7d1eb | ||
|
|
4f1d83b9dc | ||
|
|
eeabc0092b | ||
|
|
ea44b5888b | ||
|
|
abde62f369 | ||
|
|
16184d1e9d | ||
|
|
38f89b93ff | ||
|
|
912bc8464e | ||
|
|
d5199501c7 | ||
|
|
f9235cb07d | ||
|
|
d7649c2547 | ||
|
|
be5468a7cf | ||
|
|
eba56e4784 | ||
|
|
26efb2cbee | ||
|
|
02896768f5 | ||
|
|
1d4e76117c | ||
|
|
a912ba3148 | ||
|
|
5b0980172d | ||
|
|
ff8dce77f3 | ||
|
|
7dbdb023a0 | ||
|
|
2fa200889a | ||
|
|
f97d0846ed | ||
|
|
19e5ddeba5 | ||
|
|
df090fa5e1 | ||
|
|
c283f9803c | ||
|
|
3a370e2726 | ||
|
|
73cbf857c1 | ||
|
|
64ad222a28 | ||
|
|
6e94931023 | ||
|
|
ba3b6fd564 | ||
|
|
039bb103c2 | ||
|
|
e946c1c512 | ||
|
|
c67d834d39 | ||
|
|
8bdfd57e9b | ||
|
|
629390dd4a | ||
|
|
3bfd999590 | ||
|
|
80a67149af | ||
|
|
7962f68991 | ||
|
|
c10a005997 | ||
|
|
4dd28c0adf | ||
|
|
4bdd67da79 | ||
|
|
8e24c73ac8 | ||
|
|
bd2887aa77 | ||
|
|
bdc57d57c1 | ||
|
|
38cc183be5 | ||
|
|
930b0d9ad7 | ||
|
|
bf2ca4008e | ||
|
|
a7c9e71490 | ||
|
|
0040d3fc3b | ||
|
|
7e5a5f79cf | ||
|
|
7a161cc8bb | ||
|
|
c5e8a010dc | ||
|
|
5638e9e1c5 | ||
|
|
56e09bf189 | ||
|
|
7aafbcd5d9 | ||
|
|
78fbf45b80 | ||
|
|
bd124d5f9b | ||
|
|
5ef65e837c | ||
|
|
2591dd862b | ||
|
|
159b371973 | ||
|
|
948f37b01a | ||
|
|
1f41b3195f | ||
|
|
4628946a55 | ||
|
|
a231c1b31b | ||
|
|
3fc215d041 | ||
|
|
fed7339558 | ||
|
|
7e0238d1c7 | ||
|
|
2dd0d7189e | ||
|
|
f52504ac41 | ||
|
|
7cfee80e58 | ||
|
|
435cdb6180 | ||
|
|
fe856d3fa7 | ||
|
|
98923d0ea4 | ||
|
|
dd6434a487 | ||
|
|
842b811a41 | ||
|
|
07ab632351 | ||
|
|
9bcfda64fa | ||
|
|
453859d14f | ||
|
|
122cb1f490 | ||
|
|
6bb59b7f63 | ||
|
|
1ec88cf5b0 | ||
|
|
74139ec787 | ||
|
|
f3a780cc2d | ||
|
|
00cb0a798a | ||
|
|
c9bbfc3b0b | ||
|
|
cb085cc598 | ||
|
|
a8b39edd73 | ||
|
|
8b8ebfc7e1 | ||
|
|
d90a708ceb | ||
|
|
450b57c76a | ||
|
|
fa312fb3db | ||
|
|
9384aa1398 | ||
|
|
ce7e7ef1d7 | ||
|
|
a19469aba5 | ||
|
|
b224917fc5 | ||
|
|
3f1eecc4be | ||
|
|
3ba3be877e | ||
|
|
daefbcf994 | ||
|
|
5d9669e5a6 | ||
|
|
1e7745b5c3 | ||
|
|
46d02f15fe | ||
|
|
01f3dddd91 | ||
|
|
2db41ef052 | ||
|
|
afa9471c7c | ||
|
|
08f28de903 | ||
|
|
1fe8da67dc | ||
|
|
591a794247 | ||
|
|
a9e8f315a4 | ||
|
|
c738f9fbd3 | ||
|
|
588f6ba253 | ||
|
|
a9f6948d74 | ||
|
|
f51098c50b | ||
|
|
8b0bf503c3 | ||
|
|
ec402f759b | ||
|
|
bc82f18acb | ||
|
|
a001d53d2a | ||
|
|
1a4d03e283 | ||
|
|
8a8bf47043 | ||
|
|
836ab2820a | ||
|
|
01d0821142 | ||
|
|
47fab2d034 | ||
|
|
fc295ac2ac | ||
|
|
69919902b4 | ||
|
|
a6af72cbbc | ||
|
|
4aa102fcab | ||
|
|
0dc8bb7c3b | ||
|
|
5e450d5cd4 | ||
|
|
38722e8273 | ||
|
|
61be5cc90e | ||
|
|
1422d2962e |
No files matched your search
@@ -45,3 +45,6 @@ catalog.xml
|
||||
#########
|
||||
target
|
||||
|
||||
# Maven shade
|
||||
#############
|
||||
*dependency-reduced-pom.xml
|
||||
+8
-1
@@ -1,5 +1,12 @@
|
||||
language: java
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
|
||||
before_cache:
|
||||
- rm -rf $HOME/.m2/repository/org/keycloak
|
||||
|
||||
env:
|
||||
global:
|
||||
- MAVEN_SKIP_RC=true
|
||||
@@ -12,7 +19,7 @@ before_script:
|
||||
- export MAVEN_SKIP_RC=true
|
||||
|
||||
install:
|
||||
- mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||
- travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||
|
||||
script:
|
||||
- mvn test -B
|
||||
|
||||
@@ -36,7 +36,7 @@ Once completed you will find distribution archives in `distribution`.
|
||||
Starting Keycloak
|
||||
-----------------
|
||||
|
||||
To start Keycloak during development first build as specficied above, then run:
|
||||
To start Keycloak during development first build as specified above, then run:
|
||||
|
||||
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server
|
||||
|
||||
@@ -52,7 +52,7 @@ To stop the server press `Ctrl + C`.
|
||||
|
||||
Help and Documentation
|
||||
----------------------
|
||||
* [Documentation](http://keycloak.jboss.org/docs) - User Guide, Admin REST API and Javadocs
|
||||
* [Documentation](http://www.keycloak.org/documentation.html) - User Guide, Admin REST API and Javadocs
|
||||
* [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak
|
||||
* [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests
|
||||
|
||||
@@ -65,10 +65,11 @@ Contributing
|
||||
* [Testsuite](misc/Testsuite.md) - Details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server)
|
||||
* [Database Testing](misc/DatabaseTesting.md) - How to do testing of Keycloak on different databases
|
||||
* [Updating Database](misc/UpdatingDatabaseSchema.md) - How to change the Keycloak database
|
||||
* [Changing the Default keycloak-subsystem Configuration](misc/UpdatingServerConfig.md) - How to update the default keycloak-subsystem config
|
||||
* [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+35
-55
@@ -17,26 +17,21 @@
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -57,7 +52,7 @@ public class AdapterDeploymentContext {
|
||||
* during the application deployment's life cycle.
|
||||
*
|
||||
* @param deployment A KeycloakConfigResolver, possibly missing the Auth
|
||||
* Server URL and/or Realm Public Key
|
||||
* Server URL
|
||||
*/
|
||||
public AdapterDeploymentContext(KeycloakDeployment deployment) {
|
||||
this.deployment = deployment;
|
||||
@@ -79,7 +74,6 @@ public class AdapterDeploymentContext {
|
||||
/**
|
||||
* For single-tenant deployments, it complements KeycloakDeployment
|
||||
* by resolving a relative Auth Server's URL based on the current request
|
||||
* and, if needed, will lazily resolve the Realm's Public Key.
|
||||
*
|
||||
* For multi-tenant deployments, defers the resolution of KeycloakDeployment
|
||||
* to the KeycloakConfigResolver .
|
||||
@@ -98,8 +92,8 @@ public class AdapterDeploymentContext {
|
||||
if (deployment.getAuthServerBaseUrl() == null) return deployment;
|
||||
|
||||
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
|
||||
if (resolvedDeployment.getRealmKey() == null) {
|
||||
resolveRealmKey(resolvedDeployment);
|
||||
if (resolvedDeployment.getPublicKeyLocator() == null) {
|
||||
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
|
||||
}
|
||||
return resolvedDeployment;
|
||||
}
|
||||
@@ -115,45 +109,6 @@ public class AdapterDeploymentContext {
|
||||
}
|
||||
}
|
||||
|
||||
public void resolveRealmKey(KeycloakDeployment deployment) {
|
||||
if (deployment.getClient() == null) {
|
||||
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
|
||||
}
|
||||
HttpGet get = new HttpGet(deployment.getRealmInfoUrl());
|
||||
try {
|
||||
HttpResponse response = deployment.getClient().execute(get);
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status != 200) {
|
||||
close(response);
|
||||
throw new RuntimeException("Unable to resolve realm public key remotely, status = " + status);
|
||||
}
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity == null) {
|
||||
throw new RuntimeException("Unable to resolve realm public key remotely. There was no entity.");
|
||||
}
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
int c;
|
||||
while ((c = is.read()) != -1) {
|
||||
os.write(c);
|
||||
}
|
||||
byte[] bytes = os.toByteArray();
|
||||
String json = new String(bytes);
|
||||
PublishedRealmRepresentation rep = JsonSerialization.readValue(json, PublishedRealmRepresentation.class);
|
||||
deployment.setRealmKey(rep.getPublicKey());
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to resolve realm public key remotely", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This delegate is used to store temporary, per-request metadata like request resolved URLs.
|
||||
* Ever method is delegated except URL get methods and isConfigured()
|
||||
@@ -207,6 +162,11 @@ public class AdapterDeploymentContext {
|
||||
return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJwksUrl() {
|
||||
return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceName() {
|
||||
return delegate.getResourceName();
|
||||
@@ -223,13 +183,13 @@ public class AdapterDeploymentContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getRealmKey() {
|
||||
return delegate.getRealmKey();
|
||||
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
|
||||
delegate.setPublicKeyLocator(publicKeyLocator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRealmKey(PublicKey realmKey) {
|
||||
delegate.setRealmKey(realmKey);
|
||||
public PublicKeyLocator getPublicKeyLocator() {
|
||||
return delegate.getPublicKeyLocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -466,6 +426,26 @@ public class AdapterDeploymentContext {
|
||||
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
|
||||
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolicyEnforcer getPolicyEnforcer() {
|
||||
return delegate.getPolicyEnforcer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
delegate.setPolicyEnforcer(policyEnforcer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
|
||||
delegate.setMinTimeBetweenJwksRequests(minTimeBetweenJwksRequests);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinTimeBetweenJwksRequests() {
|
||||
return delegate.getMinTimeBetweenJwksRequests();
|
||||
}
|
||||
}
|
||||
|
||||
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
||||
|
||||
@@ -19,9 +19,7 @@ package org.keycloak.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
+1
-1
@@ -31,10 +31,10 @@ import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
@@ -84,7 +84,7 @@ public class BearerTokenRequestAuthenticator {
|
||||
|
||||
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
|
||||
try {
|
||||
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
} catch (VerificationException e) {
|
||||
log.error("Failed to verify token", e);
|
||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
|
||||
|
||||
+3
-5
@@ -17,19 +17,17 @@
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
@@ -73,7 +71,7 @@ public class CookieTokenStore {
|
||||
|
||||
try {
|
||||
// Skip check if token is active now. It's supposed to be done later by the caller
|
||||
AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
|
||||
AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true);
|
||||
IDToken idToken;
|
||||
if (idTokenString != null && idTokenString.length() > 0) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class HttpAdapterUtils {
|
||||
|
||||
|
||||
public static <T> T sendJsonHttpRequest(KeycloakDeployment deployment, HttpRequestBase httpRequest, Class<T> clazz) throws HttpClientAdapterException {
|
||||
try {
|
||||
HttpResponse response = deployment.getClient().execute(httpRequest);
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status != 200) {
|
||||
close(response);
|
||||
throw new HttpClientAdapterException("Unexpected status = " + status);
|
||||
}
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity == null) {
|
||||
throw new HttpClientAdapterException("There was no entity.");
|
||||
}
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
int c;
|
||||
while ((c = is.read()) != -1) {
|
||||
os.write(c);
|
||||
}
|
||||
byte[] bytes = os.toByteArray();
|
||||
String json = new String(bytes);
|
||||
return JsonSerialization.readValue(json, clazz);
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new HttpClientAdapterException("IO error", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void close(HttpResponse response) {
|
||||
if (response.getEntity() != null) {
|
||||
try {
|
||||
response.getEntity().getContent().close();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-10
@@ -15,22 +15,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.example;
|
||||
package org.keycloak.adapters;
|
||||
|
||||
/**
|
||||
* Client authentication with traditional OAuth2 client_id + client_secret
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ProductSAClientSecretServlet extends ProductServiceAccountServlet {
|
||||
public class HttpClientAdapterException extends Exception {
|
||||
|
||||
@Override
|
||||
protected String getAdapterConfigLocation() {
|
||||
return "/WEB-INF/keycloak-client-secret.json";
|
||||
public HttpClientAdapterException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getClientAuthenticationMethod() {
|
||||
return "secret";
|
||||
public HttpClientAdapterException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
}
|
||||
+24
-9
@@ -21,15 +21,15 @@ import org.apache.http.client.HttpClient;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.security.PublicKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class KeycloakDeployment {
|
||||
|
||||
protected RelativeUrlsUsed relativeUrls;
|
||||
protected String realm;
|
||||
protected volatile PublicKey realmKey;
|
||||
protected PublicKeyLocator publicKeyLocator;
|
||||
protected String authServerBaseUrl;
|
||||
protected String realmInfoUrl;
|
||||
protected KeycloakUriBuilder authUrl;
|
||||
@@ -52,6 +52,7 @@ public class KeycloakDeployment {
|
||||
protected String accountUrl;
|
||||
protected String registerNodeUrl;
|
||||
protected String unregisterNodeUrl;
|
||||
protected String jwksUrl;
|
||||
protected String principalAttribute = "sub";
|
||||
|
||||
protected String resourceName;
|
||||
@@ -79,13 +80,14 @@ public class KeycloakDeployment {
|
||||
|
||||
protected volatile int notBefore;
|
||||
protected int tokenMinimumTimeToLive;
|
||||
protected int minTimeBetweenJwksRequests;
|
||||
private PolicyEnforcer policyEnforcer;
|
||||
|
||||
public KeycloakDeployment() {
|
||||
}
|
||||
|
||||
public boolean isConfigured() {
|
||||
return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
|
||||
return getRealm() != null && getPublicKeyLocator() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
|
||||
}
|
||||
|
||||
public String getResourceName() {
|
||||
@@ -100,12 +102,12 @@ public class KeycloakDeployment {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public PublicKey getRealmKey() {
|
||||
return realmKey;
|
||||
public PublicKeyLocator getPublicKeyLocator() {
|
||||
return publicKeyLocator;
|
||||
}
|
||||
|
||||
public void setRealmKey(PublicKey realmKey) {
|
||||
this.realmKey = realmKey;
|
||||
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
|
||||
this.publicKeyLocator = publicKeyLocator;
|
||||
}
|
||||
|
||||
public String getAuthServerBaseUrl() {
|
||||
@@ -147,6 +149,7 @@ public class KeycloakDeployment {
|
||||
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
|
||||
registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
|
||||
unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
|
||||
jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).build(getRealm()).toString();
|
||||
}
|
||||
|
||||
public RelativeUrlsUsed getRelativeUrls() {
|
||||
@@ -181,6 +184,10 @@ public class KeycloakDeployment {
|
||||
return unregisterNodeUrl;
|
||||
}
|
||||
|
||||
public String getJwksUrl() {
|
||||
return jwksUrl;
|
||||
}
|
||||
|
||||
public void setResourceName(String resourceName) {
|
||||
this.resourceName = resourceName;
|
||||
}
|
||||
@@ -369,6 +376,14 @@ public class KeycloakDeployment {
|
||||
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
|
||||
}
|
||||
|
||||
public int getMinTimeBetweenJwksRequests() {
|
||||
return minTimeBetweenJwksRequests;
|
||||
}
|
||||
|
||||
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
|
||||
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
|
||||
}
|
||||
|
||||
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
}
|
||||
|
||||
+10
-2
@@ -22,6 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
@@ -59,11 +61,16 @@ public class KeycloakDeploymentBuilder {
|
||||
PublicKey realmKey;
|
||||
try {
|
||||
realmKey = PemUtils.decodePublicKey(realmKeyPem);
|
||||
HardcodedPublicKeyLocator pkLocator = new HardcodedPublicKeyLocator(realmKey);
|
||||
deployment.setPublicKeyLocator(pkLocator);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
deployment.setRealmKey(realmKey);
|
||||
} else {
|
||||
JWKPublicKeyLocator pkLocator = new JWKPublicKeyLocator();
|
||||
deployment.setPublicKeyLocator(pkLocator);
|
||||
}
|
||||
|
||||
if (adapterConfig.getSslRequired() != null) {
|
||||
deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
|
||||
} else {
|
||||
@@ -97,6 +104,7 @@ public class KeycloakDeploymentBuilder {
|
||||
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
||||
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
||||
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
||||
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
||||
|
||||
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
|
||||
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
|
||||
@@ -105,7 +113,7 @@ public class KeycloakDeploymentBuilder {
|
||||
deployment.setClient(new HttpClientBuilder().build(adapterConfig));
|
||||
}
|
||||
if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) {
|
||||
throw new RuntimeException("You must specify auth-url");
|
||||
throw new RuntimeException("You must specify auth-server-url");
|
||||
}
|
||||
deployment.setAuthServerBaseUrl(adapterConfig);
|
||||
if (adapterConfig.getTurnOffChangeSessionIdOnLogin() != null) {
|
||||
|
||||
+4
-4
@@ -17,6 +17,10 @@
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.HostUtils;
|
||||
import org.keycloak.common.util.Time;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
@@ -24,10 +28,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.HostUtils;
|
||||
import org.keycloak.common.util.Time;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
||||
+19
-6
@@ -19,12 +19,14 @@ package org.keycloak.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
@@ -32,8 +34,7 @@ import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@@ -159,6 +160,12 @@ public class OAuthRequestAuthenticator {
|
||||
String scope = getQueryParamValue(OAuth2Constants.SCOPE);
|
||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.SCOPE);
|
||||
|
||||
String prompt = getQueryParamValue(OAuth2Constants.PROMPT);
|
||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.PROMPT);
|
||||
|
||||
String maxAge = getQueryParamValue(OAuth2Constants.MAX_AGE);
|
||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.MAX_AGE);
|
||||
|
||||
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
@@ -171,9 +178,15 @@ public class OAuthRequestAuthenticator {
|
||||
if (idpHint != null && idpHint.length() > 0) {
|
||||
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
|
||||
}
|
||||
if (scope != null && scope.length() > 0) {
|
||||
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
||||
if (prompt != null && prompt.length() > 0) {
|
||||
redirectUriBuilder.queryParam(OAuth2Constants.PROMPT, prompt);
|
||||
}
|
||||
if (maxAge != null && maxAge.length() > 0) {
|
||||
redirectUriBuilder.queryParam(OAuth2Constants.MAX_AGE, maxAge);
|
||||
}
|
||||
|
||||
scope = TokenUtil.attachOIDCScope(scope);
|
||||
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
||||
|
||||
return redirectUriBuilder.build().toString();
|
||||
}
|
||||
@@ -329,7 +342,7 @@ public class OAuthRequestAuthenticator {
|
||||
refreshToken = tokenResponse.getRefreshToken();
|
||||
idTokenString = tokenResponse.getIdToken();
|
||||
try {
|
||||
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
if (idTokenString != null) {
|
||||
try {
|
||||
JWSInput input = new JWSInput(idTokenString);
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ public class OIDCAuthenticationError implements AuthenticationError {
|
||||
CODE_TO_TOKEN_FAILURE,
|
||||
INVALID_TOKEN,
|
||||
STALE_TOKEN,
|
||||
NO_AUTHORIZATION_HEADER
|
||||
NO_AUTHORIZATION_HEADER,
|
||||
NO_QUERY_PARAMETER_ACCESS_TOKEN
|
||||
}
|
||||
|
||||
private Reason reason;
|
||||
|
||||
+41
-2
@@ -17,20 +17,32 @@
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.VersionRepresentation;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.VersionRepresentation;
|
||||
import org.keycloak.representations.adapters.action.AdminAction;
|
||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
|
||||
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
@@ -82,6 +94,10 @@ public class PreAuthActionsHandler {
|
||||
if (!resolveDeployment()) return true;
|
||||
handleTestAvailable();
|
||||
return true;
|
||||
} else if (requestUri.endsWith(AdapterConstants.K_JWKS)) {
|
||||
if (!resolveDeployment()) return true;
|
||||
handleJwksRequest();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -198,7 +214,8 @@ public class PreAuthActionsHandler {
|
||||
|
||||
try {
|
||||
JWSInput input = new JWSInput(token);
|
||||
if (RSAProvider.verify(input, deployment.getRealmKey())) {
|
||||
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment);
|
||||
if (RSAProvider.verify(input, publicKey)) {
|
||||
return input;
|
||||
}
|
||||
} catch (JWSInputException ignore) {
|
||||
@@ -240,4 +257,26 @@ public class PreAuthActionsHandler {
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleJwksRequest() {
|
||||
try {
|
||||
JSONWebKeySet jwks = new JSONWebKeySet();
|
||||
ClientCredentialsProvider clientCredentialsProvider = deployment.getClientAuthenticator();
|
||||
|
||||
// For now, just get signature key from JWT provider. We can add more if we support encryption etc.
|
||||
if (clientCredentialsProvider instanceof JWTClientCredentialsProvider) {
|
||||
PublicKey publicKey = ((JWTClientCredentialsProvider) clientCredentialsProvider).getPublicKey();
|
||||
JWK jwk = JWKBuilder.create().rs256(publicKey);
|
||||
jwks.setKeys(new JWK[] { jwk });
|
||||
} else {
|
||||
jwks.setKeys(new JWK[] {});
|
||||
}
|
||||
|
||||
facade.getResponse().setStatus(200);
|
||||
facade.getResponse().setHeader("Content-Type", "application/json");
|
||||
JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), jwks);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
|
||||
public static final String ACCESS_TOKEN = "access_token";
|
||||
protected Logger log = Logger.getLogger(QueryParamterTokenRequestAuthenticator.class);
|
||||
|
||||
public QueryParamterTokenRequestAuthenticator(KeycloakDeployment deployment) {
|
||||
super(deployment);
|
||||
}
|
||||
|
||||
public AuthOutcome authenticate(HttpFacade exchange) {
|
||||
tokenString = null;
|
||||
tokenString = getAccessTokenFromQueryParamter(exchange);
|
||||
if (tokenString == null || tokenString.trim().isEmpty()) {
|
||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_QUERY_PARAMETER_ACCESS_TOKEN, null, null);
|
||||
return AuthOutcome.NOT_ATTEMPTED;
|
||||
}
|
||||
return (authenticateToken(exchange, tokenString));
|
||||
}
|
||||
|
||||
String getAccessTokenFromQueryParamter(HttpFacade exchange) {
|
||||
try {
|
||||
if (exchange != null && exchange.getRequest() != null) {
|
||||
return exchange.getRequest().getQueryParamValue(ACCESS_TOKEN);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -20,7 +20,7 @@ package org.keycloak.adapters;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
@@ -130,7 +130,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
||||
String tokenString = response.getToken();
|
||||
AccessToken token = null;
|
||||
try {
|
||||
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
log.debug("Token Verification succeeded!");
|
||||
} catch (VerificationException e) {
|
||||
log.error("failed verification of token");
|
||||
|
||||
+26
-2
@@ -61,7 +61,7 @@ public abstract class RequestAuthenticator {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("try bearer");
|
||||
}
|
||||
|
||||
|
||||
AuthOutcome outcome = bearer.authenticate(facade);
|
||||
if (outcome == AuthOutcome.FAILED) {
|
||||
challenge = bearer.getChallenge();
|
||||
@@ -74,18 +74,36 @@ public abstract class RequestAuthenticator {
|
||||
return AuthOutcome.AUTHENTICATED;
|
||||
}
|
||||
|
||||
QueryParamterTokenRequestAuthenticator queryParamAuth = createQueryParamterTokenRequestAuthenticator();
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("try query paramter auth");
|
||||
}
|
||||
|
||||
outcome = queryParamAuth.authenticate(facade);
|
||||
if (outcome == AuthOutcome.FAILED) {
|
||||
challenge = queryParamAuth.getChallenge();
|
||||
log.debug("QueryParamAuth auth FAILED");
|
||||
return AuthOutcome.FAILED;
|
||||
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||
if (verifySSL()) return AuthOutcome.FAILED;
|
||||
log.debug("QueryParamAuth AUTHENTICATED");
|
||||
completeAuthentication(queryParamAuth, "KEYCLOAK");
|
||||
return AuthOutcome.AUTHENTICATED;
|
||||
}
|
||||
|
||||
if (deployment.isEnableBasicAuth()) {
|
||||
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("try basic auth");
|
||||
}
|
||||
|
||||
|
||||
outcome = basicAuth.authenticate(facade);
|
||||
if (outcome == AuthOutcome.FAILED) {
|
||||
challenge = basicAuth.getChallenge();
|
||||
log.debug("BasicAuth FAILED");
|
||||
return AuthOutcome.FAILED;
|
||||
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||
if (verifySSL()) return AuthOutcome.FAILED;
|
||||
log.debug("BasicAuth AUTHENTICATED");
|
||||
completeAuthentication(basicAuth, "BASIC");
|
||||
return AuthOutcome.AUTHENTICATED;
|
||||
@@ -150,6 +168,10 @@ public abstract class RequestAuthenticator {
|
||||
return new BasicAuthRequestAuthenticator(deployment);
|
||||
}
|
||||
|
||||
protected QueryParamterTokenRequestAuthenticator createQueryParamterTokenRequestAuthenticator() {
|
||||
return new QueryParamterTokenRequestAuthenticator(deployment);
|
||||
}
|
||||
|
||||
protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
|
||||
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
|
||||
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
|
||||
@@ -158,10 +180,12 @@ public abstract class RequestAuthenticator {
|
||||
}
|
||||
|
||||
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
|
||||
|
||||
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
|
||||
|
||||
/**
|
||||
* After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation
|
||||
*
|
||||
* @param create
|
||||
* @return
|
||||
*/
|
||||
|
||||
@@ -26,12 +26,12 @@ import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.common.util.HostUtils;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
+2
-2
@@ -17,10 +17,10 @@
|
||||
|
||||
package org.keycloak.adapters.authentication;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The simple SPI for authenticating clients/applications . It's used by adapter during all OIDC backchannel requests to Keycloak server
|
||||
* (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request
|
||||
|
||||
+6
-6
@@ -17,6 +17,12 @@
|
||||
|
||||
package org.keycloak.adapters.authentication;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -24,12 +30,6 @@ import java.util.Map;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
||||
+2
-2
@@ -17,14 +17,14 @@
|
||||
|
||||
package org.keycloak.adapters.authentication;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Traditional OAuth2 authentication of clients based on client_id and client_secret
|
||||
*
|
||||
|
||||
+28
-6
@@ -17,16 +17,24 @@
|
||||
|
||||
package org.keycloak.adapters.authentication;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.common.util.KeystoreUtil;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client private key .
|
||||
@@ -38,7 +46,9 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
||||
|
||||
public static final String PROVIDER_ID = "jwt";
|
||||
|
||||
private PrivateKey privateKey;
|
||||
private KeyPair keyPair;
|
||||
private JWK publicKeyJwk;
|
||||
|
||||
private int tokenTimeout;
|
||||
|
||||
@Override
|
||||
@@ -46,14 +56,23 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
public void setPrivateKey(PrivateKey privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
public void setupKeyPair(KeyPair keyPair) {
|
||||
this.keyPair = keyPair;
|
||||
this.publicKeyJwk = JWKBuilder.create().rs256(keyPair.getPublic());
|
||||
}
|
||||
|
||||
public void setTokenTimeout(int tokenTimeout) {
|
||||
this.tokenTimeout = tokenTimeout;
|
||||
}
|
||||
|
||||
protected int getTokenTimeout() {
|
||||
return tokenTimeout;
|
||||
}
|
||||
|
||||
public PublicKey getPublicKey() {
|
||||
return keyPair.getPublic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(KeycloakDeployment deployment, Object config) {
|
||||
if (config == null || !(config instanceof Map)) {
|
||||
@@ -84,7 +103,9 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
||||
if (clientKeyAlias == null) {
|
||||
clientKeyAlias = deployment.getResourceName();
|
||||
}
|
||||
this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
|
||||
|
||||
KeyPair keyPair = KeystoreUtil.loadKeyPairFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
|
||||
setupKeyPair(keyPair);
|
||||
|
||||
this.tokenTimeout = asInt(cfg, "token-timeout", 10);
|
||||
}
|
||||
@@ -116,8 +137,9 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
||||
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
|
||||
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
|
||||
return new JWSBuilder()
|
||||
.kid(publicKeyJwk.getKeyId())
|
||||
.jsonContent(jwt)
|
||||
.rsa256(privateKey);
|
||||
.rsa256(keyPair.getPrivate());
|
||||
}
|
||||
|
||||
protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
|
||||
|
||||
+20
-10
@@ -29,9 +29,10 @@ import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -55,7 +56,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
|
||||
this.authzClient = policyEnforcer.getClient();
|
||||
this.pathMatcher = new PathMatcher();
|
||||
this.paths = policyEnforcer.getPaths();
|
||||
this.paths = new ArrayList<>(policyEnforcer.getPaths());
|
||||
}
|
||||
|
||||
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
||||
@@ -126,11 +127,13 @@ public abstract class AbstractPolicyEnforcer {
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
for (Permission permission : permissions) {
|
||||
Set<String> allowedScopes = permission.getScopes();
|
||||
|
||||
if (permission.getResourceSetId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
if (((allowedScopes == null || allowedScopes.isEmpty()) && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
|
||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||
continue;
|
||||
|
||||
}
|
||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
||||
this.paths.remove(actualPathConfig);
|
||||
@@ -139,8 +142,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((allowedScopes.isEmpty() && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -151,6 +153,11 @@ public abstract class AbstractPolicyEnforcer {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
|
||||
Set<String> allowedScopes = permission.getScopes();
|
||||
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
|
||||
}
|
||||
|
||||
protected AuthzClient getAuthzClient() {
|
||||
return this.authzClient;
|
||||
}
|
||||
@@ -210,7 +217,6 @@ public abstract class AbstractPolicyEnforcer {
|
||||
config.setPath(targetResource.getUri());
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setInstance(true);
|
||||
config.setParentConfig(originalConfig);
|
||||
|
||||
this.paths.add(config);
|
||||
@@ -244,13 +250,17 @@ public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
// first we try a match using resource id
|
||||
boolean resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getId());
|
||||
boolean resourceMatch = matchResourcePermission(actualPathConfig, permission);
|
||||
|
||||
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
|
||||
if (!resourceMatch && actualPathConfig.isInstance()) {
|
||||
resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getParentConfig().getId());
|
||||
resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission);
|
||||
}
|
||||
|
||||
return resourceMatch;
|
||||
}
|
||||
|
||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
return permission.getResourceSetId().equals(actualPathConfig.getId());
|
||||
}
|
||||
}
|
||||
-1
@@ -26,7 +26,6 @@ import org.keycloak.authorization.client.resource.PermissionResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
||||
+37
-6
@@ -18,20 +18,24 @@
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.AuthorizationRequest;
|
||||
import org.keycloak.authorization.client.representation.AuthorizationResponse;
|
||||
import org.keycloak.authorization.client.representation.EntitlementRequest;
|
||||
import org.keycloak.authorization.client.representation.EntitlementResponse;
|
||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
||||
import org.keycloak.authorization.client.representation.PermissionResponse;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -50,9 +54,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
int retry = 2;
|
||||
AccessToken original = accessToken;
|
||||
|
||||
while (retry >= 0) {
|
||||
while (retry > 0) {
|
||||
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
original.setAuthorization(accessToken.getAuthorization());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -62,6 +65,21 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessToken.Authorization authorization = original.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
authorization = new AccessToken.Authorization();
|
||||
authorization.setPermissions(new ArrayList<Permission>());
|
||||
}
|
||||
|
||||
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
||||
|
||||
if (newAuthorization != null) {
|
||||
authorization.getPermissions().addAll(newAuthorization.getPermissions());
|
||||
}
|
||||
|
||||
original.setAuthorization(authorization);
|
||||
|
||||
retry--;
|
||||
}
|
||||
|
||||
@@ -101,14 +119,27 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
|
||||
|
||||
if (authzResponse != null) {
|
||||
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
}
|
||||
|
||||
return null;
|
||||
} else {
|
||||
LOGGER.debug("Obtaining entitlements for authenticated user.");
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
|
||||
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||
AccessToken token = httpFacade.getSecurityContext().getToken();
|
||||
|
||||
if (token.getAuthorization() == null) {
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
} else {
|
||||
EntitlementRequest request = new EntitlementRequest();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setResourceSetName(pathConfig.getName());
|
||||
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
||||
request.addPermission(permissionRequest);
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
}
|
||||
}
|
||||
} catch (AuthorizationDeniedException e) {
|
||||
return null;
|
||||
|
||||
+20
-7
@@ -30,9 +30,10 @@ import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -45,7 +46,6 @@ public class PolicyEnforcer {
|
||||
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
||||
|
||||
private final KeycloakDeployment deployment;
|
||||
private final PathMatcher pathMatcher;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
private final List<PathConfig> paths;
|
||||
@@ -54,7 +54,6 @@ public class PolicyEnforcer {
|
||||
this.deployment = deployment;
|
||||
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
|
||||
this.pathMatcher = new PathMatcher();
|
||||
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -97,8 +96,8 @@ public class PolicyEnforcer {
|
||||
return authzClient;
|
||||
}
|
||||
|
||||
List<PathConfig> getPaths() {
|
||||
return paths;
|
||||
public List<PathConfig> getPaths() {
|
||||
return Collections.unmodifiableList(paths);
|
||||
}
|
||||
|
||||
KeycloakDeployment getDeployment() {
|
||||
@@ -156,13 +155,27 @@ public class PolicyEnforcer {
|
||||
|
||||
pathConfig.setId(registrationResponse.getId());
|
||||
} else {
|
||||
throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + ". Make sure you have created a resource on the server that matches with the path configuration.");
|
||||
throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration.");
|
||||
}
|
||||
} else {
|
||||
pathConfig.setId(search.iterator().next());
|
||||
}
|
||||
|
||||
paths.add(pathConfig);
|
||||
PathConfig existingPath = null;
|
||||
|
||||
for (PathConfig current : paths) {
|
||||
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
|
||||
existingPath = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingPath == null) {
|
||||
paths.add(pathConfig);
|
||||
} else {
|
||||
existingPath.getMethods().addAll(pathConfig.getMethods());
|
||||
existingPath.getScopes().addAll(pathConfig.getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
|
||||
+20
-25
@@ -17,14 +17,17 @@
|
||||
|
||||
package org.keycloak.adapters.jaas;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.security.Principal;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.FindFile;
|
||||
import org.keycloak.common.util.reflections.Reflections;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.Callback;
|
||||
@@ -34,19 +37,14 @@ import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import javax.security.auth.spi.LoginModule;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.FindFile;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.common.util.reflections.Reflections;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.security.Principal;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
@@ -88,9 +86,6 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
||||
try {
|
||||
InputStream is = FindFile.findFile(keycloakConfigFile);
|
||||
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
|
||||
if (kd.getRealmKey() == null) {
|
||||
new AdapterDeploymentContext().resolveRealmKey(kd);
|
||||
}
|
||||
return kd;
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
|
||||
@@ -190,7 +185,7 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
||||
|
||||
|
||||
protected Auth bearerAuth(String tokenString) throws VerificationException {
|
||||
AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||
AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
|
||||
boolean verifyCaller;
|
||||
if (deployment.isUseResourceRoleMappings()) {
|
||||
|
||||
+21
-21
@@ -17,19 +17,6 @@
|
||||
|
||||
package org.keycloak.adapters.jaas;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
@@ -39,12 +26,25 @@ import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Login module based on Resource Owner password credentials grant from OAuth2 specs. It's supposed to be used in environments. which
|
||||
@@ -102,9 +102,9 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
||||
StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status);
|
||||
if (entity != null) {
|
||||
InputStream is = entity.getContent();
|
||||
Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
|
||||
errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
|
||||
.append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
|
||||
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
||||
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
||||
.append(", Error description: " + errorRep.getErrorDescription());
|
||||
}
|
||||
String error = errorBuilder.toString();
|
||||
log.warn(error);
|
||||
@@ -161,9 +161,9 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
||||
if (entity != null) {
|
||||
InputStream is = entity.getContent();
|
||||
if (status == 400) {
|
||||
Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
|
||||
errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
|
||||
.append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
|
||||
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
||||
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
||||
.append(", Error description: " + errorRep.getErrorDescription());
|
||||
|
||||
} else {
|
||||
if (is != null) is.close();
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.rotation;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AdapterRSATokenVerifier {
|
||||
|
||||
private static final Logger log = Logger.getLogger(AdapterRSATokenVerifier.class);
|
||||
|
||||
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
|
||||
return verifyToken(tokenString, deployment, true, true);
|
||||
}
|
||||
|
||||
|
||||
public static PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
|
||||
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
|
||||
|
||||
PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
|
||||
if (publicKey == null) {
|
||||
log.errorf("Didn't find publicKey for kid: %s", kid);
|
||||
throw new VerificationException("Didn't find publicKey for specified kid");
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
||||
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(deployment.getRealmInfoUrl()).checkActive(checkActive).checkTokenType(checkTokenType);
|
||||
PublicKey publicKey = getPublicKey(verifier.getHeader().getKeyId(), deployment);
|
||||
return verifier.publicKey(publicKey).verify().getToken();
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -15,23 +15,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.example;
|
||||
package org.keycloak.adapters.rotation;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client private key .
|
||||
* See Keycloak documentation and <a href="https://tools.ietf.org/html/rfc7519">specs</a> for more details.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ProductSAClientSignedJWTServlet extends ProductServiceAccountServlet {
|
||||
public class HardcodedPublicKeyLocator implements PublicKeyLocator {
|
||||
|
||||
@Override
|
||||
protected String getAdapterConfigLocation() {
|
||||
return "/WEB-INF/keycloak-client-signed-jwt.json";
|
||||
private PublicKey publicKey;
|
||||
|
||||
public HardcodedPublicKeyLocator(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getClientAuthenticationMethod() {
|
||||
return "jwt";
|
||||
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
||||
return publicKey;
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.rotation;
|
||||
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.HttpAdapterUtils;
|
||||
import org.keycloak.adapters.HttpClientAdapterException;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.util.JWKSUtils;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* When needed, publicKeys are downloaded by sending request to realm's jwks_url
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class JWKPublicKeyLocator implements PublicKeyLocator {
|
||||
|
||||
private static final Logger log = Logger.getLogger(JWKPublicKeyLocator.class);
|
||||
|
||||
private Map<String, PublicKey> currentKeys = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile int lastRequestTime = 0;
|
||||
|
||||
@Override
|
||||
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
||||
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
|
||||
|
||||
// Check if key is in cache.
|
||||
PublicKey publicKey = currentKeys.get(kid);
|
||||
if (publicKey != null) {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
int currentTime = Time.currentTime();
|
||||
|
||||
// Check if we are allowed to send request
|
||||
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
|
||||
synchronized (this) {
|
||||
currentTime = Time.currentTime();
|
||||
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
|
||||
sendRequest(deployment);
|
||||
lastRequestTime = currentTime;
|
||||
} else {
|
||||
log.debugf("Won't send request to realm jwks url. Last request time was %d", lastRequestTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentKeys.get(kid);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void sendRequest(KeycloakDeployment deployment) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.tracef("Going to send request to retrieve new set of realm public keys for client %s", deployment.getResourceName());
|
||||
}
|
||||
|
||||
HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
|
||||
try {
|
||||
JSONWebKeySet jwks = HttpAdapterUtils.sendJsonHttpRequest(deployment, getMethod, JSONWebKeySet.class);
|
||||
|
||||
Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debugf("Realm public keys successfully retrieved for client %s. New kids: %s", deployment.getResourceName(), publicKeys.keySet().toString());
|
||||
}
|
||||
|
||||
// Update current keys
|
||||
currentKeys.clear();
|
||||
currentKeys.putAll(publicKeys);
|
||||
|
||||
} catch (HttpClientAdapterException e) {
|
||||
log.error("Error when sending request to retrieve realm keys", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.rotation;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface PublicKeyLocator {
|
||||
|
||||
/**
|
||||
* @param kid
|
||||
* @param deployment
|
||||
* @return publicKey, which should be used for verify signature on given "input"
|
||||
*/
|
||||
PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
|
||||
|
||||
}
|
||||
+12
-2
@@ -21,10 +21,12 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -39,7 +41,11 @@ public class KeycloakDeploymentBuilderTest {
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
|
||||
assertEquals("demo", deployment.getRealm());
|
||||
assertEquals("customer-portal", deployment.getResourceName());
|
||||
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey());
|
||||
|
||||
assertTrue(deployment.getPublicKeyLocator() instanceof HardcodedPublicKeyLocator);
|
||||
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"),
|
||||
deployment.getPublicKeyLocator().getPublicKey(null, deployment));
|
||||
|
||||
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
|
||||
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
|
||||
assertTrue(deployment.isUseResourceRoleMappings());
|
||||
@@ -62,12 +68,16 @@ public class KeycloakDeploymentBuilderTest {
|
||||
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
|
||||
assertEquals("email", deployment.getPrincipalAttribute());
|
||||
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
||||
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadNoClientCredentials() throws Exception {
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
|
||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||
|
||||
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
|
||||
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
/**
|
||||
* @author github.com/tubbynl
|
||||
*
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"realm": "demo",
|
||||
"resource": "customer-portal",
|
||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url": "https://localhost:8443/auth",
|
||||
"public-client": true,
|
||||
"expose-token": true
|
||||
|
||||
@@ -29,5 +29,6 @@
|
||||
"register-node-period": 1000,
|
||||
"token-store": "cookie",
|
||||
"principal-attribute": "email",
|
||||
"token-minimum-time-to-live": 10
|
||||
"token-minimum-time-to-live": 10,
|
||||
"min-time-between-jwks-requests": 20
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
-4
@@ -22,15 +22,11 @@ import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.connector.Response;
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.deploy.LoginConfig;
|
||||
import org.apache.catalina.realm.GenericPrincipal;
|
||||
import org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve;
|
||||
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Keycloak authentication valve
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+8
-8
@@ -49,7 +49,6 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
||||
@Override
|
||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||
String deploymentName = deploymentUnit.getName();
|
||||
|
||||
// if it's not a web-app there's nothing to secure
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
@@ -67,24 +66,25 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
||||
// otherwise
|
||||
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
|
||||
|
||||
boolean hasSubsystemConfig = service.isSecureDeployment(deploymentName);
|
||||
boolean hasSubsystemConfig = service.isSecureDeployment(deploymentUnit);
|
||||
boolean webRequiresKC = loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod());
|
||||
boolean isConfigured = service.isDeploymentConfigured(deploymentUnit);
|
||||
|
||||
if (hasSubsystemConfig || webRequiresKC) {
|
||||
log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentName);
|
||||
if ((hasSubsystemConfig && isConfigured) || webRequiresKC) {
|
||||
log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentUnit.getName());
|
||||
|
||||
// if secure-deployment configuration exists for web app, we force KEYCLOAK auth method on it
|
||||
if (hasSubsystemConfig) {
|
||||
addJSONData(service.getJSON(deploymentName), warMetaData);
|
||||
addJSONData(service.getJSON(deploymentUnit), warMetaData);
|
||||
if (loginConfig != null) {
|
||||
loginConfig.setAuthMethod("KEYCLOAK");
|
||||
loginConfig.setRealmName(service.getRealmName(deploymentName));
|
||||
loginConfig.setRealmName(service.getRealmName(deploymentUnit));
|
||||
} else {
|
||||
log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentName + " (loginConfig == null)");
|
||||
log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentUnit.getName() + " (loginConfig == null)");
|
||||
}
|
||||
}
|
||||
addValve(webMetaData);
|
||||
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
|
||||
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+44
-5
@@ -17,9 +17,12 @@
|
||||
|
||||
package org.keycloak.subsystem.as7;
|
||||
|
||||
import org.jboss.as.server.deployment.DeploymentUnit;
|
||||
import org.jboss.as.web.deployment.WarMetaData;
|
||||
import org.jboss.dmr.ModelNode;
|
||||
import org.jboss.dmr.Property;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -154,14 +157,23 @@ public final class KeycloakAdapterConfigService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getRealmName(String deploymentName) {
|
||||
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||
public String getRealmName(DeploymentUnit deploymentUnit) {
|
||||
ModelNode deployment = getSecureDeployment(deploymentUnit);
|
||||
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||
|
||||
}
|
||||
|
||||
public String getJSON(String deploymentName) {
|
||||
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||
protected boolean isDeploymentConfigured(DeploymentUnit deploymentUnit) {
|
||||
ModelNode deployment = getSecureDeployment(deploymentUnit);
|
||||
if (! deployment.isDefined()) {
|
||||
return false;
|
||||
}
|
||||
ModelNode resource = deployment.get(SecureDeploymentDefinition.RESOURCE.getName());
|
||||
return resource.isDefined();
|
||||
}
|
||||
|
||||
public String getJSON(DeploymentUnit deploymentUnit) {
|
||||
ModelNode deployment = getSecureDeployment(deploymentUnit);
|
||||
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||
ModelNode realm = this.realms.get(realmName);
|
||||
|
||||
@@ -184,9 +196,36 @@ public final class KeycloakAdapterConfigService {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSecureDeployment(String deploymentName) {
|
||||
public boolean isSecureDeployment(DeploymentUnit deploymentUnit) {
|
||||
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
||||
|
||||
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||
return this.secureDeployments.containsKey(deploymentName);
|
||||
}
|
||||
|
||||
private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) {
|
||||
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||
return this.secureDeployments.containsKey(deploymentName)
|
||||
? this.secureDeployments.get(deploymentName)
|
||||
: new ModelNode();
|
||||
}
|
||||
|
||||
// KEYCLOAK-3273: prefer module name if available
|
||||
private String preferredDeploymentName(DeploymentUnit deploymentUnit) {
|
||||
String deploymentName = deploymentUnit.getName();
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return deploymentName;
|
||||
}
|
||||
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return deploymentName;
|
||||
}
|
||||
|
||||
String moduleName = webMetaData.getModuleName();
|
||||
if (moduleName != null) return moduleName + ".war";
|
||||
|
||||
return deploymentName;
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -46,8 +46,7 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||
|
||||
String deploymentName = deploymentUnit.getName();
|
||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
|
||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentUnit)) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return;
|
||||
|
||||
-1
@@ -25,7 +25,6 @@ import org.jboss.as.server.AbstractDeploymentChainStep;
|
||||
import org.jboss.as.server.DeploymentProcessorTarget;
|
||||
import org.jboss.as.server.deployment.Phase;
|
||||
import org.jboss.dmr.ModelNode;
|
||||
|
||||
import org.jboss.msc.service.ServiceController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||
credentialsToAdd.add(addCredential);
|
||||
} else {
|
||||
for (Map.Entry<String, String> entry : values.entrySet()) {
|
||||
ModelNode addCredential = getCredentialToAdd(parent, name + "" + entry.getKey(), entry.getValue());
|
||||
ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue());
|
||||
credentialsToAdd.add(addCredential);
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -23,8 +23,6 @@ import org.jboss.as.controller.OperationContext;
|
||||
import org.jboss.as.controller.OperationFailedException;
|
||||
import org.jboss.dmr.ModelNode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Update an attribute on a realm.
|
||||
*
|
||||
|
||||
+22
@@ -23,6 +23,7 @@ import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
|
||||
import org.jboss.as.controller.SimpleResourceDefinition;
|
||||
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
|
||||
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
|
||||
import org.jboss.as.controller.operations.validation.IntRangeValidator;
|
||||
import org.jboss.as.controller.operations.validation.StringLengthValidator;
|
||||
import org.jboss.as.controller.registry.ManagementResourceRegistration;
|
||||
import org.jboss.as.controller.registry.OperationEntry;
|
||||
@@ -79,6 +80,24 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition TURN_OFF_CHANGE_SESSION =
|
||||
new SimpleAttributeDefinitionBuilder("turn-off-change-session-id-on-login", ModelType.BOOLEAN, true)
|
||||
.setXmlName("turn-off-change-session-id-on-login")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition TOKEN_MINIMUM_TIME_TO_LIVE =
|
||||
new SimpleAttributeDefinitionBuilder("token-minimum-time-to-live", ModelType.INT, true)
|
||||
.setXmlName("token-minimum-time-to-live")
|
||||
.setValidator(new IntRangeValidator(-1, true))
|
||||
.setAllowExpression(true)
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition MIN_TIME_BETWEEN_JWKS_REQUESTS =
|
||||
new SimpleAttributeDefinitionBuilder("min-time-between-jwks-requests", ModelType.INT, true)
|
||||
.setXmlName("min-time-between-jwks-requests")
|
||||
.setValidator(new IntRangeValidator(-1, true))
|
||||
.setAllowExpression(true)
|
||||
.build();
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
static {
|
||||
@@ -88,6 +107,9 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
|
||||
}
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
|
||||
+2
@@ -80,6 +80,8 @@ keycloak.secure-deployment.register-node-period=how often to re-register node
|
||||
keycloak.secure-deployment.token-store=cookie or session storage for auth session data
|
||||
keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
|
||||
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
|
||||
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
|
||||
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+5
-3
@@ -19,11 +19,11 @@ package org.keycloak.adapters.installed;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
@@ -119,6 +119,7 @@ public class KeycloakInstalled {
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.queryParam(OAuth2Constants.STATE, state)
|
||||
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
|
||||
.build().toString();
|
||||
|
||||
Desktop.getDesktop().browse(new URI(authUrl));
|
||||
@@ -175,6 +176,7 @@ public class KeycloakInstalled {
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
|
||||
.build().toString();
|
||||
|
||||
printer.println("Open the following URL in a browser. After login copy/paste the code back and press <enter>");
|
||||
@@ -211,7 +213,7 @@ public class KeycloakInstalled {
|
||||
refreshToken = tokenResponse.getRefreshToken();
|
||||
idTokenString = tokenResponse.getIdToken();
|
||||
|
||||
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
if (idTokenString != null) {
|
||||
try {
|
||||
JWSInput input = new JWSInput(idTokenString);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+2
-3
@@ -20,8 +20,6 @@ package org.keycloak.jaxrs;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||
import org.keycloak.adapters.BasicAuthRequestAuthenticator;
|
||||
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
|
||||
@@ -31,6 +29,8 @@ import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||
import org.keycloak.common.constants.GenericConstants;
|
||||
|
||||
@@ -40,7 +40,6 @@ import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.container.PreMatching;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
+9
-10
@@ -17,22 +17,21 @@
|
||||
|
||||
package org.keycloak.jaxrs;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.security.cert.X509Certificate;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.AuthenticationError;
|
||||
import org.keycloak.adapters.spi.LogoutError;
|
||||
import org.keycloak.common.util.HostUtils;
|
||||
|
||||
import javax.security.cert.X509Certificate;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
||||
+4
-4
@@ -20,6 +20,7 @@ package org.keycloak.jaxrs;
|
||||
import org.keycloak.AbstractOAuthClient;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.InternalServerErrorException;
|
||||
@@ -85,14 +86,13 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||
}
|
||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
||||
String state = getStateCode();
|
||||
String scopeParam = TokenUtil.attachOIDCScope(scope);
|
||||
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, clientId)
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.queryParam(OAuth2Constants.STATE, state);
|
||||
if (scope != null) {
|
||||
uriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
||||
}
|
||||
.queryParam(OAuth2Constants.STATE, state)
|
||||
.queryParam(OAuth2Constants.SCOPE, scopeParam);
|
||||
|
||||
URI url = uriBuilder.build();
|
||||
|
||||
|
||||
+7
-8
@@ -17,18 +17,17 @@
|
||||
|
||||
package org.keycloak.jaxrs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Logger;
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.common.constants.GenericConstants;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
import javax.ws.rs.Priorities;
|
||||
import javax.ws.rs.container.PreMatching;
|
||||
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.common.constants.GenericConstants;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Variant of JaxrsBearerTokenFilter, which can be used to properly use resources from current osgi bundle
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+7
-10
@@ -27,28 +27,26 @@ import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
|
||||
import org.eclipse.jetty.server.Authentication;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
@@ -61,7 +59,6 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -340,13 +337,13 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
|
||||
Authentication authentication = request.getAuthentication();
|
||||
if (!(authentication instanceof KeycloakAuthentication)) {
|
||||
UserIdentity userIdentity = createIdentity(principal);
|
||||
authentication = createAuthentication(userIdentity);
|
||||
authentication = createAuthentication(userIdentity, request);
|
||||
request.setAuthentication(authentication);
|
||||
}
|
||||
return authentication;
|
||||
}
|
||||
|
||||
protected abstract Authentication createAuthentication(UserIdentity userIdentity);
|
||||
protected abstract Authentication createAuthentication(UserIdentity userIdentity, Request request);
|
||||
|
||||
public static abstract class KeycloakAuthentication extends UserAuthentication {
|
||||
public KeycloakAuthentication(String method, UserIdentity userIdentity) {
|
||||
|
||||
+1
-1
@@ -23,11 +23,11 @@ import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.CookieTokenStore;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* Handle storage of token info in cookie. Per-request object.
|
||||
|
||||
+1
-1
@@ -23,12 +23,12 @@ import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.security.Principal;
|
||||
|
||||
+1
-1
@@ -21,13 +21,13 @@ import org.eclipse.jetty.server.Request;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+1
-1
@@ -20,8 +20,8 @@ package org.keycloak.adapters.jetty;
|
||||
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Authentication createAuthentication(UserIdentity userIdentity) {
|
||||
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) {
|
||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||
@Override
|
||||
public void logout() {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+1
-1
@@ -21,8 +21,8 @@ import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Authentication createAuthentication(UserIdentity userIdentity) {
|
||||
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) {
|
||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||
@Override
|
||||
public void logout() {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+1
-1
@@ -21,8 +21,8 @@ import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Authentication createAuthentication(UserIdentity userIdentity) {
|
||||
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) {
|
||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||
@Override
|
||||
public void logout() {
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-jetty93-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9.3.x Integration</name>
|
||||
<properties>
|
||||
<jetty9.version>9.3.9.v20160517</jetty9.version>
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.jetty.*
|
||||
</keycloak.osgi.export>
|
||||
<keycloak.osgi.import>
|
||||
org.eclipse.jetty.*;resolution:=optional,
|
||||
javax.servlet.*;version="[3.0,4)";resolution:=optional,
|
||||
org.keycloak.*;version="${project.version}",
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
</properties>
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundle-manifest</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-ClassPath>.</Bundle-ClassPath>
|
||||
<Bundle-Name>${project.name}</Bundle-Name>
|
||||
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
||||
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
||||
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.jetty;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Jetty93RequestAuthenticator extends JettyRequestAuthenticator {
|
||||
public Jetty93RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
|
||||
super(facade, deployment, tokenStore, sslRedirectPort, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
Request request = this.request;
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null) {
|
||||
return request.getSession(true).getId();
|
||||
}
|
||||
if (!deployment.isTurnOffChangeSessionIdOnLogin()) return request.changeSessionId();
|
||||
else return session.getId();
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.jetty;
|
||||
|
||||
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class JettyAdapterSessionStore implements AdapterSessionStore {
|
||||
public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
|
||||
protected Request myRequest;
|
||||
|
||||
public JettyAdapterSessionStore(Request request) {
|
||||
this.myRequest = request; // for IDE/compilation purposes
|
||||
}
|
||||
|
||||
protected MultiMap<String> extractFormParameters(Request base_request) {
|
||||
MultiMap<String> formParameters = new MultiMap<String>();
|
||||
base_request.extractFormParameters(formParameters);
|
||||
return formParameters;
|
||||
}
|
||||
protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
|
||||
base_request.setContentParameters(j_post);
|
||||
}
|
||||
|
||||
public boolean restoreRequest() {
|
||||
HttpSession session = myRequest.getSession(false);
|
||||
if (session == null) return false;
|
||||
synchronized (session) {
|
||||
String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
|
||||
if (j_uri != null) {
|
||||
// check if the request is for the same url as the original and restore
|
||||
// params if it was a post
|
||||
StringBuffer buf = myRequest.getRequestURL();
|
||||
if (myRequest.getQueryString() != null)
|
||||
buf.append("?").append(myRequest.getQueryString());
|
||||
if (j_uri.equals(buf.toString())) {
|
||||
String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
|
||||
myRequest.setMethod(method);
|
||||
MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
|
||||
if (j_post != null) {
|
||||
myRequest.setContentType("application/x-www-form-urlencoded");
|
||||
MultiMap<String> map = new MultiMap<String>();
|
||||
for (String key : j_post.keySet()) {
|
||||
for (String val : j_post.getList(key)) {
|
||||
map.add(key, val);
|
||||
}
|
||||
}
|
||||
restoreFormParameters(map, myRequest);
|
||||
}
|
||||
session.removeAttribute(FormAuthenticator.__J_URI);
|
||||
session.removeAttribute(JettyHttpFacade.__J_METHOD);
|
||||
session.removeAttribute(FormAuthenticator.__J_POST);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void saveRequest() {
|
||||
// remember the current URI
|
||||
HttpSession session = myRequest.getSession();
|
||||
synchronized (session) {
|
||||
// But only if it is not set already, or we save every uri that leads to a login form redirect
|
||||
if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
|
||||
StringBuffer buf = myRequest.getRequestURL();
|
||||
if (myRequest.getQueryString() != null)
|
||||
buf.append("?").append(myRequest.getQueryString());
|
||||
session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
|
||||
session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
|
||||
|
||||
if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
|
||||
MultiMap<String> formParameters = extractFormParameters(myRequest);
|
||||
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
|
||||
for (String key : formParameters.keySet()) {
|
||||
for (Object value : formParameters.getValues(key)) {
|
||||
map.add(key, (String) value);
|
||||
}
|
||||
}
|
||||
session.setAttribute(CACHED_FORM_PARAMETERS, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.jetty;
|
||||
|
||||
import org.eclipse.jetty.server.Authentication;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
|
||||
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
|
||||
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
|
||||
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticator {
|
||||
|
||||
public KeycloakJettyAuthenticator() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Request resolveRequest(ServletRequest req) {
|
||||
return Request.getBaseRequest(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Authentication createAuthentication(UserIdentity userIdentity, final Request request) {
|
||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||
@Override
|
||||
public void logout() {
|
||||
logoutCurrent(request);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
|
||||
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
|
||||
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
|
||||
return new Jetty93RequestAuthenticator(facade, deployment, tokenStore, -1, request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Jetty Integration</name>
|
||||
@@ -33,7 +33,8 @@
|
||||
<modules>
|
||||
<module>jetty-core</module>
|
||||
<module>jetty8.1</module>
|
||||
<module>jetty9.2</module>
|
||||
<module>jetty9.1</module>
|
||||
<module>jetty9.2</module>
|
||||
<module>jetty9.3</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
(function( window, undefined ) {
|
||||
|
||||
|
||||
var KeycloakAuthorization = function (keycloak) {
|
||||
var _instance = this;
|
||||
this.rpt = null;
|
||||
@@ -49,7 +49,7 @@
|
||||
*/
|
||||
this.authorize = function (wwwAuthenticateHeader) {
|
||||
this.then = function (onGrant, onDeny, onError) {
|
||||
if (wwwAuthenticateHeader.startsWith('UMA')) {
|
||||
if (wwwAuthenticateHeader.indexOf('UMA') != -1) {
|
||||
var params = wwwAuthenticateHeader.split(',');
|
||||
|
||||
for (i = 0; i < params.length; i++) {
|
||||
@@ -96,7 +96,7 @@
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if (wwwAuthenticateHeader.startsWith('KC_ETT')) {
|
||||
} else if (wwwAuthenticateHeader.indexOf('KC_ETT') != -1) {
|
||||
var params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(',');
|
||||
var clientId = null;
|
||||
|
||||
@@ -112,44 +112,44 @@
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
|
||||
*/
|
||||
this.entitlement = function (resourceSeververId) {
|
||||
this.then = function (onGrant, onDeny, onError) {
|
||||
var request = new XMLHttpRequest();
|
||||
return this;
|
||||
};
|
||||
|
||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
||||
/**
|
||||
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
|
||||
*/
|
||||
this.entitlement = function (resourceSeververId) {
|
||||
this.then = function (onGrant, onDeny, onError) {
|
||||
var request = new XMLHttpRequest();
|
||||
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
var status = request.status;
|
||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
var rpt = JSON.parse(request.responseText).rpt;
|
||||
_instance.rpt = rpt;
|
||||
onGrant(rpt);
|
||||
} else if (status == 403) {
|
||||
if (onDeny) {
|
||||
onDeny();
|
||||
} else {
|
||||
console.error('Authorization request was denied by the server.');
|
||||
}
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
var status = request.status;
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
var rpt = JSON.parse(request.responseText).rpt;
|
||||
_instance.rpt = rpt;
|
||||
onGrant(rpt);
|
||||
} else if (status == 403) {
|
||||
if (onDeny) {
|
||||
onDeny();
|
||||
} else {
|
||||
if (onError) {
|
||||
onError();
|
||||
} else {
|
||||
console.error('Could not obtain authorization data from server.');
|
||||
}
|
||||
console.error('Authorization request was denied by the server.');
|
||||
}
|
||||
} else {
|
||||
if (onError) {
|
||||
onError();
|
||||
} else {
|
||||
console.error('Could not obtain authorization data from server.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
request.send(null);
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
request.send(null);
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
@@ -25,21 +25,21 @@
|
||||
var kc = this;
|
||||
var adapter;
|
||||
var refreshQueue = [];
|
||||
var storage;
|
||||
var callbackStorage;
|
||||
|
||||
var loginIframe = {
|
||||
enable: true,
|
||||
callbackMap: [],
|
||||
callbackList: [],
|
||||
interval: 5
|
||||
};
|
||||
|
||||
kc.init = function (initOptions) {
|
||||
kc.authenticated = false;
|
||||
|
||||
storage = new PersistentStorage();
|
||||
callbackStorage = createCallbackStorage();
|
||||
|
||||
if (initOptions && initOptions.adapter === 'cordova') {
|
||||
adapter = loadAdapter('cordova');
|
||||
adapter = loadAdapter('cordova');
|
||||
} else if (initOptions && initOptions.adapter === 'default') {
|
||||
adapter = loadAdapter();
|
||||
} else {
|
||||
@@ -103,8 +103,8 @@
|
||||
initPromise.promise.success(function() {
|
||||
kc.onReady && kc.onReady(kc.authenticated);
|
||||
promise.setSuccess(kc.authenticated);
|
||||
}).error(function() {
|
||||
promise.setError();
|
||||
}).error(function(errorData) {
|
||||
promise.setError(errorData);
|
||||
});
|
||||
|
||||
var configPromise = loadConfig(config);
|
||||
@@ -154,21 +154,34 @@
|
||||
return;
|
||||
} else if (initOptions) {
|
||||
if (initOptions.token || initOptions.refreshToken) {
|
||||
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false);
|
||||
kc.timeSkew = initOptions.timeSkew || 0;
|
||||
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
||||
|
||||
if (loginIframe.enable) {
|
||||
setupCheckLoginIframe().success(function() {
|
||||
checkLoginIframe().success(function () {
|
||||
kc.onAuthSuccess && kc.onAuthSuccess();
|
||||
initPromise.setSuccess();
|
||||
}).error(function () {
|
||||
kc.onAuthError && kc.onAuthError();
|
||||
if (initOptions.onLoad) {
|
||||
onLoad();
|
||||
} else {
|
||||
initPromise.setError();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
initPromise.setSuccess();
|
||||
kc.updateToken(-1).success(function() {
|
||||
kc.onAuthSuccess && kc.onAuthSuccess();
|
||||
initPromise.setSuccess();
|
||||
}).error(function() {
|
||||
kc.onAuthError && kc.onAuthError();
|
||||
if (initOptions.onLoad) {
|
||||
onLoad();
|
||||
} else {
|
||||
initPromise.setError();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (initOptions.onLoad) {
|
||||
onLoad();
|
||||
@@ -201,13 +214,15 @@
|
||||
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
|
||||
}
|
||||
|
||||
storage.setItem('oauthState', JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) }));
|
||||
callbackStorage.add({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
|
||||
|
||||
var action = 'auth';
|
||||
if (options && options.action == 'register') {
|
||||
action = 'registrations';
|
||||
}
|
||||
|
||||
var scope = (options && options.scope) ? "openid " + options.scope : "openid";
|
||||
|
||||
var url = getRealmUrl()
|
||||
+ '/protocol/openid-connect/' + action
|
||||
+ '?client_id=' + encodeURIComponent(kc.clientId)
|
||||
@@ -215,12 +230,17 @@
|
||||
+ '&state=' + encodeURIComponent(state)
|
||||
+ '&nonce=' + encodeURIComponent(nonce)
|
||||
+ '&response_mode=' + encodeURIComponent(kc.responseMode)
|
||||
+ '&response_type=' + encodeURIComponent(kc.responseType);
|
||||
+ '&response_type=' + encodeURIComponent(kc.responseType)
|
||||
+ '&scope=' + encodeURIComponent(scope);
|
||||
|
||||
if (options && options.prompt) {
|
||||
url += '&prompt=' + encodeURIComponent(options.prompt);
|
||||
}
|
||||
|
||||
if (options && options.maxAge) {
|
||||
url += '&max_age=' + encodeURIComponent(options.maxAge);
|
||||
}
|
||||
|
||||
if (options && options.loginHint) {
|
||||
url += '&login_hint=' + encodeURIComponent(options.loginHint);
|
||||
}
|
||||
@@ -229,10 +249,6 @@
|
||||
url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
|
||||
}
|
||||
|
||||
if (options && options.scope) {
|
||||
url += '&scope=' + encodeURIComponent(options.scope);
|
||||
}
|
||||
|
||||
if (options && options.locale) {
|
||||
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
||||
}
|
||||
@@ -346,11 +362,10 @@
|
||||
throw 'Not authenticated';
|
||||
}
|
||||
|
||||
var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew;
|
||||
var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew;
|
||||
if (minValidity) {
|
||||
expiresIn -= minValidity;
|
||||
}
|
||||
|
||||
return expiresIn < 0;
|
||||
}
|
||||
|
||||
@@ -365,7 +380,20 @@
|
||||
minValidity = minValidity || 5;
|
||||
|
||||
var exec = function() {
|
||||
if (!kc.isTokenExpired(minValidity)) {
|
||||
var refreshToken = false;
|
||||
if (kc.timeSkew == -1) {
|
||||
console.info('Skew ' + kc.timeSkew);
|
||||
refreshToken = true;
|
||||
console.info('[KEYCLOAK] Refreshing token: time skew not set');
|
||||
} else if (minValidity == -1) {
|
||||
refreshToken = true;
|
||||
console.info('[KEYCLOAK] Refreshing token: forced refresh');
|
||||
} else if (kc.isTokenExpired(minValidity)) {
|
||||
refreshToken = true;
|
||||
console.info('[KEYCLOAK] Refreshing token: token expired');
|
||||
}
|
||||
|
||||
if (!refreshToken) {
|
||||
promise.setSuccess(false);
|
||||
} else {
|
||||
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
|
||||
@@ -377,6 +405,7 @@
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('POST', url, true);
|
||||
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
req.withCredentials = true;
|
||||
|
||||
if (kc.clientId && kc.clientSecret) {
|
||||
req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret));
|
||||
@@ -389,18 +418,21 @@
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
console.info('[KEYCLOAK] Token refreshed');
|
||||
|
||||
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
||||
|
||||
var tokenResponse = JSON.parse(req.responseText);
|
||||
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true);
|
||||
|
||||
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
||||
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal);
|
||||
|
||||
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
|
||||
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
||||
p.setSuccess(true);
|
||||
}
|
||||
} else {
|
||||
console.warn('[KEYCLOAK] Failed to refresh token');
|
||||
|
||||
kc.onAuthRefreshError && kc.onAuthRefreshError();
|
||||
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
||||
p.setError(true);
|
||||
@@ -430,7 +462,7 @@
|
||||
|
||||
kc.clearToken = function() {
|
||||
if (kc.token) {
|
||||
setToken(null, null, null, true);
|
||||
setToken(null, null, null);
|
||||
kc.onAuthLogout && kc.onAuthLogout();
|
||||
if (kc.loginRequired) {
|
||||
kc.login();
|
||||
@@ -463,8 +495,9 @@
|
||||
|
||||
if (error) {
|
||||
if (prompt != 'none') {
|
||||
kc.onAuthError && kc.onAuthError();
|
||||
promise && promise.setError();
|
||||
var errorData = { error: error, error_description: oauth.error_description };
|
||||
kc.onAuthError && kc.onAuthError(errorData);
|
||||
promise && promise.setError(errorData);
|
||||
} else {
|
||||
promise && promise.setSuccess();
|
||||
}
|
||||
@@ -510,18 +543,16 @@
|
||||
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
|
||||
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
||||
|
||||
setToken(accessToken, refreshToken, idToken, true);
|
||||
setToken(accessToken, refreshToken, idToken, timeLocal);
|
||||
|
||||
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
|
||||
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
|
||||
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
|
||||
|
||||
console.log('invalid nonce!');
|
||||
console.info('[KEYCLOAK] Invalid nonce, clearing token');
|
||||
kc.clearToken();
|
||||
promise && promise.setError();
|
||||
} else {
|
||||
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
||||
|
||||
if (fulfillPromise) {
|
||||
kc.onAuthSuccess && kc.onAuthSuccess();
|
||||
promise && promise.setSuccess();
|
||||
@@ -594,7 +625,7 @@
|
||||
return promise.promise;
|
||||
}
|
||||
|
||||
function setToken(token, refreshToken, idToken, useTokenTime) {
|
||||
function setToken(token, refreshToken, idToken, timeLocal) {
|
||||
if (kc.tokenTimeoutHandle) {
|
||||
clearTimeout(kc.tokenTimeoutHandle);
|
||||
kc.tokenTimeoutHandle = null;
|
||||
@@ -613,10 +644,24 @@
|
||||
kc.realmAccess = kc.tokenParsed.realm_access;
|
||||
kc.resourceAccess = kc.tokenParsed.resource_access;
|
||||
|
||||
if (timeLocal) {
|
||||
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
||||
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
|
||||
} else {
|
||||
kc.timeSkew = -1;
|
||||
}
|
||||
|
||||
if (kc.onTokenExpired) {
|
||||
var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000);
|
||||
var expiresIn = kc.tokenParsed.exp - start;
|
||||
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000);
|
||||
if (kc.timeSkew == -1) {
|
||||
kc.onTokenExpired();
|
||||
} else {
|
||||
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
|
||||
if (expiresIn <= 0) {
|
||||
kc.onTokenExpired();
|
||||
} else {
|
||||
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -697,15 +742,11 @@
|
||||
|
||||
function parseCallback(url) {
|
||||
var oauth = new CallbackParser(url, kc.responseMode).parseUri();
|
||||
var oauthState = callbackStorage.get(oauth.state);
|
||||
|
||||
var oauthState = storage.getItem('oauthState');
|
||||
var sessionState = oauthState && JSON.parse(oauthState);
|
||||
|
||||
if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
|
||||
storage.removeItem('oauthState');
|
||||
|
||||
oauth.redirectUri = sessionState.redirectUri;
|
||||
oauth.storedNonce = sessionState.nonce;
|
||||
if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) {
|
||||
oauth.redirectUri = oauthState.redirectUri;
|
||||
oauth.storedNonce = oauthState.nonce;
|
||||
|
||||
if (oauth.fragment) {
|
||||
oauth.newUrl += '#' + oauth.fragment;
|
||||
@@ -783,7 +824,7 @@
|
||||
setTimeout(check, loginIframe.interval * 1000);
|
||||
}
|
||||
|
||||
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html?client_id=' + encodeURIComponent(kc.clientId) + '&origin=' + getOrigin();
|
||||
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
|
||||
iframe.setAttribute('src', src );
|
||||
iframe.style.display = 'none';
|
||||
document.body.appendChild(iframe);
|
||||
@@ -792,17 +833,22 @@
|
||||
if (event.origin !== loginIframe.iframeOrigin) {
|
||||
return;
|
||||
}
|
||||
var data = JSON.parse(event.data);
|
||||
var promise = loginIframe.callbackMap[data.callbackId];
|
||||
delete loginIframe.callbackMap[data.callbackId];
|
||||
|
||||
if ((!kc.sessionId || kc.sessionId == data.session) && data.loggedIn) {
|
||||
promise.setSuccess();
|
||||
} else {
|
||||
if (event.data != "unchanged") {
|
||||
kc.clearToken();
|
||||
promise.setError();
|
||||
}
|
||||
|
||||
for (var i = loginIframe.callbackList.length - 1; i >= 0; --i) {
|
||||
var promise = loginIframe.callbackList[i];
|
||||
if (event.data == "unchanged") {
|
||||
promise.setSuccess();
|
||||
} else {
|
||||
promise.setError();
|
||||
}
|
||||
loginIframe.callbackList.splice(i, 1);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', messageCallback, false);
|
||||
|
||||
var check = function() {
|
||||
@@ -818,12 +864,13 @@
|
||||
function checkLoginIframe() {
|
||||
var promise = createPromise();
|
||||
|
||||
if (loginIframe.iframe && loginIframe.iframeOrigin) {
|
||||
var msg = {};
|
||||
msg.callbackId = createCallbackId();
|
||||
loginIframe.callbackMap[msg.callbackId] = promise;
|
||||
if (loginIframe.iframe && loginIframe.iframeOrigin ) {
|
||||
var msg = kc.clientId + ' ' + kc.sessionId;
|
||||
loginIframe.callbackList.push(promise);
|
||||
var origin = loginIframe.iframeOrigin;
|
||||
loginIframe.iframe.contentWindow.postMessage(JSON.stringify(msg), origin);
|
||||
if (loginIframe.callbackList.length == 1) {
|
||||
loginIframe.iframe.contentWindow.postMessage(msg, origin);
|
||||
}
|
||||
} else {
|
||||
promise.setSuccess();
|
||||
}
|
||||
@@ -982,60 +1029,93 @@
|
||||
throw 'invalid adapter type: ' + type;
|
||||
}
|
||||
|
||||
|
||||
var PersistentStorage = function() {
|
||||
if (!(this instanceof PersistentStorage)) {
|
||||
return new PersistentStorage();
|
||||
var LocalStorage = function() {
|
||||
if (!(this instanceof LocalStorage)) {
|
||||
return new LocalStorage();
|
||||
}
|
||||
var ps = this;
|
||||
var useCookieStorage = function () {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return true;
|
||||
|
||||
localStorage.setItem('kc-test', 'test');
|
||||
localStorage.removeItem('kc-test');
|
||||
|
||||
var cs = this;
|
||||
|
||||
function clearExpired() {
|
||||
var time = new Date().getTime();
|
||||
for (var i = 1; i <= localStorage.length; i++) {
|
||||
var key = localStorage.key(i);
|
||||
if (key && key.indexOf('kc-callback-') == 0) {
|
||||
var value = localStorage.getItem(key);
|
||||
if (value) {
|
||||
try {
|
||||
var expires = JSON.parse(value).expires;
|
||||
if (!expires || expires < time) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
} catch (err) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
var key = '@@keycloak-session-storage/test';
|
||||
localStorage.setItem(key, 'test');
|
||||
}
|
||||
|
||||
cs.get = function(state) {
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
var key = 'kc-callback-' + state;
|
||||
var value = localStorage.getItem(key);
|
||||
if (value) {
|
||||
localStorage.removeItem(key);
|
||||
return false;
|
||||
} catch (err) {
|
||||
// Probably in Safari "private mode" where localStorage
|
||||
// quota is 0, or quota exceeded. Switching to cookie
|
||||
// storage.
|
||||
return true;
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
|
||||
clearExpired();
|
||||
return value;
|
||||
};
|
||||
|
||||
cs.add = function(state) {
|
||||
clearExpired();
|
||||
|
||||
var key = 'kc-callback-' + state.state;
|
||||
state.expires = new Date().getTime() + (60 * 60 * 1000);
|
||||
localStorage.setItem(key, JSON.stringify(state));
|
||||
};
|
||||
};
|
||||
|
||||
var CookieStorage = function() {
|
||||
if (!(this instanceof CookieStorage)) {
|
||||
return new CookieStorage();
|
||||
}
|
||||
|
||||
ps.setItem = function(key, value) {
|
||||
if (useCookieStorage()) {
|
||||
setCookie(key, value, cookieExpiration(5));
|
||||
} else {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
var cs = this;
|
||||
|
||||
ps.getItem = function(key) {
|
||||
if (useCookieStorage()) {
|
||||
return getCookie(key);
|
||||
cs.get = function(state) {
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
|
||||
ps.removeItem = function(key) {
|
||||
if (typeof localStorage !== "undefined") {
|
||||
try {
|
||||
// Always try to delete from localStorage.
|
||||
localStorage.removeItem(key);
|
||||
} catch (err) { }
|
||||
var value = getCookie('kc-callback-' + state);
|
||||
setCookie('kc-callback-' + state, '', cookieExpiration(-100));
|
||||
if (value) {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
// Always remove the cookie.
|
||||
};
|
||||
|
||||
cs.add = function(state) {
|
||||
setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60));
|
||||
};
|
||||
|
||||
cs.removeItem = function(key) {
|
||||
setCookie(key, '', cookieExpiration(-100));
|
||||
}
|
||||
};
|
||||
|
||||
var cookieExpiration = function (minutes) {
|
||||
var exp = new Date();
|
||||
exp.setTime(exp.getTime() + (minutes*60*1000));
|
||||
return exp;
|
||||
}
|
||||
};
|
||||
|
||||
var getCookie = function (key) {
|
||||
var name = key + '=';
|
||||
@@ -1050,13 +1130,22 @@
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
var setCookie = function (key, value, expirationDate) {
|
||||
var cookie = key + '=' + value + '; '
|
||||
+ 'expires=' + expirationDate.toUTCString() + '; ';
|
||||
document.cookie = cookie;
|
||||
}
|
||||
};
|
||||
|
||||
function createCallbackStorage() {
|
||||
try {
|
||||
return new LocalStorage();
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
return new CookieStorage();
|
||||
}
|
||||
|
||||
var CallbackParser = function(uriToParse, responseMode) {
|
||||
@@ -1103,7 +1192,7 @@
|
||||
}
|
||||
|
||||
var handleQueryParam = function(paramName, paramValue, oauth) {
|
||||
var supportedOAuthParams = [ 'code', 'error', 'state' ];
|
||||
var supportedOAuthParams = [ 'code', 'state', 'error', 'error_description' ];
|
||||
|
||||
for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
|
||||
if (paramName === supportedOAuthParams[i]) {
|
||||
|
||||
@@ -15,10 +15,56 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
function getCookie(cname)
|
||||
var init;
|
||||
|
||||
function checkState(clientId, origin, sessionState, callback) {
|
||||
if (!init) {
|
||||
var req = new XMLHttpRequest();
|
||||
|
||||
var url = location.href + "/init";
|
||||
url += "?client_id=" + encodeURIComponent(clientId);
|
||||
url += "&origin=" + encodeURIComponent(origin);
|
||||
url += "&session_state=" + encodeURIComponent(sessionState);
|
||||
|
||||
req.open('GET', url, true);
|
||||
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 204) {
|
||||
init = {
|
||||
clientId: clientId,
|
||||
origin: origin
|
||||
}
|
||||
callback('unchanged');
|
||||
} else if (req.status = 404) {
|
||||
callback('changed');
|
||||
} else {
|
||||
callback('error');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
req.send();
|
||||
} else {
|
||||
if (clientId == init.clientId && origin == init.origin) {
|
||||
var cookie = getCookie();
|
||||
if (sessionState == cookie) {
|
||||
callback('unchanged');
|
||||
} else {
|
||||
callback('changed');
|
||||
}
|
||||
} else {
|
||||
callback('error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCookie()
|
||||
{
|
||||
var name = cname + "=";
|
||||
var name = 'KEYCLOAK_SESSION=';
|
||||
var ca = document.cookie.split(';');
|
||||
for(var i=0; i<ca.length; i++)
|
||||
{
|
||||
@@ -27,23 +73,24 @@
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function receiveMessage(event)
|
||||
{
|
||||
if (event.origin !== "ORIGIN") {
|
||||
console.log(event.origin + " does not match built origin");
|
||||
return;
|
||||
|
||||
}
|
||||
var data = JSON.parse(event.data);
|
||||
data.loggedIn = false;
|
||||
var cookie = getCookie('KEYCLOAK_SESSION');
|
||||
if (cookie) {
|
||||
data.loggedIn = true;
|
||||
data.session = cookie;
|
||||
var origin = event.origin;
|
||||
var data = event.data.split(' ');
|
||||
if (data.length != 2) {
|
||||
event.source.postMessage('error', origin);
|
||||
}
|
||||
|
||||
event.source.postMessage(JSON.stringify(data),
|
||||
event.origin);
|
||||
var clientId = data[0];
|
||||
var sessionState = data[1];
|
||||
|
||||
checkState(clientId, event.origin, sessionState, function(result) {
|
||||
event.source.postMessage(result, origin);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("message", receiveMessage, false);
|
||||
</script>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+5
-5
@@ -16,6 +16,11 @@
|
||||
*/
|
||||
package org.keycloak.adapters.osgi;
|
||||
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -23,11 +28,6 @@ import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
|
||||
public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
|
||||
|
||||
private final Map<String, KeycloakDeployment> cache = new ConcurrentHashMap<String, KeycloakDeployment>();
|
||||
|
||||
+5
-5
@@ -17,11 +17,6 @@
|
||||
|
||||
package org.keycloak.adapters.osgi;
|
||||
|
||||
import java.net.URL;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.security.ConstraintMapping;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.jboss.logging.Logger;
|
||||
@@ -32,6 +27,11 @@ import org.osgi.service.http.HttpContext;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
||||
|
||||
import java.net.URL;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Integration with pax-web, which allows to inject custom jetty-web.xml configuration from current bundle classpath into {@link WebContainer}
|
||||
* and allows to inject custom security constraint for securing resources by Keycloak.
|
||||
|
||||
+8
-9
@@ -17,15 +17,6 @@
|
||||
|
||||
package org.keycloak.adapters.osgi;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Dictionary;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
|
||||
import org.apache.cxf.transport.http.DestinationRegistry;
|
||||
import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
|
||||
import org.jboss.logging.Logger;
|
||||
@@ -38,6 +29,14 @@ import org.osgi.service.http.HttpContext;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
import java.util.Arrays;
|
||||
import java.util.Dictionary;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Service, which allows to remove previously registered servlets in karaf/fuse environment. It assumes that particular servlet was previously
|
||||
* registered as service in OSGI container under {@link javax.servlet.Servlet} interface.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak OIDC Client Adapter Modules</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+50
-11
@@ -18,15 +18,15 @@
|
||||
package org.keycloak.adapters.servlet;
|
||||
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||
|
||||
@@ -39,7 +39,6 @@ import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@@ -47,6 +46,7 @@ import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
@@ -56,10 +56,18 @@ public class KeycloakOIDCFilter implements Filter {
|
||||
protected AdapterDeploymentContext deploymentContext;
|
||||
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
|
||||
protected NodesRegistrationManagement nodesRegistrationManagement;
|
||||
protected Pattern skipPattern;
|
||||
|
||||
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
|
||||
|
||||
@Override
|
||||
public void init(final FilterConfig filterConfig) throws ServletException {
|
||||
|
||||
String skipPatternDefinition = filterConfig.getInitParameter("keycloak.config.skipPattern");
|
||||
if (skipPatternDefinition != null) {
|
||||
skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
|
||||
}
|
||||
|
||||
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
|
||||
if (configResolverClass != null) {
|
||||
try {
|
||||
@@ -85,13 +93,7 @@ public class KeycloakOIDCFilter implements Filter {
|
||||
if (pathParam != null) path = pathParam;
|
||||
is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||
}
|
||||
KeycloakDeployment kd;
|
||||
if (is == null) {
|
||||
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
||||
kd = new KeycloakDeployment();
|
||||
} else {
|
||||
kd = KeycloakDeploymentBuilder.build(is);
|
||||
}
|
||||
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
|
||||
deploymentContext = new AdapterDeploymentContext(kd);
|
||||
log.fine("Keycloak is using a per-deployment configuration.");
|
||||
}
|
||||
@@ -99,13 +101,30 @@ public class KeycloakOIDCFilter implements Filter {
|
||||
nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||
}
|
||||
|
||||
private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) {
|
||||
|
||||
if (is == null) {
|
||||
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
||||
return new KeycloakDeployment();
|
||||
}
|
||||
|
||||
return KeycloakDeploymentBuilder.build(is);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
log.fine("Keycloak OIDC Filter");
|
||||
//System.err.println("Keycloak OIDC Filter: " + ((HttpServletRequest)req).getRequestURL().toString());
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
|
||||
if (shouldSkip(request)) {
|
||||
chain.doFilter(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response);
|
||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||
if (deployment == null || !deployment.isConfigured()) {
|
||||
@@ -171,6 +190,26 @@ public class KeycloakOIDCFilter implements Filter {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether this {@link Filter} should skip the given {@link HttpServletRequest} based on the configured {@link KeycloakOIDCFilter#skipPattern}.
|
||||
* Patterns are matched against the {@link HttpServletRequest#getRequestURI() requestURI} of a request without the context-path.
|
||||
* A request for {@code /myapp/index.html} would be tested with {@code /index.html} against the skip pattern.
|
||||
* Skipped requests will not be processed further by {@link KeycloakOIDCFilter} and immediately delegated to the {@link FilterChain}.
|
||||
*
|
||||
* @param request the request to check
|
||||
* @return {@code true} if the request should not be handled,
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
private boolean shouldSkip(HttpServletRequest request) {
|
||||
|
||||
if (skipPattern == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String requestPath = request.getRequestURI().substring(request.getContextPath().length());
|
||||
return skipPattern.matcher(requestPath).matches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+2
-2
@@ -17,12 +17,12 @@
|
||||
|
||||
package org.keycloak.servlet;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.AbstractOAuthClient;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
||||
+6
-5
@@ -25,11 +25,12 @@ import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.adapters.spi.AuthenticationError;
|
||||
import org.keycloak.adapters.spi.LogoutError;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import javax.security.cert.X509Certificate;
|
||||
import javax.servlet.http.Cookie;
|
||||
@@ -91,15 +92,15 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
||||
String state = getStateCode();
|
||||
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
|
||||
String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString();
|
||||
String scopeParam = TokenUtil.attachOIDCScope(scope);
|
||||
|
||||
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl)
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, getClientId())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.queryParam(OAuth2Constants.STATE, state);
|
||||
if (scope != null) {
|
||||
uriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
||||
}
|
||||
.queryParam(OAuth2Constants.STATE, state)
|
||||
.queryParam(OAuth2Constants.SCOPE, scopeParam);
|
||||
|
||||
URI url = uriBuilder.build();
|
||||
|
||||
String stateCookiePath = this.stateCookiePath;
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ package org.keycloak.servlet;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -67,6 +67,45 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
<artifactId>undertow-servlet</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
<artifactId>undertow-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
||||
+208
-62
@@ -17,24 +17,38 @@
|
||||
|
||||
package org.keycloak.adapters.springboot;
|
||||
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.api.WebResourceCollection;
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||
import org.eclipse.jetty.security.ConstraintMapping;
|
||||
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
import org.keycloak.adapters.jetty.KeycloakJettyAuthenticator;
|
||||
import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
|
||||
import org.keycloak.adapters.undertow.KeycloakServletExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
||||
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
|
||||
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
|
||||
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.context.embedded.undertow.UndertowDeploymentInfoCustomizer;
|
||||
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -61,80 +75,212 @@ public class KeycloakSpringBootConfiguration {
|
||||
return new EmbeddedServletContainerCustomizer() {
|
||||
@Override
|
||||
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
|
||||
|
||||
if (configurableEmbeddedServletContainer instanceof TomcatEmbeddedServletContainerFactory) {
|
||||
|
||||
TomcatEmbeddedServletContainerFactory container = (TomcatEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
||||
|
||||
container.addContextValves(new KeycloakAuthenticatorValve());
|
||||
container.addContextCustomizers(tomcatKeycloakContextCustomizer());
|
||||
|
||||
container.addContextCustomizers(getTomcatKeycloakContextCustomizer());
|
||||
} else if (configurableEmbeddedServletContainer instanceof UndertowEmbeddedServletContainerFactory) {
|
||||
throw new IllegalArgumentException("Undertow Keycloak integration is not yet implemented");
|
||||
|
||||
UndertowEmbeddedServletContainerFactory container = (UndertowEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
||||
container.addDeploymentInfoCustomizers(undertowKeycloakContextCustomizer());
|
||||
|
||||
} else if (configurableEmbeddedServletContainer instanceof JettyEmbeddedServletContainerFactory) {
|
||||
throw new IllegalArgumentException("Jetty Keycloak integration is not yet implemented");
|
||||
|
||||
JettyEmbeddedServletContainerFactory container = (JettyEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
||||
container.addServerCustomizers(jettyKeycloakServerCustomizer());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TomcatContextCustomizer getTomcatKeycloakContextCustomizer() {
|
||||
return new TomcatContextCustomizer() {
|
||||
@Override
|
||||
public void customize(Context context) {
|
||||
LoginConfig loginConfig = new LoginConfig();
|
||||
loginConfig.setAuthMethod("KEYCLOAK");
|
||||
context.setLoginConfig(loginConfig);
|
||||
|
||||
Set<String> authRoles = new HashSet<String>();
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
||||
for (String authRole : collection.getAuthRoles()) {
|
||||
if (!authRoles.contains(authRole)) {
|
||||
context.addSecurityRole(authRole);
|
||||
authRoles.add(authRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||
SecurityConstraint tomcatConstraint = new SecurityConstraint();
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
||||
SecurityCollection tomcatSecCollection = new SecurityCollection();
|
||||
|
||||
if (collection.getName() != null) {
|
||||
tomcatSecCollection.setName(collection.getName());
|
||||
}
|
||||
if (collection.getDescription() != null) {
|
||||
tomcatSecCollection.setDescription(collection.getDescription());
|
||||
}
|
||||
|
||||
for (String authRole : collection.getAuthRoles()) {
|
||||
tomcatConstraint.addAuthRole(authRole);
|
||||
}
|
||||
|
||||
for (String pattern : collection.getPatterns()) {
|
||||
tomcatSecCollection.addPattern(pattern);
|
||||
}
|
||||
|
||||
for (String method : collection.getMethods()) {
|
||||
tomcatSecCollection.addMethod(method);
|
||||
}
|
||||
|
||||
for (String method : collection.getOmittedMethods()) {
|
||||
tomcatSecCollection.addOmittedMethod(method);
|
||||
}
|
||||
|
||||
tomcatConstraint.addCollection(tomcatSecCollection);
|
||||
}
|
||||
|
||||
context.addConstraint(tomcatConstraint);
|
||||
}
|
||||
|
||||
context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName());
|
||||
}
|
||||
};
|
||||
@ConditionalOnClass(name = {"org.eclipse.jetty.webapp.WebAppContext"})
|
||||
public JettyServerCustomizer jettyKeycloakServerCustomizer() {
|
||||
return new KeycloakJettyServerCustomizer(keycloakProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = {"org.apache.catalina.startup.Tomcat"})
|
||||
public TomcatContextCustomizer tomcatKeycloakContextCustomizer() {
|
||||
return new KeycloakTomcatContextCustomizer(keycloakProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = {"io.undertow.Undertow"})
|
||||
public UndertowDeploymentInfoCustomizer undertowKeycloakContextCustomizer() {
|
||||
return new KeycloakUndertowDeploymentInfoCustomizer(keycloakProperties);
|
||||
}
|
||||
|
||||
static class KeycloakUndertowDeploymentInfoCustomizer implements UndertowDeploymentInfoCustomizer {
|
||||
|
||||
private final KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
public KeycloakUndertowDeploymentInfoCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||
this.keycloakProperties = keycloakProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(DeploymentInfo deploymentInfo) {
|
||||
|
||||
io.undertow.servlet.api.LoginConfig loginConfig = new io.undertow.servlet.api.LoginConfig(keycloakProperties.getRealm());
|
||||
loginConfig.addFirstAuthMethod("KEYCLOAK");
|
||||
|
||||
deploymentInfo.setLoginConfig(loginConfig);
|
||||
|
||||
deploymentInfo.addInitParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName());
|
||||
deploymentInfo.addSecurityConstraints(getSecurityConstraints());
|
||||
|
||||
deploymentInfo.addServletExtension(new KeycloakServletExtension());
|
||||
}
|
||||
|
||||
private List<io.undertow.servlet.api.SecurityConstraint> getSecurityConstraints() {
|
||||
|
||||
List<io.undertow.servlet.api.SecurityConstraint> undertowSecurityConstraints = new ArrayList<io.undertow.servlet.api.SecurityConstraint>();
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collectionDefinition : constraintDefinition.getSecurityCollections()) {
|
||||
|
||||
io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint();
|
||||
undertowSecurityConstraint.addRolesAllowed(collectionDefinition.getAuthRoles());
|
||||
|
||||
WebResourceCollection webResourceCollection = new WebResourceCollection();
|
||||
webResourceCollection.addHttpMethods(collectionDefinition.getMethods());
|
||||
webResourceCollection.addHttpMethodOmissions(collectionDefinition.getOmittedMethods());
|
||||
webResourceCollection.addUrlPatterns(collectionDefinition.getPatterns());
|
||||
|
||||
undertowSecurityConstraint.addWebResourceCollections(webResourceCollection);
|
||||
|
||||
undertowSecurityConstraints.add(undertowSecurityConstraint);
|
||||
}
|
||||
}
|
||||
return undertowSecurityConstraints;
|
||||
}
|
||||
}
|
||||
|
||||
static class KeycloakJettyServerCustomizer implements JettyServerCustomizer {
|
||||
|
||||
private final KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
public KeycloakJettyServerCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||
this.keycloakProperties = keycloakProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(Server server) {
|
||||
|
||||
KeycloakJettyAuthenticator keycloakJettyAuthenticator = new KeycloakJettyAuthenticator();
|
||||
keycloakJettyAuthenticator.setConfigResolver(new KeycloakSpringBootConfigResolver());
|
||||
|
||||
List<ConstraintMapping> jettyConstraintMappings = new ArrayList<ConstraintMapping>();
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityCollection securityCollectionDefinition : constraintDefinition
|
||||
.getSecurityCollections()) {
|
||||
|
||||
Constraint jettyConstraint = new Constraint();
|
||||
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||
jettyConstraint.setAuthenticate(true);
|
||||
|
||||
if (securityCollectionDefinition.getName() != null) {
|
||||
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||
}
|
||||
|
||||
jettyConstraint.setRoles(securityCollectionDefinition.getAuthRoles().toArray(new String[0]));
|
||||
|
||||
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
||||
if (securityCollectionDefinition.getPatterns().size() > 0) {
|
||||
//First pattern wins
|
||||
jettyConstraintMapping.setPathSpec(securityCollectionDefinition.getPatterns().get(0));
|
||||
jettyConstraintMapping.setConstraint(jettyConstraint);
|
||||
}
|
||||
|
||||
if (securityCollectionDefinition.getMethods().size() > 0) {
|
||||
//First method wins
|
||||
jettyConstraintMapping.setMethod(securityCollectionDefinition.getMethods().get(0));
|
||||
}
|
||||
|
||||
jettyConstraintMapping.setMethodOmissions(
|
||||
securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
|
||||
|
||||
jettyConstraintMappings.add(jettyConstraintMapping);
|
||||
}
|
||||
}
|
||||
|
||||
WebAppContext webAppContext = server.getBean(WebAppContext.class);
|
||||
|
||||
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
|
||||
securityHandler.setConstraintMappings(jettyConstraintMappings);
|
||||
securityHandler.setAuthenticator(keycloakJettyAuthenticator);
|
||||
|
||||
webAppContext.setHandler(securityHandler);
|
||||
}
|
||||
}
|
||||
|
||||
static class KeycloakTomcatContextCustomizer implements TomcatContextCustomizer {
|
||||
|
||||
private final KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
public KeycloakTomcatContextCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||
this.keycloakProperties = keycloakProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(Context context) {
|
||||
LoginConfig loginConfig = new LoginConfig();
|
||||
loginConfig.setAuthMethod("KEYCLOAK");
|
||||
context.setLoginConfig(loginConfig);
|
||||
|
||||
Set<String> authRoles = new HashSet<String>();
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
||||
for (String authRole : collection.getAuthRoles()) {
|
||||
if (!authRoles.contains(authRole)) {
|
||||
context.addSecurityRole(authRole);
|
||||
authRoles.add(authRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||
SecurityConstraint tomcatConstraint = new SecurityConstraint();
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
||||
SecurityCollection tomcatSecCollection = new SecurityCollection();
|
||||
|
||||
if (collection.getName() != null) {
|
||||
tomcatSecCollection.setName(collection.getName());
|
||||
}
|
||||
if (collection.getDescription() != null) {
|
||||
tomcatSecCollection.setDescription(collection.getDescription());
|
||||
}
|
||||
|
||||
for (String authRole : collection.getAuthRoles()) {
|
||||
tomcatConstraint.addAuthRole(authRole);
|
||||
}
|
||||
|
||||
for (String pattern : collection.getPatterns()) {
|
||||
tomcatSecCollection.addPattern(pattern);
|
||||
}
|
||||
|
||||
for (String method : collection.getMethods()) {
|
||||
tomcatSecCollection.addMethod(method);
|
||||
}
|
||||
|
||||
for (String method : collection.getOmittedMethods()) {
|
||||
tomcatSecCollection.addOmittedMethod(method);
|
||||
}
|
||||
|
||||
tomcatConstraint.addCollection(tomcatSecCollection);
|
||||
}
|
||||
|
||||
context.addConstraint(tomcatConstraint);
|
||||
}
|
||||
|
||||
context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -17,15 +17,27 @@
|
||||
|
||||
package org.keycloak.adapters.springboot;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ConfigurationProperties(prefix = "keycloak", ignoreUnknownFields = false)
|
||||
public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
|
||||
/* this is a dummy property to avoid re-rebinding problem with property keycloak.config.resolver
|
||||
when using spring cloud - see KEYCLOAK-2977 */
|
||||
@JsonIgnore
|
||||
private Map config = new HashMap();
|
||||
|
||||
public Map getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
|
||||
|
||||
public static class SecurityConstraint {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>2.0.0.CR1-SNAPSHOT</version>
|
||||
<version>2.3.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -71,6 +71,13 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
@@ -121,12 +128,6 @@
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
+4
-6
@@ -17,22 +17,20 @@
|
||||
|
||||
package org.keycloak.adapters.springsecurity;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} that creates an {@link AdapterDeploymentContext} given a {@link Resource} defining the Keycloak
|
||||
* client configuration or a {@link KeycloakConfigResolver} for multi-tenant environments.
|
||||
|
||||
+2
-2
@@ -21,13 +21,13 @@ import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
Loaded 100 of 2978 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user