Compare commits
360
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fc98ad9e3 | ||
|
|
51769f2e78 | ||
|
|
b418dcafc3 | ||
|
|
124ae402e2 | ||
|
|
fdb80d4319 | ||
|
|
f0f1b85582 | ||
|
|
6874cdc3b2 | ||
|
|
19bdf09e3d | ||
|
|
999dff353c | ||
|
|
f5389b0e17 | ||
|
|
b4ad69b841 | ||
|
|
6ba3687b76 | ||
|
|
500a21685f | ||
|
|
620bea3553 | ||
|
|
5e225c2bd5 | ||
|
|
c9bc321d2a | ||
|
|
74fe9249d5 | ||
|
|
9f814c6cf1 | ||
|
|
89fcddd605 | ||
|
|
0b108d83af | ||
|
|
5e16a32f86 | ||
|
|
6b846b53e4 | ||
|
|
e964b156cc | ||
|
|
bdc9e8d2c3 | ||
|
|
ce4506f367 | ||
|
|
55d0ad3a50 | ||
|
|
d90235d1bd | ||
|
|
1220d7f898 | ||
|
|
f5545eabdc | ||
|
|
3db8f97248 | ||
|
|
48421cbc43 | ||
|
|
7010c39544 | ||
|
|
a3ccac2012 | ||
|
|
6bbdc13544 | ||
|
|
073f1eda54 | ||
|
|
29c2ef4c60 | ||
|
|
947254e14f | ||
|
|
b02d48f772 | ||
|
|
4be0e36306 | ||
|
|
b041146a3f | ||
|
|
56c5996aff | ||
|
|
fa165806f9 | ||
|
|
7849191ec7 | ||
|
|
06a318d7d5 | ||
|
|
361ab1c988 | ||
|
|
c1c1a81a4e | ||
|
|
b4d39ca061 | ||
|
|
b4470165c7 | ||
|
|
af63fcbc4d | ||
|
|
d77824ff98 | ||
|
|
b771960e21 | ||
|
|
bc05560d4d | ||
|
|
3855b5c0a3 | ||
|
|
28b3ef9aa9 | ||
|
|
22987bb90b | ||
|
|
1c10199698 | ||
|
|
4367f03304 | ||
|
|
955cbc76d7 | ||
|
|
f1807aead4 | ||
|
|
756d996a4a | ||
|
|
3ee86fedc7 | ||
|
|
e7f781df5a | ||
|
|
8f9ed32a66 | ||
|
|
39dea4b078 | ||
|
|
58e5f41415 | ||
|
|
a7fbcaaef4 | ||
|
|
e75f47d523 | ||
|
|
cc14c5db23 | ||
|
|
6f731dfee9 | ||
|
|
ab7a0c2252 | ||
|
|
d08ddade2e | ||
|
|
52e40922bc | ||
|
|
2b1613d36b | ||
|
|
f1132ffabe | ||
|
|
8c82201add | ||
|
|
fc61a4e89f | ||
|
|
f289771fd3 | ||
|
|
8676148f20 | ||
|
|
bf43ccf6c1 | ||
|
|
eebd3feb28 | ||
|
|
be5291f710 | ||
|
|
3fd6fc250d | ||
|
|
e91dd011c5 | ||
|
|
8e36a52f1e | ||
|
|
9edb5b53c0 | ||
|
|
eae0360eb1 | ||
|
|
c7a20d7620 | ||
|
|
93d57c7d00 | ||
|
|
32cf8b7cad | ||
|
|
88d9411f4d | ||
|
|
2e2d15be9f | ||
|
|
f363dbcad0 | ||
|
|
63d2d0f7ed | ||
|
|
a04d79c576 | ||
|
|
57cb46148f | ||
|
|
7614ff8c6f | ||
|
|
edc6d99c9c | ||
|
|
a994af9010 | ||
|
|
0b5e6b0d49 | ||
|
|
222541ab5c | ||
|
|
dfed148921 | ||
|
|
a785f9fd11 | ||
|
|
93105a2182 | ||
|
|
0ac92c4bfa | ||
|
|
98a5c57e65 | ||
|
|
5d72def1bc | ||
|
|
f0d1149311 | ||
|
|
d4a85d6abc | ||
|
|
a34f890453 | ||
|
|
f8999128dc | ||
|
|
91585f8563 | ||
|
|
bdadef1282 | ||
|
|
5028c05cc2 | ||
|
|
6202222da4 | ||
|
|
60942346f3 | ||
|
|
97509ebf89 | ||
|
|
169280b6a1 | ||
|
|
35857bf649 | ||
|
|
bc407a7968 | ||
|
|
473065fd31 | ||
|
|
a0f3a6469f | ||
|
|
fd8a3dccaf | ||
|
|
f12cef2c86 | ||
|
|
2b55cd7902 | ||
|
|
a9b9d16500 | ||
|
|
80f8815b9a | ||
|
|
6cccd66162 | ||
|
|
84d2d7b431 | ||
|
|
9135ba7c40 | ||
|
|
94528976d4 | ||
|
|
7381ec456a | ||
|
|
536a57a514 | ||
|
|
5d88c2b8be | ||
|
|
9be9e30ad6 | ||
|
|
40c4140021 | ||
|
|
23887f4031 | ||
|
|
3760f2753b | ||
|
|
d0f505455d | ||
|
|
a41d282e92 | ||
|
|
813af5d757 | ||
|
|
241c58dd61 | ||
|
|
1cddaeb707 | ||
|
|
d69d00082f | ||
|
|
b9f7a43a72 | ||
|
|
bed6f96fc6 | ||
|
|
4d5e03049e | ||
|
|
7d017b4edf | ||
|
|
dcd1a68d95 | ||
|
|
b71bb96540 | ||
|
|
c4a0470a37 | ||
|
|
a52a1f4618 | ||
|
|
a2e35bf711 | ||
|
|
a6e4245185 | ||
|
|
67bee0dfc2 | ||
|
|
8f1938c28d | ||
|
|
a15517c5ce | ||
|
|
af6f617e0e | ||
|
|
8c53c5a90e | ||
|
|
ca4488a845 | ||
|
|
7d0b461683 | ||
|
|
c3ea847b3e | ||
|
|
684689d40d | ||
|
|
c47952250d | ||
|
|
c4f172afe7 | ||
|
|
c43329d033 | ||
|
|
5560175888 | ||
|
|
bb1096c511 | ||
|
|
88473ce3c1 | ||
|
|
81f1a5b145 | ||
|
|
e4fc8f083e | ||
|
|
79af4dfd18 | ||
|
|
9e509d71d2 | ||
|
|
2b59db71a8 | ||
|
|
63c237423d | ||
|
|
b870e81648 | ||
|
|
03308d8b20 | ||
|
|
73c328d5e3 | ||
|
|
829bcf5eaf | ||
|
|
554e692d8f | ||
|
|
366cb78a36 | ||
|
|
1d5bd2567e | ||
|
|
67a05ee227 | ||
|
|
a9c98a577b | ||
|
|
c442bcd8d3 | ||
|
|
c00a64208a | ||
|
|
e59aeb56cc | ||
|
|
e3a04ebd90 | ||
|
|
06152d9883 | ||
|
|
c230edbf72 | ||
|
|
1b6405a28f | ||
|
|
12fec09410 | ||
|
|
ef29097679 | ||
|
|
0a869f56a7 | ||
|
|
cd53486566 | ||
|
|
ff2d6941d0 | ||
|
|
acd78ee407 | ||
|
|
130452f6c3 | ||
|
|
178fd08d9a | ||
|
|
097a2267f5 | ||
|
|
7afdd98499 | ||
|
|
f47283f61a | ||
|
|
03b1dff1bd | ||
|
|
804766827c | ||
|
|
8adde64e2c | ||
|
|
ca8577fb4a | ||
|
|
37a98fba20 | ||
|
|
ef6b303bfe | ||
|
|
8d40ee17f1 | ||
|
|
29c5d683d0 | ||
|
|
3580dea399 | ||
|
|
7ae117707b | ||
|
|
d1d590b45c | ||
|
|
62ffab7239 | ||
|
|
7681c76ee1 | ||
|
|
ab763e7c5b | ||
|
|
c2379dbe49 | ||
|
|
f114895cd2 | ||
|
|
ce9320755d | ||
|
|
dce4353e0d | ||
|
|
4d5ca2b8fa | ||
|
|
8e4899f874 | ||
|
|
e2a7b71cf3 | ||
|
|
f0386d9374 | ||
|
|
336d05dd34 | ||
|
|
d80381ec0d | ||
|
|
d4f870fbb4 | ||
|
|
0b6c9aa927 | ||
|
|
ca8d756c05 | ||
|
|
a2af516df7 | ||
|
|
43a625db28 | ||
|
|
cc42ea9332 | ||
|
|
9ec3a8c3d9 | ||
|
|
2e83eda172 | ||
|
|
6d8a3f7a8b | ||
|
|
56d68c17f5 | ||
|
|
dac848e164 | ||
|
|
badabf8385 | ||
|
|
d22a284df1 | ||
|
|
60ef910b5b | ||
|
|
53ab314eb9 | ||
|
|
2cac8b1bb7 | ||
|
|
c291748f43 | ||
|
|
c6ff1037dc | ||
|
|
b68494b3f0 | ||
|
|
39afd4a020 | ||
|
|
355af6d1cf | ||
|
|
69ada965d8 | ||
|
|
f9767ad6cd | ||
|
|
28acf489a1 | ||
|
|
86f95d3f87 | ||
|
|
c178a2392d | ||
|
|
10c9e0f00f | ||
|
|
71f0db0837 | ||
|
|
27b291c345 | ||
|
|
4581272dcd | ||
|
|
70d7e07526 | ||
|
|
7d0ca42c6c | ||
|
|
9804aaf796 | ||
|
|
954ef99f22 | ||
|
|
789722ec6a | ||
|
|
d824b4d93c | ||
|
|
7569493b17 | ||
|
|
aaddb035a8 | ||
|
|
cc6a5419de | ||
|
|
7d8796e614 | ||
|
|
b8262a9f02 | ||
|
|
db8b733610 | ||
|
|
c431cc1b01 | ||
|
|
168153c6e7 | ||
|
|
47aaa5a636 | ||
|
|
e7272dc05a | ||
|
|
b55b089355 | ||
|
|
a9ec69e424 | ||
|
|
19a41c8704 | ||
|
|
83b29c5080 | ||
|
|
f392e79ad7 | ||
|
|
e4aba9e471 | ||
|
|
c19d6f36ed | ||
|
|
14908d9038 | ||
|
|
c3a2b3a6b6 | ||
|
|
9378ede362 | ||
|
|
349a59cf35 | ||
|
|
64cd689e38 | ||
|
|
bfb4395b28 | ||
|
|
46ec12c41c | ||
|
|
0d9ffd4e64 | ||
|
|
d39d39a8eb | ||
|
|
b64817761a | ||
|
|
f3febd1067 | ||
|
|
ec53af29c5 | ||
|
|
c0b08a27a2 | ||
|
|
18295497ae | ||
|
|
978619ead0 | ||
|
|
12e56086d5 | ||
|
|
a8a8ea4bcd | ||
|
|
f760427c5c | ||
|
|
126830cd8b | ||
|
|
e738b84fd5 | ||
|
|
59d5604699 | ||
|
|
68c7da141b | ||
|
|
f63c60855e | ||
|
|
f0a0b553e5 | ||
|
|
e14be4460b | ||
|
|
2d825dd366 | ||
|
|
f4f9a98e70 | ||
|
|
47a8077426 | ||
|
|
e1b6ba13cc | ||
|
|
8da766e02e | ||
|
|
a8071356bb | ||
|
|
ec5860d7f5 | ||
|
|
7c2fcaf141 | ||
|
|
e0da7ed6b4 | ||
|
|
5e1654658f | ||
|
|
e6477ed261 | ||
|
|
43f64e51c6 | ||
|
|
b00651391b | ||
|
|
2e66cd1070 | ||
|
|
55f3d8c1d0 | ||
|
|
d5c643eaf9 | ||
|
|
ee327b7eec | ||
|
|
5fd704af58 | ||
|
|
aa1eb334e0 | ||
|
|
47ea1ade8a | ||
|
|
b33f7414da | ||
|
|
16aaa3bf01 | ||
|
|
b8fecca0ba | ||
|
|
b824c4195a | ||
|
|
1438982104 | ||
|
|
c3b44e61d4 | ||
|
|
93ef2ec9bf | ||
|
|
af792b8abe | ||
|
|
1f4311a02c | ||
|
|
ff6dbd6bde | ||
|
|
2df1175315 | ||
|
|
eb93eef874 | ||
|
|
5b926a3da6 | ||
|
|
618a8e7f90 | ||
|
|
5a8634e359 | ||
|
|
bbed1ffca7 | ||
|
|
09ecc37ccb | ||
|
|
ab8658333f | ||
|
|
ca1152c3e5 | ||
|
|
7bcbc9a6af | ||
|
|
6073f4c159 | ||
|
|
dee4548798 | ||
|
|
8f9b6a5e44 | ||
|
|
87dedb56e5 | ||
|
|
c7bdb489ee | ||
|
|
2276f99d54 | ||
|
|
f67013bcb6 | ||
|
|
b37ed7145c | ||
|
|
1f98dc5527 | ||
|
|
754b0fd123 | ||
|
|
46bc102799 | ||
|
|
cb7cef8858 | ||
|
|
12cb295a35 | ||
|
|
58868ca99f | ||
|
|
b84f6d306d | ||
|
|
19a59429ae | ||
|
|
89c6cda2ac |
No files matched your search
+1
-1
@@ -25,7 +25,7 @@ before_script:
|
||||
- export MAVEN_SKIP_RC=true
|
||||
|
||||
install:
|
||||
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTests=true -B -V -q
|
||||
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTestsuite -B -V -q
|
||||
|
||||
script:
|
||||
- ./travis-run-tests.sh $TESTS
|
||||
|
||||
@@ -41,10 +41,10 @@ To start Keycloak during development first build as specified above, then run:
|
||||
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server
|
||||
|
||||
|
||||
To start Keycloak from the appliance distribution first build the distribution it as specified above, then run:
|
||||
To start Keycloak from the server distribution first build the distribution it as specified above, then run:
|
||||
|
||||
tar xfz distribution/appliance-dist/target/keycloak-appliance-dist-all-<VERSION>.tar.gz
|
||||
cd keycloak-appliance-dist-all-<VERSION>/keycloak
|
||||
tar xfz distribution/server-dist/target/keycloak-<VERSION>.tar.gz
|
||||
cd keycloak-<VERSION>
|
||||
bin/standalone.sh
|
||||
|
||||
To stop the server press `Ctrl + C`.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+4
@@ -101,6 +101,7 @@ public class AuthenticatedActionsHandler {
|
||||
if (!deployment.isCors()) return false;
|
||||
KeycloakSecurityContext securityContext = facade.getSecurityContext();
|
||||
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
|
||||
String exposeHeaders = deployment.getCorsExposedHeaders();
|
||||
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
|
||||
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
|
||||
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
|
||||
@@ -124,6 +125,9 @@ public class AuthenticatedActionsHandler {
|
||||
facade.getResponse().setStatus(200);
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
if (exposeHeaders != null) {
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
|
||||
}
|
||||
} else {
|
||||
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
|
||||
}
|
||||
|
||||
@@ -30,4 +30,5 @@ public interface CorsHeaders {
|
||||
String ORIGIN = "Origin";
|
||||
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
|
||||
String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
|
||||
String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
|
||||
}
|
||||
+3
-3
@@ -170,8 +170,8 @@ public class HttpClientBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder disableCookieCache() {
|
||||
this.disableCookieCache = true;
|
||||
public HttpClientBuilder disableCookieCache(boolean disable) {
|
||||
this.disableCookieCache = disable;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ public class HttpClientBuilder {
|
||||
}
|
||||
|
||||
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
|
||||
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
|
||||
disableCookieCache(true); // disable cookie cache as we don't want sticky sessions for load balancing
|
||||
|
||||
String truststorePath = adapterConfig.getTruststore();
|
||||
if (truststorePath != null) {
|
||||
|
||||
+31
@@ -35,6 +35,8 @@ import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakDeployment {
|
||||
@@ -73,6 +75,7 @@ public class KeycloakDeployment {
|
||||
protected int corsMaxAge = -1;
|
||||
protected String corsAllowedHeaders;
|
||||
protected String corsAllowedMethods;
|
||||
protected String corsExposedHeaders;
|
||||
protected boolean exposeToken;
|
||||
protected boolean alwaysRefreshToken;
|
||||
protected boolean registerNodeAtStartup;
|
||||
@@ -87,6 +90,9 @@ public class KeycloakDeployment {
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
protected boolean pkce = false;
|
||||
protected boolean ignoreOAuthQueryParameter;
|
||||
|
||||
protected Map<String, String> redirectRewriteRules;
|
||||
|
||||
public KeycloakDeployment() {
|
||||
}
|
||||
@@ -325,6 +331,14 @@ public class KeycloakDeployment {
|
||||
this.corsAllowedMethods = corsAllowedMethods;
|
||||
}
|
||||
|
||||
public String getCorsExposedHeaders() {
|
||||
return corsExposedHeaders;
|
||||
}
|
||||
|
||||
public void setCorsExposedHeaders(String corsExposedHeaders) {
|
||||
this.corsExposedHeaders = corsExposedHeaders;
|
||||
}
|
||||
|
||||
public boolean isExposeToken() {
|
||||
return exposeToken;
|
||||
}
|
||||
@@ -427,4 +441,21 @@ public class KeycloakDeployment {
|
||||
this.pkce = pkce;
|
||||
}
|
||||
|
||||
public void setIgnoreOAuthQueryParameter(boolean ignoreOAuthQueryParameter) {
|
||||
this.ignoreOAuthQueryParameter = ignoreOAuthQueryParameter;
|
||||
}
|
||||
|
||||
public boolean isOAuthQueryParameterEnabled() {
|
||||
return !this.ignoreOAuthQueryParameter;
|
||||
}
|
||||
|
||||
public Map<String, String> getRedirectRewriteRules() {
|
||||
return redirectRewriteRules;
|
||||
}
|
||||
|
||||
public void setRewriteRedirectRules(Map<String, String> redirectRewriteRules) {
|
||||
this.redirectRewriteRules = redirectRewriteRules;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+5
@@ -37,6 +37,8 @@ import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakDeploymentBuilder {
|
||||
@@ -96,6 +98,7 @@ public class KeycloakDeploymentBuilder {
|
||||
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
|
||||
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
|
||||
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
|
||||
deployment.setCorsExposedHeaders(adapterConfig.getCorsExposedHeaders());
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
@@ -112,6 +115,8 @@ public class KeycloakDeploymentBuilder {
|
||||
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
||||
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
||||
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
|
||||
deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
|
||||
deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
|
||||
|
||||
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");
|
||||
|
||||
+26
-5
@@ -25,7 +25,6 @@ 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.Encode;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
@@ -38,7 +37,10 @@ import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
/**
|
||||
@@ -141,6 +143,7 @@ public class OAuthRequestAuthenticator {
|
||||
protected String getRedirectUri(String state) {
|
||||
String url = getRequestUrl();
|
||||
log.debugf("callback uri: %s", url);
|
||||
|
||||
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
|
||||
int port = sslRedirectPort();
|
||||
if (port < 0) {
|
||||
@@ -170,7 +173,7 @@ public class OAuthRequestAuthenticator {
|
||||
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters.
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url))
|
||||
.queryParam(OAuth2Constants.STATE, state)
|
||||
.queryParam("login", "true");
|
||||
if(loginHint != null && loginHint.length() > 0){
|
||||
@@ -320,10 +323,11 @@ public class OAuthRequestAuthenticator {
|
||||
|
||||
AccessTokenResponse tokenResponse = null;
|
||||
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
|
||||
|
||||
try {
|
||||
// For COOKIE store we don't have httpSessionId and single sign-out won't be available
|
||||
String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null;
|
||||
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
|
||||
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, rewrittenRedirectUri(strippedOauthParametersRequestUri), httpSessionId);
|
||||
} catch (ServerRequest.HttpFailure failure) {
|
||||
log.error("failed to turn code into token");
|
||||
log.error("status from server: " + failure.getStatus());
|
||||
@@ -375,6 +379,23 @@ public class OAuthRequestAuthenticator {
|
||||
.replaceQueryParam(OAuth2Constants.STATE, null);
|
||||
return builder.build().toString();
|
||||
}
|
||||
|
||||
|
||||
private String rewrittenRedirectUri(String originalUri) {
|
||||
Map<String, String> rewriteRules = deployment.getRedirectRewriteRules();
|
||||
if(rewriteRules != null && !rewriteRules.isEmpty()) {
|
||||
try {
|
||||
URL url = new URL(originalUri);
|
||||
Map.Entry<String, String> rule = rewriteRules.entrySet().iterator().next();
|
||||
StringBuilder redirectUriBuilder = new StringBuilder(url.getProtocol());
|
||||
redirectUriBuilder.append("://"+ url.getAuthority());
|
||||
redirectUriBuilder.append(url.getPath().replaceFirst(rule.getKey(), rule.getValue()));
|
||||
return redirectUriBuilder.toString();
|
||||
} catch (MalformedURLException ex) {
|
||||
log.error("Not a valid request url");
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
return originalUri;
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -22,6 +22,8 @@ import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
|
||||
@@ -33,6 +35,9 @@ public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAu
|
||||
}
|
||||
|
||||
public AuthOutcome authenticate(HttpFacade exchange) {
|
||||
if(!deployment.isOAuthQueryParameterEnabled()) {
|
||||
return AuthOutcome.NOT_ATTEMPTED;
|
||||
}
|
||||
tokenString = null;
|
||||
tokenString = getAccessTokenFromQueryParamter(exchange);
|
||||
if (tokenString == null || tokenString.trim().isEmpty()) {
|
||||
|
||||
+5
-5
@@ -78,13 +78,13 @@ public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken);
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
||||
|
||||
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
|
||||
return createAuthorizationContext(accessToken);
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
@@ -100,7 +100,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
if (isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken);
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
@@ -252,8 +252,8 @@ public abstract class AbstractPolicyEnforcer {
|
||||
return requiredScopes;
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
|
||||
return new ClientAuthorizationContext(accessToken, this.paths, authzClient);
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, this.paths, authzClient);
|
||||
}
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
|
||||
+4
-4
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
@@ -26,8 +28,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.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
@@ -52,7 +52,7 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String clientId = authzClient.getConfiguration().getClientId();
|
||||
String clientId = authzClient.getConfiguration().getResource();
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
|
||||
@@ -65,7 +65,7 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
|
||||
String clientId = authzClient.getConfiguration().getClientId();
|
||||
String clientId = authzClient.getConfiguration().getResource();
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
|
||||
|
||||
+2
-2
@@ -127,7 +127,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
AccessToken token = httpFacade.getSecurityContext().getToken();
|
||||
|
||||
if (token.getAuthorization() == null) {
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getResource());
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
} else {
|
||||
EntitlementRequest request = new EntitlementRequest();
|
||||
@@ -137,7 +137,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
||||
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
|
||||
request.addPermission(permissionRequest);
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getResource(), request);
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -29,10 +29,13 @@ import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
*/
|
||||
public class KeycloakDeploymentBuilderTest {
|
||||
|
||||
@@ -53,10 +56,12 @@ public class KeycloakDeploymentBuilderTest {
|
||||
assertEquals(1000, deployment.getCorsMaxAge());
|
||||
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
|
||||
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
|
||||
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
|
||||
assertTrue(deployment.isBearerOnly());
|
||||
assertTrue(deployment.isPublicClient());
|
||||
assertTrue(deployment.isEnableBasicAuth());
|
||||
assertTrue(deployment.isExposeToken());
|
||||
assertFalse(deployment.isOAuthQueryParameterEnabled());
|
||||
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
|
||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
|
||||
@@ -70,6 +75,7 @@ public class KeycloakDeploymentBuilderTest {
|
||||
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
||||
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
||||
assertEquals(120, deployment.getPublicKeyCacheTtl());
|
||||
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
*/
|
||||
public class KeycloakDeploymentTest {
|
||||
@Test
|
||||
public void shouldNotEnableOAuthQueryParamWhenIgnoreIsTrue() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setIgnoreOAuthQueryParameter(true);
|
||||
assertFalse(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEnableOAuthQueryParamWhenIgnoreIsFalse() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setIgnoreOAuthQueryParameter(false);
|
||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEnableOAuthQueryParamWhenIgnoreNotSet() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
|
||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
"cors-max-age": 1000,
|
||||
"cors-allowed-methods": "POST, PUT, DELETE, GET",
|
||||
"cors-allowed-headers": "X-Custom, X-Custom2",
|
||||
"cors-exposed-headers": "X-Custom3, X-Custom4",
|
||||
"bearer-only": true,
|
||||
"public-client": true,
|
||||
"enable-basic-auth": true,
|
||||
@@ -31,5 +32,9 @@
|
||||
"principal-attribute": "email",
|
||||
"token-minimum-time-to-live": 10,
|
||||
"min-time-between-jwks-requests": 20,
|
||||
"public-key-cache-ttl": 120
|
||||
"public-key-cache-ttl": 120,
|
||||
"ignore-oauth-query-parameter": true,
|
||||
"redirect-rewrite-rules" : {
|
||||
"^/wsmaster/api/(.*)$" : "/api/$1"
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.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>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.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>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Jetty Integration</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
* following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
* portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import * as Keycloak from 'keycloak';
|
||||
|
||||
export as namespace KeycloakAuthorization;
|
||||
|
||||
export = KeycloakAuthorization;
|
||||
|
||||
/**
|
||||
* Creates a new Keycloak client instance.
|
||||
* @param config Path to a JSON config file or a plain config object.
|
||||
*/
|
||||
declare function KeycloakAuthorization(keycloak: Keycloak.KeycloakInstance): KeycloakAuthorization.KeycloakAuthorizationInstance;
|
||||
|
||||
declare namespace KeycloakAuthorization {
|
||||
interface KeycloakAuthorizationPromise {
|
||||
then(onGrant: (rpt: string) => void, onDeny: () => void, onError: () => void): void;
|
||||
}
|
||||
|
||||
interface KeycloakAuthorizationInstance {
|
||||
rpt: any;
|
||||
config: { rpt_endpoint: string };
|
||||
|
||||
init(): void;
|
||||
|
||||
/**
|
||||
* This method enables client applications to better integrate with resource servers protected by a Keycloak
|
||||
* policy enforcer.
|
||||
*
|
||||
* In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the
|
||||
* necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
|
||||
* depending on how the policy enforcer at the resource server was configured.
|
||||
*/
|
||||
authorize(wwwAuthenticateHeader: string): KeycloakAuthorizationPromise;
|
||||
|
||||
/**
|
||||
* Obtains all entitlements from a Keycloak server based on a given resourceServerId.
|
||||
*/
|
||||
entitlement(resourceServerId: string, entitlementRequest: {}): KeycloakAuthorizationPromise;
|
||||
}
|
||||
}
|
||||
+464
-87
@@ -1,101 +1,478 @@
|
||||
/*
|
||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
* MIT License
|
||||
*
|
||||
* 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
|
||||
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
* following conditions:
|
||||
*
|
||||
* 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.
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
* portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
declare module KeycloakModule {
|
||||
export as namespace Keycloak;
|
||||
|
||||
export interface Promise {
|
||||
success(callback: Function): Promise;
|
||||
error(callback: Function): Promise;
|
||||
}
|
||||
export = Keycloak;
|
||||
|
||||
export type ResponseModes = "query" | "fragment";
|
||||
export type Flows = "standard" | "implicit" | "hybrid";
|
||||
|
||||
export interface InitOptions {
|
||||
checkLoginIframe?: boolean;
|
||||
checkLoginIframeInterval?: number;
|
||||
onLoad?: string;
|
||||
adapter?: string;
|
||||
responseMode?: ResponseModes;
|
||||
flow?: Flows;
|
||||
token?: string;
|
||||
refreshToken?: string;
|
||||
idToken?: string;
|
||||
timeSkew?: number;
|
||||
}
|
||||
/**
|
||||
* Creates a new Keycloak client instance.
|
||||
* @param config Path to a JSON config file or a plain config object.
|
||||
*/
|
||||
declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
|
||||
|
||||
export interface LoginOptions {
|
||||
redirectUri?: string;
|
||||
prompt?: string;
|
||||
maxAge?: number;
|
||||
loginHint?: string;
|
||||
action?: string;
|
||||
locale?: string;
|
||||
}
|
||||
declare namespace Keycloak {
|
||||
type KeycloakAdapterName = 'cordova'|'default';
|
||||
type KeycloakOnLoad = 'login-required'|'check-sso';
|
||||
type KeycloakResponseMode = 'query'|'fragment';
|
||||
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
|
||||
type KeycloakFlow = 'standard'|'implicit'|'hybrid';
|
||||
|
||||
export interface RedirectUriOptions {
|
||||
redirectUri?: string;
|
||||
}
|
||||
interface KeycloakInitOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
adapter?: KeycloakAdapterName;
|
||||
|
||||
export interface KeycloakClient {
|
||||
init(options?: InitOptions): Promise;
|
||||
login(options?: LoginOptions): Promise;
|
||||
createLoginUrl(options?: LoginOptions): string;
|
||||
logout(options?: RedirectUriOptions): Promise;
|
||||
createLogoutUrl(options?: RedirectUriOptions): string;
|
||||
register(options?: LoginOptions): Promise;
|
||||
createRegisterUrl(options?: RedirectUriOptions): string;
|
||||
accountManagement(): Promise;
|
||||
createAccountUrl(options?: RedirectUriOptions): string;
|
||||
hasRealmRole(role: string): boolean;
|
||||
hasResourceRole(role: string, resource?: string): boolean;
|
||||
loadUserProfile(): Promise;
|
||||
isTokenExpired(minValidity: number): boolean;
|
||||
updateToken(minValidity: number): Promise;
|
||||
clearToken(): any;
|
||||
/**
|
||||
* Specifies an action to do on load.
|
||||
*/
|
||||
onLoad?: KeycloakOnLoad;
|
||||
|
||||
realm: string;
|
||||
clientId: string;
|
||||
authServerUrl: string;
|
||||
/**
|
||||
* Set an initial value for the token.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
token: string;
|
||||
tokenParsed: any;
|
||||
refreshToken: string;
|
||||
refreshTokenParsed: any;
|
||||
idToken: string;
|
||||
idTokenParsed: any;
|
||||
realmAccess: any;
|
||||
resourceAccess: any;
|
||||
authenticated: boolean;
|
||||
subject: string;
|
||||
timeSkew: number;
|
||||
responseMode: ResponseModes;
|
||||
flow: Flows;
|
||||
responseType: string;
|
||||
/**
|
||||
* Set an initial value for the refresh token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
onReady: Function;
|
||||
onAuthSuccess: Function;
|
||||
onAuthError: Function;
|
||||
onAuthRefreshSuccess: Function;
|
||||
onAuthRefreshError: Function;
|
||||
onAuthLogout: Function;
|
||||
onTokenExpired: Function;
|
||||
}
|
||||
/**
|
||||
* Set an initial value for the id token (only together with `token` or
|
||||
* `refreshToken`).
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for skew between local time and Keycloak server in
|
||||
* seconds (only together with `token` or `refreshToken`).
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* Set to enable/disable monitoring login state.
|
||||
* @default true
|
||||
*/
|
||||
checkLoginIframe?: boolean;
|
||||
|
||||
/**
|
||||
* Set the interval to check login state (in seconds).
|
||||
* @default 5
|
||||
*/
|
||||
checkLoginIframeInterval?: boolean;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect response mode to send to Keycloak upon login.
|
||||
* @default fragment After successful authentication Keycloak will redirect
|
||||
* to JavaScript application with OpenID Connect parameters
|
||||
* added in URL fragment. This is generally safer and
|
||||
* recommended over query.
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect flow.
|
||||
* @default standard
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
}
|
||||
|
||||
interface KeycloakLoginOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
scope?: string;
|
||||
|
||||
/**
|
||||
* Specifies the uri to redirect to after login.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* By default the login screen is displayed if the user is not logged into
|
||||
* Keycloak. To only authenticate to the application if the user is already
|
||||
* logged in and not display the login page if the user is not logged in, set
|
||||
* this option to `'none'`. To always require re-authentication and ignore
|
||||
* SSO, set this option to `'login'`.
|
||||
*/
|
||||
prompt?: 'none'|'login';
|
||||
|
||||
/**
|
||||
* If value is `'register'` then user is redirected to registration page,
|
||||
* otherwise to login page.
|
||||
*/
|
||||
action?: 'register';
|
||||
|
||||
/**
|
||||
* Used just if user is already authenticated. Specifies maximum time since
|
||||
* the authentication of user happened. If user is already authenticated for
|
||||
* longer time than `'maxAge'`, the SSO is ignored and he will need to
|
||||
* authenticate again.
|
||||
*/
|
||||
maxAge?: number;
|
||||
|
||||
/**
|
||||
* Used to pre-fill the username/email field on the login form.
|
||||
*/
|
||||
loginHint?: string;
|
||||
|
||||
/**
|
||||
* Used to tell Keycloak which IDP the user wants to authenticate with.
|
||||
*/
|
||||
idpHint?: string;
|
||||
|
||||
/**
|
||||
* Specifies the desired locale for the UI.
|
||||
*/
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
type KeycloakPromiseCallback<T> = (result: T) => void;
|
||||
|
||||
interface KeycloakPromise<TSuccess, TError> {
|
||||
/**
|
||||
* Function to call if the promised action succeeds.
|
||||
*/
|
||||
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
|
||||
|
||||
/**
|
||||
* Function to call if the promised action throws an error.
|
||||
*/
|
||||
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
|
||||
}
|
||||
|
||||
interface KeycloakError {
|
||||
error: string;
|
||||
error_description: string;
|
||||
}
|
||||
|
||||
interface KeycloakAdapter {
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
|
||||
}
|
||||
|
||||
interface KeycloakProfile {
|
||||
id?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
totp?: boolean;
|
||||
createdTimestamp?: number;
|
||||
}
|
||||
|
||||
// export interface KeycloakUserInfo {}
|
||||
|
||||
/**
|
||||
* A client for the Keycloak authentication server.
|
||||
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
|
||||
*/
|
||||
interface KeycloakInstance {
|
||||
/**
|
||||
* Is true if the user is authenticated, false otherwise.
|
||||
*/
|
||||
authenticated?: boolean;
|
||||
|
||||
/**
|
||||
* The user id.
|
||||
*/
|
||||
subject?: string;
|
||||
|
||||
/**
|
||||
* Response mode passed in init (default value is `'fragment'`).
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Response type sent to Keycloak with login requests. This is determined
|
||||
* based on the flow value used during initialization, but can be overridden
|
||||
* by setting this value.
|
||||
*/
|
||||
responseType?: KeycloakResponseType;
|
||||
|
||||
/**
|
||||
* Flow passed in init.
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
|
||||
/**
|
||||
* The realm roles associated with the token.
|
||||
*/
|
||||
realmAccess?: { roles: string[] };
|
||||
|
||||
/**
|
||||
* The resource roles associated with the token.
|
||||
*/
|
||||
resourceAccess?: string[];
|
||||
|
||||
/**
|
||||
* The base64 encoded token that can be sent in the Authorization header in
|
||||
* requests to services.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* The parsed token as a JavaScript object.
|
||||
*/
|
||||
tokenParsed?: {
|
||||
exp?: number;
|
||||
iat?: number;
|
||||
nonce?: string;
|
||||
sub?: string;
|
||||
session_state?: string;
|
||||
realm_access?: { roles: string[] };
|
||||
resource_access?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The base64 encoded refresh token that can be used to retrieve a new token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed refresh token as a JavaScript object.
|
||||
*/
|
||||
refreshTokenParsed?: { nonce?: string };
|
||||
|
||||
/**
|
||||
* The base64 encoded ID token.
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed id token as a JavaScript object.
|
||||
*/
|
||||
idTokenParsed?: { nonce?: string };
|
||||
|
||||
/**
|
||||
* The estimated time difference between the browser time and the Keycloak
|
||||
* server in seconds. This value is just an estimation, but is accurate
|
||||
* enough when determining if a token is expired or not.
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loginRequired?: boolean;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
authServerUrl?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
realm?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientSecret?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
sessionId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
profile?: KeycloakProfile;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
userInfo?: {}; // KeycloakUserInfo;
|
||||
|
||||
/**
|
||||
* Called when the adapter is initialized.
|
||||
*/
|
||||
onReady?(authenticated?: boolean): void;
|
||||
|
||||
/**
|
||||
* Called when a user is successfully authenticated.
|
||||
*/
|
||||
onAuthSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error during authentication.
|
||||
*/
|
||||
onAuthError?(errorData: KeycloakError): void;
|
||||
|
||||
/**
|
||||
* Called when the token is refreshed.
|
||||
*/
|
||||
onAuthRefreshSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error while trying to refresh the token.
|
||||
*/
|
||||
onAuthRefreshError?(): void;
|
||||
|
||||
/**
|
||||
* Called if the user is logged out (will only be called if the session
|
||||
* status iframe is enabled, or in Cordova mode).
|
||||
*/
|
||||
onAuthLogout?(): void;
|
||||
|
||||
/**
|
||||
* Called when the access token is expired. If a refresh token is available
|
||||
* the token can be refreshed with Keycloak#updateToken, or in cases where
|
||||
* it's not (ie. with implicit flow) you can redirect to login screen to
|
||||
* obtain a new access token.
|
||||
*/
|
||||
onTokenExpired?(): void;
|
||||
|
||||
/**
|
||||
* Called to initialize the adapter.
|
||||
* @param initOptions Initialization options.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
|
||||
|
||||
/**
|
||||
* Redirects to login form.
|
||||
* @param options Login options.
|
||||
*/
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to logout.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to registration form.
|
||||
* @param options Supports same options as Keycloak#login but `action` is
|
||||
* set to `'register'`.
|
||||
*/
|
||||
register(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to the Account Management Console.
|
||||
*/
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Returns the URL to login form.
|
||||
* @param options Supports same options as Keycloak#login.
|
||||
*/
|
||||
createLoginUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to logout the user.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
createLogoutUrl(options?: any): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to registration page.
|
||||
* @param options Supports same options as Keycloak#createLoginUrl but
|
||||
* `action` is set to `'register'`.
|
||||
*/
|
||||
createRegisterUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to the Account Management Console.
|
||||
*/
|
||||
createAccountUrl(): string;
|
||||
|
||||
/**
|
||||
* Returns true if the token has less than `minValidity` seconds left before
|
||||
* it expires.
|
||||
* @param minValidity If not specified, `0` is used.
|
||||
*/
|
||||
isTokenExpired(minValidity?: number): boolean;
|
||||
|
||||
/**
|
||||
* If the token expires within `minValidity` seconds, the token is refreshed.
|
||||
* If the session status iframe is enabled, the session status is also
|
||||
* checked.
|
||||
* @returns A promise to set functions that can be invoked if the token is
|
||||
* still valid, or if the token is no longer valid.
|
||||
* @example
|
||||
* ```js
|
||||
* keycloak.updateToken(5).success(function(refreshed) {
|
||||
* if (refreshed) {
|
||||
* alert('Token was successfully refreshed');
|
||||
* } else {
|
||||
* alert('Token is still valid');
|
||||
* }
|
||||
* }).error(function() {
|
||||
* alert('Failed to refresh the token, or the session has expired');
|
||||
* });
|
||||
*/
|
||||
updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
|
||||
|
||||
/**
|
||||
* Clears authentication state, including tokens. This can be useful if
|
||||
* the application has detected the session was expired, for example if
|
||||
* updating token fails. Invoking this results in Keycloak#onAuthLogout
|
||||
* callback listener being invoked.
|
||||
*/
|
||||
clearToken(): void;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given realm role.
|
||||
* @param role A realm role name.
|
||||
*/
|
||||
hasRealmRole(role: string): boolean;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given role for the resource.
|
||||
* @param role A role name.
|
||||
* @param resource If not specified, `clientId` is used.
|
||||
*/
|
||||
hasResourceRole(role: string, resource?: string): boolean;
|
||||
|
||||
/**
|
||||
* Loads the user's profile.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loadUserInfo(): KeycloakPromise<{}, void>;
|
||||
}
|
||||
}
|
||||
|
||||
declare var Keycloak: {
|
||||
new(config?: any): KeycloakModule.KeycloakClient;
|
||||
};
|
||||
@@ -587,7 +587,7 @@
|
||||
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
if (req.status == 200 || fileLoaded(req)) {
|
||||
var config = JSON.parse(req.responseText);
|
||||
|
||||
kc.authServerUrl = config['auth-server-url'];
|
||||
@@ -633,6 +633,10 @@
|
||||
return promise.promise;
|
||||
}
|
||||
|
||||
function fileLoaded(xhr) {
|
||||
return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:');
|
||||
}
|
||||
|
||||
function setToken(token, refreshToken, idToken, timeLocal) {
|
||||
if (kc.tokenTimeoutHandle) {
|
||||
clearTimeout(kc.tokenTimeoutHandle);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.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>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+54
-30
@@ -54,72 +54,96 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class KeycloakOIDCFilter implements Filter {
|
||||
|
||||
private final static Logger log = Logger.getLogger("" + KeycloakOIDCFilter.class);
|
||||
|
||||
public static final String SKIP_PATTERN_PARAM = "keycloak.config.skipPattern";
|
||||
|
||||
public static final String CONFIG_RESOLVER_PARAM = "keycloak.config.resolver";
|
||||
|
||||
public static final String CONFIG_FILE_PARAM = "keycloak.config.file";
|
||||
|
||||
public static final String CONFIG_PATH_PARAM = "keycloak.config.path";
|
||||
|
||||
protected AdapterDeploymentContext deploymentContext;
|
||||
|
||||
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
|
||||
|
||||
protected NodesRegistrationManagement nodesRegistrationManagement;
|
||||
|
||||
protected Pattern skipPattern;
|
||||
|
||||
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
|
||||
private final KeycloakConfigResolver definedconfigResolver;
|
||||
|
||||
/**
|
||||
* Constructor that can be used to define a {@code KeycloakConfigResolver} that will be used at initialization to
|
||||
* provide the {@code KeycloakDeployment}.
|
||||
* @param definedconfigResolver the resolver
|
||||
*/
|
||||
public KeycloakOIDCFilter(KeycloakConfigResolver definedconfigResolver) {
|
||||
this.definedconfigResolver = definedconfigResolver;
|
||||
}
|
||||
|
||||
public KeycloakOIDCFilter() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(final FilterConfig filterConfig) throws ServletException {
|
||||
|
||||
String skipPatternDefinition = filterConfig.getInitParameter(SKIP_PATTERN_PARAM);
|
||||
if (skipPatternDefinition != null) {
|
||||
skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
|
||||
}
|
||||
|
||||
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
|
||||
if (configResolverClass != null) {
|
||||
try {
|
||||
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
|
||||
deploymentContext = new AdapterDeploymentContext(configResolver);
|
||||
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
|
||||
} catch (Exception ex) {
|
||||
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
|
||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||
}
|
||||
if (definedconfigResolver != null) {
|
||||
deploymentContext = new AdapterDeploymentContext(definedconfigResolver);
|
||||
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", definedconfigResolver.getClass());
|
||||
} else {
|
||||
String fp = filterConfig.getInitParameter("keycloak.config.file");
|
||||
InputStream is = null;
|
||||
if (fp != null) {
|
||||
String configResolverClass = filterConfig.getInitParameter(CONFIG_RESOLVER_PARAM);
|
||||
if (configResolverClass != null) {
|
||||
try {
|
||||
is = new FileInputStream(fp);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
|
||||
deploymentContext = new AdapterDeploymentContext(configResolver);
|
||||
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
|
||||
} catch (Exception ex) {
|
||||
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
|
||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||
}
|
||||
} else {
|
||||
String path = "/WEB-INF/keycloak.json";
|
||||
String pathParam = filterConfig.getInitParameter("keycloak.config.path");
|
||||
if (pathParam != null) path = pathParam;
|
||||
is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||
String fp = filterConfig.getInitParameter(CONFIG_FILE_PARAM);
|
||||
InputStream is = null;
|
||||
if (fp != null) {
|
||||
try {
|
||||
is = new FileInputStream(fp);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
String path = "/WEB-INF/keycloak.json";
|
||||
String pathParam = filterConfig.getInitParameter(CONFIG_PATH_PARAM);
|
||||
if (pathParam != null) path = pathParam;
|
||||
is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||
}
|
||||
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
|
||||
deploymentContext = new AdapterDeploymentContext(kd);
|
||||
log.fine("Keycloak is using a per-deployment configuration.");
|
||||
}
|
||||
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
|
||||
deploymentContext = new AdapterDeploymentContext(kd);
|
||||
log.fine("Keycloak is using a per-deployment configuration.");
|
||||
}
|
||||
filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
|
||||
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;
|
||||
|
||||
@@ -201,7 +225,7 @@ public class KeycloakOIDCFilter implements Filter {
|
||||
*
|
||||
* @param request the request to check
|
||||
* @return {@code true} if the request should not be handled,
|
||||
* {@code false} otherwise.
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
private boolean shouldSkip(HttpServletRequest request) {
|
||||
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
|
||||
HttpSession httpSession = request.getSession();
|
||||
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
|
||||
httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
|
||||
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
|
||||
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getSessionState(), account.getPrincipal().getName(), httpSession.getId());
|
||||
//String username = securityContext.getToken().getSubject();
|
||||
//log.fine("userSessionManagement.login: " + username);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-boot-container-bundle</artifactId>
|
||||
@@ -52,6 +52,7 @@
|
||||
<include>org.keycloak:keycloak-jetty-adapter-spi</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<createSourcesJar>true</createSourcesJar>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+2
@@ -33,6 +33,7 @@ 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.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
||||
@@ -60,6 +61,7 @@ import java.util.Set;
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
|
||||
@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true)
|
||||
public class KeycloakAutoConfiguration {
|
||||
|
||||
private KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
+13
@@ -34,6 +34,11 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
@JsonIgnore
|
||||
private Map config = new HashMap();
|
||||
|
||||
/**
|
||||
* Allow enabling of Keycloak Spring Boot adapter by configuration.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public Map getConfig() {
|
||||
return config;
|
||||
}
|
||||
@@ -43,6 +48,14 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
*/
|
||||
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* This matches security-constraint of the servlet spec
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.keycloak.adapters.springsecurity;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Add this annotation to a class that extends {@code KeycloakWebSecurityConfigurerAdapter} to provide
|
||||
* a keycloak based Spring security configuration.
|
||||
*
|
||||
* @author Hendrik Ebbers
|
||||
*/
|
||||
@Retention(value = RUNTIME)
|
||||
@Target(value = { TYPE })
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
|
||||
@EnableWebSecurity
|
||||
public @interface KeycloakConfiguration {
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.springsecurity.authentication;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
|
||||
/**
|
||||
* To return the forbidden code with the corresponding message.
|
||||
*
|
||||
* @author emilienbondu
|
||||
*
|
||||
*/
|
||||
public class KeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate using the Authorization header");
|
||||
}
|
||||
}
|
||||
+35
-27
@@ -17,6 +17,13 @@
|
||||
|
||||
package org.keycloak.adapters.springsecurity.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
@@ -25,7 +32,7 @@ import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
||||
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
|
||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
|
||||
@@ -41,19 +48,12 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides a Keycloak authentication processing filter.
|
||||
*
|
||||
@@ -61,11 +61,13 @@ import java.io.IOException;
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
|
||||
|
||||
public static final String DEFAULT_LOGIN_URL = "/sso/login";
|
||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
public static final String SCHEME_BEARER = "bearer ";
|
||||
public static final String SCHEME_BASIC = "basic ";
|
||||
|
||||
|
||||
/**
|
||||
* Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
|
||||
* and any request with a <code>Authorization</code> header.
|
||||
@@ -89,7 +91,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
*/
|
||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
|
||||
this(authenticationManager, DEFAULT_REQUEST_MATCHER);
|
||||
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(DEFAULT_LOGIN_URL));
|
||||
setAuthenticationFailureHandler(new KeycloakAuthenticationFailureHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +111,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
*
|
||||
*/
|
||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher
|
||||
requiresAuthenticationRequestMatcher) {
|
||||
requiresAuthenticationRequestMatcher) {
|
||||
super(requiresAuthenticationRequestMatcher);
|
||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||
this.authenticationManager = authenticationManager;
|
||||
@@ -140,9 +142,27 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
log.debug("Auth outcome: {}", result);
|
||||
|
||||
if (AuthOutcome.FAILED.equals(result)) {
|
||||
throw new KeycloakAuthenticationException("Auth outcome: " + result);
|
||||
AuthChallenge challenge = authenticator.getChallenge();
|
||||
if (challenge != null) {
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
throw new KeycloakAuthenticationException("Invalid authorization header, see WWW-Authenticate header for details");
|
||||
}
|
||||
|
||||
|
||||
if (AuthOutcome.NOT_ATTEMPTED.equals(result)) {
|
||||
AuthChallenge challenge = authenticator.getChallenge();
|
||||
if (challenge != null) {
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
if (deployment.isBearerOnly()) {
|
||||
// no redirection in this mode, throwing exception for the spring handler
|
||||
throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
|
||||
} else {
|
||||
// let continue if challenged, it may redirect
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
|
||||
@@ -184,7 +204,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
|
||||
@Override
|
||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
|
||||
Authentication authResult) throws IOException, ServletException {
|
||||
Authentication authResult) throws IOException, ServletException {
|
||||
|
||||
if (!(this.isBearerTokenRequest(request) || this.isBasicAuthRequest(request))) {
|
||||
super.successfulAuthentication(request, response, chain, authResult);
|
||||
@@ -212,19 +232,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
|
||||
@Override
|
||||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException failed) throws IOException, ServletException {
|
||||
|
||||
if (this.isBearerTokenRequest(request)) {
|
||||
SecurityContextHolder.clearContext();
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate bearer token");
|
||||
return;
|
||||
}
|
||||
else if (this.isBasicAuthRequest(request)) {
|
||||
SecurityContextHolder.clearContext();
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication");
|
||||
return;
|
||||
}
|
||||
|
||||
AuthenticationException failed) throws IOException, ServletException {
|
||||
super.unsuccessfulAuthentication(request, response, failed);
|
||||
}
|
||||
|
||||
@@ -262,4 +270,4 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
public final void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
|
||||
throw new UnsupportedOperationException("This filter does not support explicitly setting a continue chain before success policy");
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-5
@@ -27,6 +27,7 @@ import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
@@ -89,6 +90,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
|
||||
@Mock
|
||||
private AuthenticationFailureHandler failureHandler;
|
||||
|
||||
private KeycloakAuthenticationFailureHandler keycloakFailureHandler;
|
||||
|
||||
@Mock
|
||||
private OidcKeycloakAccount keycloakAccount;
|
||||
@@ -106,6 +109,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
request = spy(new MockHttpServletRequest());
|
||||
filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
|
||||
keycloakFailureHandler = new KeycloakAuthenticationFailureHandler();
|
||||
|
||||
filter.setApplicationContext(applicationContext);
|
||||
filter.setAuthenticationSuccessHandler(successHandler);
|
||||
@@ -155,8 +159,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
|
||||
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
|
||||
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
|
||||
filter.attemptAuthentication(request, response);
|
||||
|
||||
filter.attemptAuthentication(request, response);
|
||||
verify(response).setStatus(302);
|
||||
verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
|
||||
}
|
||||
@@ -167,6 +171,13 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
filter.attemptAuthentication(request, response);
|
||||
}
|
||||
|
||||
@Test(expected = KeycloakAuthenticationException.class)
|
||||
public void testAttemptAuthenticationWithInvalidTokenBearerOnly() throws Exception {
|
||||
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.TRUE);
|
||||
request.addHeader("Authorization", "Bearer xxx");
|
||||
filter.attemptAuthentication(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulAuthenticationInteractive() throws Exception {
|
||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, authorities);
|
||||
@@ -210,8 +221,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||
this.setBearerAuthHeader(request);
|
||||
filter.unsuccessfulAuthentication(request, response, exception);
|
||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
|
||||
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
any(AuthenticationException.class));
|
||||
}
|
||||
|
||||
@@ -220,10 +230,18 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||
this.setBasicAuthHeader(request);
|
||||
filter.unsuccessfulAuthentication(request, response, exception);
|
||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
|
||||
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
any(AuthenticationException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultFailureHanlder() throws Exception {
|
||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||
filter.setAuthenticationFailureHandler(keycloakFailureHandler);
|
||||
filter.unsuccessfulAuthentication(request, response, exception);
|
||||
|
||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), any(String.class));
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testSetAllowSessionCreation() throws Exception {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Tomcat Integration</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public class ElytronSessionTokenStore implements ElytronTokeStore {
|
||||
public boolean isCached(RequestAuthenticator authenticator) {
|
||||
HttpScope session = this.httpFacade.getScope(Scope.SESSION);
|
||||
|
||||
if (session == null) {
|
||||
if (session == null || !session.supportsAttachments()) {
|
||||
log.debug("session was null, returning null");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak WildFly Integration</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+16
-1
@@ -124,6 +124,12 @@ public class SharedAttributeDefinitons {
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition CORS_EXPOSED_HEADERS =
|
||||
new SimpleAttributeDefinitionBuilder("cors-exposed-headers", ModelType.STRING, true)
|
||||
.setXmlName("cors-exposed-headers")
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
|
||||
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
|
||||
.setXmlName("expose-token")
|
||||
@@ -172,7 +178,14 @@ public class SharedAttributeDefinitons {
|
||||
.setXmlName("autodetect-bearer-only")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
.build();
|
||||
|
||||
protected static final SimpleAttributeDefinition IGNORE_OAUTH_QUERY_PARAMETER =
|
||||
new SimpleAttributeDefinitionBuilder("ignore-oauth-query-parameter", ModelType.BOOLEAN, true)
|
||||
.setXmlName("ignore-oauth-query-parameter")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
static {
|
||||
@@ -191,6 +204,7 @@ public class SharedAttributeDefinitons {
|
||||
ATTRIBUTES.add(CORS_MAX_AGE);
|
||||
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
|
||||
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
|
||||
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
|
||||
ATTRIBUTES.add(EXPOSE_TOKEN);
|
||||
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
|
||||
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
|
||||
@@ -199,6 +213,7 @@ public class SharedAttributeDefinitons {
|
||||
ATTRIBUTES.add(TOKEN_STORE);
|
||||
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
|
||||
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
|
||||
ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
@@ -39,6 +39,7 @@ keycloak.realm.client-key-password=n/a
|
||||
keycloak.realm.cors-max-age=CORS max-age header
|
||||
keycloak.realm.cors-allowed-headers=CORS allowed headers
|
||||
keycloak.realm.cors-allowed-methods=CORS allowed methods
|
||||
keycloak.realm.cors-exposed-headers=CORS exposed headers
|
||||
keycloak.realm.expose-token=Enable secure URL that exposes access token
|
||||
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
|
||||
keycloak.realm.always-refresh-token=Refresh token on every single web request
|
||||
@@ -47,6 +48,7 @@ keycloak.realm.register-node-period=how often to re-register node
|
||||
keycloak.realm.token-store=cookie or session storage for auth session data
|
||||
keycloak.realm.principal-attribute=token attribute to use to set Principal name
|
||||
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
|
||||
keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment=A deployment secured by Keycloak
|
||||
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
|
||||
@@ -73,6 +75,7 @@ keycloak.secure-deployment.client-key-password=n/a
|
||||
keycloak.secure-deployment.cors-max-age=CORS max-age header
|
||||
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
|
||||
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
|
||||
keycloak.secure-deployment.cors-exposed-headers=CORS exposed headers
|
||||
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
|
||||
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
|
||||
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
|
||||
@@ -84,6 +87,7 @@ keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is
|
||||
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.autodetect-bearer-only=autodetect bearer-only requests
|
||||
keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
|
||||
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
@@ -66,6 +67,7 @@
|
||||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
@@ -88,6 +90,7 @@
|
||||
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
@@ -109,6 +112,7 @@
|
||||
<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:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+56
@@ -37,6 +37,8 @@ import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD
|
||||
public final class KeycloakAdapterConfigService {
|
||||
|
||||
private static final String CREDENTIALS_JSON_NAME = "credentials";
|
||||
|
||||
private static final String REDIRECT_REWRITE_RULE_JSON_NAME = "redirect-rewrite-rule";
|
||||
|
||||
private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
|
||||
|
||||
@@ -129,6 +131,56 @@ public final class KeycloakAdapterConfigService {
|
||||
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
|
||||
return deployment.get(CREDENTIALS_JSON_NAME);
|
||||
}
|
||||
|
||||
public void addRedirectRewriteRule(ModelNode operation, ModelNode model) {
|
||||
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
|
||||
if (!redirectRewritesRules.isDefined()) {
|
||||
redirectRewritesRules = new ModelNode();
|
||||
}
|
||||
|
||||
String redirectRewriteRuleName = redirectRewriteRule(operation);
|
||||
if (!redirectRewriteRuleName.contains(".")) {
|
||||
redirectRewritesRules.get(redirectRewriteRuleName).set(model.get("value").asString());
|
||||
} else {
|
||||
String[] parts = redirectRewriteRuleName.split("\\.");
|
||||
String provider = parts[0];
|
||||
String property = parts[1];
|
||||
ModelNode redirectRewriteRule = redirectRewritesRules.get(provider);
|
||||
if (!redirectRewriteRule.isDefined()) {
|
||||
redirectRewriteRule = new ModelNode();
|
||||
}
|
||||
redirectRewriteRule.get(property).set(model.get("value").asString());
|
||||
redirectRewritesRules.set(provider, redirectRewriteRule);
|
||||
}
|
||||
|
||||
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
|
||||
deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME).set(redirectRewritesRules);
|
||||
}
|
||||
|
||||
public void removeRedirectRewriteRule(ModelNode operation) {
|
||||
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
|
||||
if (!redirectRewritesRules.isDefined()) {
|
||||
throw new RuntimeException("Can not remove redirect rewrite rule. No rules defined for deployment in op " + operation.toString());
|
||||
}
|
||||
|
||||
String ruleName = credentialNameFromOp(operation);
|
||||
redirectRewritesRules.remove(ruleName);
|
||||
}
|
||||
|
||||
public void updateRedirectRewriteRule(ModelNode operation, String attrName, ModelNode resolvedValue) {
|
||||
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
|
||||
if (!redirectRewritesRules.isDefined()) {
|
||||
throw new RuntimeException("Can not update redirect rewrite rule. No rules defined for deployment in op " + operation.toString());
|
||||
}
|
||||
|
||||
String ruleName = credentialNameFromOp(operation);
|
||||
redirectRewritesRules.get(ruleName).set(resolvedValue);
|
||||
}
|
||||
|
||||
private ModelNode redirectRewriteRuleFromOp(ModelNode operation) {
|
||||
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
|
||||
return deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME);
|
||||
}
|
||||
|
||||
private String realmNameFromOp(ModelNode operation) {
|
||||
return valueFromOpAddress(RealmDefinition.TAG_NAME, operation);
|
||||
@@ -141,6 +193,10 @@ public final class KeycloakAdapterConfigService {
|
||||
private String credentialNameFromOp(ModelNode operation) {
|
||||
return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation);
|
||||
}
|
||||
|
||||
private String redirectRewriteRule(ModelNode operation) {
|
||||
return valueFromOpAddress(RedirecRewritetRuleDefinition.TAG_NAME, operation);
|
||||
}
|
||||
|
||||
private String valueFromOpAddress(String addrElement, ModelNode operation) {
|
||||
String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);
|
||||
|
||||
+2
@@ -48,6 +48,7 @@ public class KeycloakExtension implements Extension {
|
||||
static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
|
||||
static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
|
||||
static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
|
||||
static final RedirecRewritetRuleDefinition REDIRECT_RULE_DEFINITON = new RedirecRewritetRuleDefinition();
|
||||
|
||||
public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
|
||||
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
|
||||
@@ -77,6 +78,7 @@ public class KeycloakExtension implements Extension {
|
||||
registration.registerSubModel(REALM_DEFINITION);
|
||||
ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
|
||||
secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
|
||||
secureDeploymentRegistration.registerSubModel(REDIRECT_RULE_DEFINITON);
|
||||
|
||||
subsystem.registerXMLElementWriter(PARSER);
|
||||
}
|
||||
|
||||
+85
@@ -96,12 +96,17 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||
PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name));
|
||||
addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
|
||||
List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
|
||||
List<ModelNode> redirectRulesToAdd = new ArrayList<ModelNode>();
|
||||
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
|
||||
String tagName = reader.getLocalName();
|
||||
if (tagName.equals(CredentialDefinition.TAG_NAME)) {
|
||||
readCredential(reader, addr, credentialsToAdd);
|
||||
continue;
|
||||
}
|
||||
if (tagName.equals(RedirecRewritetRuleDefinition.TAG_NAME)) {
|
||||
readRewriteRule(reader, addr, redirectRulesToAdd);
|
||||
continue;
|
||||
}
|
||||
|
||||
SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName);
|
||||
if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName);
|
||||
@@ -111,6 +116,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||
// Must add credentials after the deployment is added.
|
||||
resourcesToAdd.add(addSecureDeployment);
|
||||
resourcesToAdd.addAll(credentialsToAdd);
|
||||
resourcesToAdd.addAll(redirectRulesToAdd);
|
||||
}
|
||||
|
||||
public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
|
||||
@@ -149,6 +155,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readRewriteRule(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> rewriteRuleToToAdd) throws XMLStreamException {
|
||||
String name = readNameAttribute(reader);
|
||||
|
||||
Map<String, String> values = new HashMap<>();
|
||||
String textValue = null;
|
||||
while (reader.hasNext()) {
|
||||
int next = reader.next();
|
||||
if (next == CHARACTERS) {
|
||||
// text value of redirect rule element
|
||||
String text = reader.getText();
|
||||
if (text == null || text.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
textValue = text;
|
||||
} else if (next == START_ELEMENT) {
|
||||
String key = reader.getLocalName();
|
||||
reader.next();
|
||||
String value = reader.getText();
|
||||
reader.next();
|
||||
|
||||
values.put(key, value);
|
||||
} else if (next == END_ELEMENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (textValue != null) {
|
||||
ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name, textValue);
|
||||
rewriteRuleToToAdd.add(addRedirectRule);
|
||||
} else {
|
||||
for (Map.Entry<String, String> entry : values.entrySet()) {
|
||||
ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name + "." + entry.getKey(), entry.getValue());
|
||||
rewriteRuleToToAdd.add(addRedirectRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
|
||||
ModelNode addCredential = new ModelNode();
|
||||
@@ -158,6 +201,15 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||
addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
|
||||
return addCredential;
|
||||
}
|
||||
|
||||
private ModelNode getRedirectRuleToAdd(PathAddress parent, String name, String value) {
|
||||
ModelNode addRedirectRule = new ModelNode();
|
||||
addRedirectRule.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
|
||||
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(RedirecRewritetRuleDefinition.TAG_NAME, name));
|
||||
addRedirectRule.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
|
||||
addRedirectRule.get(RedirecRewritetRuleDefinition.VALUE.getName()).set(value);
|
||||
return addRedirectRule;
|
||||
}
|
||||
|
||||
// expects that the current tag will have one single attribute called "name"
|
||||
private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException {
|
||||
@@ -219,6 +271,11 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||
if (credentials.isDefined()) {
|
||||
writeCredentials(writer, credentials);
|
||||
}
|
||||
|
||||
ModelNode redirectRewriteRule = deploymentElements.get(RedirecRewritetRuleDefinition.TAG_NAME);
|
||||
if (redirectRewriteRule.isDefined()) {
|
||||
writeRedirectRules(writer, redirectRewriteRule);
|
||||
}
|
||||
|
||||
writer.writeEndElement();
|
||||
}
|
||||
@@ -265,6 +322,34 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
||||
writer.writeEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRedirectRules(XMLExtendedStreamWriter writer, ModelNode redirectRules) throws XMLStreamException {
|
||||
Map<String, Object> parsed = new LinkedHashMap<>();
|
||||
for (Property redirectRule : redirectRules.asPropertyList()) {
|
||||
String ruleName = redirectRule.getName();
|
||||
String ruleValue = redirectRule.getValue().get(RedirecRewritetRuleDefinition.VALUE.getName()).asString();
|
||||
parsed.put(ruleName, ruleValue);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : parsed.entrySet()) {
|
||||
writer.writeStartElement(RedirecRewritetRuleDefinition.TAG_NAME);
|
||||
writer.writeAttribute("name", entry.getKey());
|
||||
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof String) {
|
||||
writeCharacters(writer, (String) value);
|
||||
} else {
|
||||
Map<String, String> redirectRulesProps = (Map<String, String>) value;
|
||||
for (Map.Entry<String, String> prop : redirectRulesProps.entrySet()) {
|
||||
writer.writeStartElement(prop.getKey());
|
||||
writeCharacters(writer, prop.getValue());
|
||||
writer.writeEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
writer.writeEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
// code taken from org.jboss.as.controller.AttributeMarshaller
|
||||
private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException {
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.subsystem.adapter.extension;
|
||||
|
||||
import org.jboss.as.controller.AttributeDefinition;
|
||||
import org.jboss.as.controller.PathElement;
|
||||
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
|
||||
import org.jboss.as.controller.SimpleResourceDefinition;
|
||||
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
|
||||
import org.jboss.as.controller.operations.validation.StringLengthValidator;
|
||||
import org.jboss.as.controller.registry.ManagementResourceRegistration;
|
||||
import org.jboss.dmr.ModelType;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sblanc
|
||||
*/
|
||||
public class RedirecRewritetRuleDefinition extends SimpleResourceDefinition {
|
||||
|
||||
public static final String TAG_NAME = "redirect-rewrite-rule";
|
||||
|
||||
protected static final AttributeDefinition VALUE =
|
||||
new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false)
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true))
|
||||
.build();
|
||||
|
||||
public RedirecRewritetRuleDefinition() {
|
||||
super(PathElement.pathElement(TAG_NAME),
|
||||
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
|
||||
new RedirectRewriteRuleAddHandler(VALUE),
|
||||
RedirectRewriteRuleRemoveHandler.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
|
||||
super.registerOperations(resourceRegistration);
|
||||
resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
|
||||
super.registerAttributes(resourceRegistration);
|
||||
resourceRegistration.registerReadWriteAttribute(VALUE, null, new RedirectRewriteRuleReadWriteAttributeHandler());
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.subsystem.adapter.extension;
|
||||
|
||||
import org.jboss.as.controller.AbstractAddStepHandler;
|
||||
import org.jboss.as.controller.AttributeDefinition;
|
||||
import org.jboss.as.controller.OperationContext;
|
||||
import org.jboss.as.controller.OperationFailedException;
|
||||
import org.jboss.dmr.ModelNode;
|
||||
|
||||
public class RedirectRewriteRuleAddHandler extends AbstractAddStepHandler {
|
||||
|
||||
public RedirectRewriteRuleAddHandler(AttributeDefinition... attributes) {
|
||||
super(attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
|
||||
ckService.addRedirectRewriteRule(operation, context.resolveExpressions(model));
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.subsystem.adapter.extension;
|
||||
|
||||
import org.jboss.as.controller.AbstractWriteAttributeHandler;
|
||||
import org.jboss.as.controller.OperationContext;
|
||||
import org.jboss.as.controller.OperationFailedException;
|
||||
import org.jboss.dmr.ModelNode;
|
||||
|
||||
public class RedirectRewriteRuleReadWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
|
||||
|
||||
@Override
|
||||
protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
|
||||
ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
|
||||
|
||||
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
|
||||
ckService.updateRedirectRewriteRule(operation, attributeName, resolvedValue);
|
||||
|
||||
hh.setHandback(ckService);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
|
||||
ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
|
||||
ckService.updateRedirectRewriteRule(operation, attributeName, valueToRestore);
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.subsystem.adapter.extension;
|
||||
|
||||
import org.jboss.as.controller.AbstractRemoveStepHandler;
|
||||
import org.jboss.as.controller.OperationContext;
|
||||
import org.jboss.as.controller.OperationFailedException;
|
||||
import org.jboss.dmr.ModelNode;
|
||||
|
||||
public class RedirectRewriteRuleRemoveHandler extends AbstractRemoveStepHandler {
|
||||
|
||||
public static RedirectRewriteRuleRemoveHandler INSTANCE = new RedirectRewriteRuleRemoveHandler();
|
||||
|
||||
private RedirectRewriteRuleRemoveHandler() {}
|
||||
|
||||
@Override
|
||||
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
|
||||
ckService.removeRedirectRewriteRule(operation);
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -124,6 +124,12 @@ public class SharedAttributeDefinitons {
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition CORS_EXPOSED_HEADERS =
|
||||
new SimpleAttributeDefinitionBuilder("cors-exposed-headers", ModelType.STRING, true)
|
||||
.setXmlName("cors-exposed-headers")
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
|
||||
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
|
||||
.setXmlName("expose-token")
|
||||
@@ -173,6 +179,14 @@ public class SharedAttributeDefinitons {
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
protected static final SimpleAttributeDefinition IGNORE_OAUTH_QUERY_PARAMETER =
|
||||
new SimpleAttributeDefinitionBuilder("ignore-oauth-query-parameter", ModelType.BOOLEAN, true)
|
||||
.setXmlName("ignore-oauth-query-parameter")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
@@ -192,6 +206,7 @@ public class SharedAttributeDefinitons {
|
||||
ATTRIBUTES.add(CORS_MAX_AGE);
|
||||
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
|
||||
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
|
||||
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
|
||||
ATTRIBUTES.add(EXPOSE_TOKEN);
|
||||
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
|
||||
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
|
||||
@@ -200,6 +215,7 @@ public class SharedAttributeDefinitons {
|
||||
ATTRIBUTES.add(TOKEN_STORE);
|
||||
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
|
||||
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
|
||||
ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
|
||||
}
|
||||
|
||||
private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
|
||||
|
||||
+11
-1
@@ -39,6 +39,7 @@ keycloak.realm.client-key-password=n/a
|
||||
keycloak.realm.cors-max-age=CORS max-age header
|
||||
keycloak.realm.cors-allowed-headers=CORS allowed headers
|
||||
keycloak.realm.cors-allowed-methods=CORS allowed methods
|
||||
keycloak.realm.cors-exposed-headers=CORS exposed headers
|
||||
keycloak.realm.expose-token=Enable secure URL that exposes access token
|
||||
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
|
||||
keycloak.realm.always-refresh-token=Refresh token on every single web request
|
||||
@@ -47,6 +48,7 @@ keycloak.realm.register-node-period=how often to re-register node
|
||||
keycloak.realm.token-store=cookie or session storage for auth session data
|
||||
keycloak.realm.principal-attribute=token attribute to use to set Principal name
|
||||
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
|
||||
keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment=A deployment secured by Keycloak
|
||||
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
|
||||
@@ -63,6 +65,7 @@ keycloak.secure-deployment.connection-pool-size=Connection pool size for the cli
|
||||
keycloak.secure-deployment.resource=Application name
|
||||
keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
|
||||
keycloak.secure-deployment.credentials=Adapter credentials
|
||||
keycloak.secure-deployment.redirect-rewrite-rule=Apply a rewrite rule for the redirect URI
|
||||
keycloak.secure-deployment.bearer-only=Bearer Token Auth only
|
||||
keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
|
||||
keycloak.secure-deployment.public-client=Public client
|
||||
@@ -74,6 +77,7 @@ keycloak.secure-deployment.client-key-password=n/a
|
||||
keycloak.secure-deployment.cors-max-age=CORS max-age header
|
||||
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
|
||||
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
|
||||
keycloak.secure-deployment.cors-exposed-headers=CORS exposed headers
|
||||
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
|
||||
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
|
||||
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
|
||||
@@ -84,10 +88,16 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
|
||||
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.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
keycloak.credential=Credential
|
||||
keycloak.credential.value=Credential value
|
||||
keycloak.credential.add=Credential add
|
||||
keycloak.credential.remove=Credential remove
|
||||
keycloak.credential.remove=Credential remove
|
||||
|
||||
keycloak.redirect-rewrite-rule=redirect-rewrite-rule
|
||||
keycloak.redirect-rewrite-rule.value=redirect-rewrite-rule value
|
||||
keycloak.redirect-rewrite-rule.add=redirect-rewrite-rule add
|
||||
keycloak.redirect-rewrite-rule.remove=redirect-rewrite-rule remove
|
||||
+11
@@ -58,6 +58,7 @@
|
||||
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
|
||||
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
@@ -66,6 +67,7 @@
|
||||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
@@ -88,6 +90,7 @@
|
||||
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="cors-exposed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
@@ -98,6 +101,7 @@
|
||||
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
|
||||
<xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
|
||||
<xs:element name="redirect-rewrite-rule" type="redirect-rewrite-rule-type" minOccurs="1" maxOccurs="1"/>
|
||||
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
@@ -109,6 +113,7 @@
|
||||
<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:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="ignore-oauth-query-parameter" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
@@ -123,4 +128,10 @@
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
</xs:complexType>
|
||||
<xs:complexType name="redirect-rewrite-rule-type" mixed="true">
|
||||
<xs:sequence maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:any processContents="lax"></xs:any>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
+2
@@ -53,6 +53,7 @@
|
||||
<auth-server-url>http://localhost:8080/auth</auth-server-url>
|
||||
<ssl-required>EXTERNAL</ssl-required>
|
||||
<credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
|
||||
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">api/$1/</redirect-rewrite-rule>
|
||||
</secure-deployment>
|
||||
<secure-deployment name="http-endpoint">
|
||||
<realm>master</realm>
|
||||
@@ -66,5 +67,6 @@
|
||||
<credential name="jwt">
|
||||
<client-keystore-file>/tmp/keystore.jks</client-keystore-file>
|
||||
</credential>
|
||||
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">/api/$1/</redirect-rewrite-rule>
|
||||
</secure-deployment>
|
||||
</subsystem>
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Adapters</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak SAML EAP Integration</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+10
@@ -294,6 +294,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
||||
private String logoutPage;
|
||||
private SignatureAlgorithm signatureAlgorithm;
|
||||
private String signatureCanonicalizationMethod;
|
||||
private boolean autodetectBearerOnly;
|
||||
|
||||
@Override
|
||||
public boolean turnOffChangeSessionIdOnLogin() {
|
||||
@@ -439,4 +440,13 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
||||
public void setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
|
||||
this.signatureAlgorithm = signatureAlgorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutodetectBearerOnly() {
|
||||
return autodetectBearerOnly;
|
||||
}
|
||||
|
||||
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
|
||||
this.autodetectBearerOnly = autodetectBearerOnly;
|
||||
}
|
||||
}
|
||||
@@ -168,6 +168,6 @@ public interface SamlDeployment {
|
||||
}
|
||||
PrincipalNamePolicy getPrincipalNamePolicy();
|
||||
String getPrincipalAttributeName();
|
||||
|
||||
boolean isAutodetectBearerOnly();
|
||||
|
||||
}
|
||||
@@ -58,6 +58,7 @@ public class SP implements Serializable {
|
||||
private PrincipalNameMapping principalNameMapping;
|
||||
private Set<String> roleAttributes;
|
||||
private IDP idp;
|
||||
private boolean autodetectBearerOnly;
|
||||
|
||||
public String getEntityID() {
|
||||
return entityID;
|
||||
@@ -147,4 +148,11 @@ public class SP implements Serializable {
|
||||
this.logoutPage = logoutPage;
|
||||
}
|
||||
|
||||
public boolean isAutodetectBearerOnly() {
|
||||
return autodetectBearerOnly;
|
||||
}
|
||||
|
||||
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
|
||||
this.autodetectBearerOnly = autodetectBearerOnly;
|
||||
}
|
||||
}
|
||||
+1
@@ -33,6 +33,7 @@ public class ConfigXmlConstants {
|
||||
public static final String SIGNATURE_ALGORITHM_ATTR = "signatureAlgorithm";
|
||||
public static final String SIGNATURE_CANONICALIZATION_METHOD_ATTR = "signatureCanonicalizationMethod";
|
||||
public static final String LOGOUT_PAGE_ATTR = "logoutPage";
|
||||
public static final String AUTODETECT_BEARER_ONLY_ATTR = "autodetectBearerOnly";
|
||||
|
||||
public static final String KEYS_ELEMENT = "Keys";
|
||||
public static final String KEY_ELEMENT = "Key";
|
||||
|
||||
+1
@@ -68,6 +68,7 @@ public class DeploymentBuilder {
|
||||
deployment.setNameIDPolicyFormat(sp.getNameIDPolicyFormat());
|
||||
deployment.setLogoutPage(sp.getLogoutPage());
|
||||
deployment.setSignatureCanonicalizationMethod(sp.getIdp().getSignatureCanonicalizationMethod());
|
||||
deployment.setAutodetectBearerOnly(sp.isAutodetectBearerOnly());
|
||||
deployment.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
|
||||
if (sp.getIdp().getSignatureAlgorithm() != null) {
|
||||
deployment.setSignatureAlgorithm(SignatureAlgorithm.valueOf(sp.getIdp().getSignatureAlgorithm()));
|
||||
|
||||
+1
@@ -89,6 +89,7 @@ public class SPXmlParser extends AbstractParser {
|
||||
sp.setNameIDPolicyFormat(getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR));
|
||||
sp.setForceAuthentication(getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR));
|
||||
sp.setIsPassive(getBooleanAttributeValue(startElement, ConfigXmlConstants.IS_PASSIVE_ATTR));
|
||||
sp.setAutodetectBearerOnly(getBooleanAttributeValue(startElement, ConfigXmlConstants.AUTODETECT_BEARER_ONLY_ATTR));
|
||||
sp.setTurnOffChangeSessionIdOnLogin(getBooleanAttributeValue(startElement, ConfigXmlConstants.TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN_ATTR));
|
||||
while (xmlEventReader.hasNext()) {
|
||||
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
|
||||
|
||||
+2
-3
@@ -34,6 +34,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||
import org.keycloak.saml.common.util.DocumentUtil;
|
||||
import org.keycloak.saml.processing.core.util.NamespaceContext;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
@@ -65,9 +66,7 @@ public class SamlDescriptorIDPKeysExtractor {
|
||||
MultivaluedHashMap<String, KeyInfo> res = new MultivaluedHashMap<>();
|
||||
|
||||
try {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
DocumentBuilder builder = DocumentUtil.getDocumentBuilder();
|
||||
Document doc = builder.parse(stream);
|
||||
|
||||
XPathExpression expr = xpath.compile("/m:EntitiesDescriptor/m:EntityDescriptor/m:IDPSSODescriptor/m:KeyDescriptor");
|
||||
|
||||
+83
-11
@@ -53,10 +53,12 @@ import org.keycloak.saml.SAML2AuthnRequestBuilder;
|
||||
import org.keycloak.saml.SAMLRequestParser;
|
||||
import org.keycloak.saml.SignatureAlgorithm;
|
||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLConstants;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.saml.common.util.Base64;
|
||||
import org.keycloak.saml.common.util.DocumentUtil;
|
||||
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
|
||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
||||
@@ -74,10 +76,14 @@ import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.util.*;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
|
||||
import org.keycloak.rotation.KeyLocator;
|
||||
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
||||
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -210,7 +216,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
||||
return AuthOutcome.FAILED;
|
||||
}
|
||||
}
|
||||
return handleLoginResponse((ResponseType) statusResponse, postBinding, onCreateSession);
|
||||
return handleLoginResponse(holder, postBinding, onCreateSession);
|
||||
} finally {
|
||||
sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
|
||||
}
|
||||
@@ -312,7 +318,8 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
||||
return false;
|
||||
}
|
||||
|
||||
protected AuthOutcome handleLoginResponse(final ResponseType responseType, boolean postBinding, OnSessionCreated onCreateSession) {
|
||||
protected AuthOutcome handleLoginResponse(SAMLDocumentHolder responseHolder, boolean postBinding, OnSessionCreated onCreateSession) {
|
||||
final ResponseType responseType = (ResponseType) responseHolder.getSamlObject();
|
||||
AssertionType assertion = null;
|
||||
if (! isSuccessfulSamlResponse(responseType) || responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
|
||||
challenge = new AuthChallenge() {
|
||||
@@ -357,14 +364,33 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
||||
|
||||
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
|
||||
try {
|
||||
validateSamlSignature(new SAMLDocumentHolder(AssertionUtil.asDocument(assertion)), postBinding, GeneralConstants.SAML_RESPONSE_KEY);
|
||||
} catch (VerificationException e) {
|
||||
log.error("Failed to verify saml assertion signature", e);
|
||||
if (!AssertionUtil.isSignatureValid(getAssertionFromResponse(responseHolder), deployment.getIDP().getSignatureValidationKeyLocator())) {
|
||||
log.error("Failed to verify saml assertion signature");
|
||||
|
||||
challenge = new AuthChallenge() {
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade exchange) {
|
||||
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
|
||||
exchange.getRequest().setError(error);
|
||||
exchange.getResponse().sendError(403);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return 403;
|
||||
}
|
||||
};
|
||||
return AuthOutcome.FAILED;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing validation of SAML assertion: " + e.getMessage());
|
||||
challenge = new AuthChallenge() {
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade exchange) {
|
||||
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
|
||||
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE);
|
||||
exchange.getRequest().setError(error);
|
||||
exchange.getResponse().sendError(403);
|
||||
return true;
|
||||
@@ -376,8 +402,6 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
||||
}
|
||||
};
|
||||
return AuthOutcome.FAILED;
|
||||
} catch (ProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,6 +504,18 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
||||
&& Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
|
||||
}
|
||||
|
||||
private Element getAssertionFromResponse(final SAMLDocumentHolder responseHolder) throws ConfigurationException, ProcessingException {
|
||||
Element encryptedAssertion = DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
|
||||
if (encryptedAssertion != null) {
|
||||
// encrypted assertion.
|
||||
// We'll need to decrypt it first.
|
||||
Document encryptedAssertionDocument = DocumentUtil.createDocument();
|
||||
encryptedAssertionDocument.appendChild(encryptedAssertionDocument.importNode(encryptedAssertion, true));
|
||||
return XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
|
||||
}
|
||||
return DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
|
||||
}
|
||||
|
||||
private String getAttributeValue(Object attrValue) {
|
||||
String value = null;
|
||||
if (attrValue instanceof String) {
|
||||
@@ -529,9 +565,15 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
||||
return new AbstractInitiateLogin(deployment, sessionStore) {
|
||||
@Override
|
||||
protected void sendAuthnRequest(HttpFacade httpFacade, SAML2AuthnRequestBuilder authnRequestBuilder, BaseSAML2BindingBuilder binding) throws ProcessingException, ConfigurationException, IOException {
|
||||
Document document = authnRequestBuilder.toDocument();
|
||||
SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
|
||||
SamlUtil.sendSaml(true, httpFacade, deployment.getIDP().getSingleSignOnService().getRequestBindingUrl(), binding, document, samlBinding);
|
||||
if (isAutodetectedBearerOnly(httpFacade.getRequest())) {
|
||||
httpFacade.getResponse().setStatus(401);
|
||||
httpFacade.getResponse().end();
|
||||
}
|
||||
else {
|
||||
Document document = authnRequestBuilder.toDocument();
|
||||
SamlDeployment.Binding samlBinding = deployment.getIDP().getSingleSignOnService().getRequestBinding();
|
||||
SamlUtil.sendSaml(true, httpFacade, deployment.getIDP().getSingleSignOnService().getRequestBindingUrl(), binding, document, samlBinding);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -654,4 +696,34 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
||||
|
||||
return signature.verify(decodedSignature);
|
||||
}
|
||||
|
||||
protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
|
||||
if (!deployment.isAutodetectBearerOnly()) return false;
|
||||
|
||||
String headerValue = facade.getRequest().getHeader(GeneralConstants.HTTP_HEADER_X_REQUESTED_WITH);
|
||||
if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
headerValue = facade.getRequest().getHeader("Faces-Request");
|
||||
if (headerValue != null && headerValue.startsWith("partial/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
headerValue = facade.getRequest().getHeader("SOAPAction");
|
||||
if (headerValue != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<String> accepts = facade.getRequest().getHeaders("Accept");
|
||||
if (accepts == null) accepts = Collections.emptyList();
|
||||
|
||||
for (String accept : accepts) {
|
||||
if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<xs:schema version="1.0"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:keycloak:saml:adapter"
|
||||
targetNamespace="urn:keycloak:saml:adapter"
|
||||
elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
|
||||
<xs:element name="keycloak-saml-adapter" type="adapter-type"/>
|
||||
<xs:complexType name="adapter-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Keycloak SAML Adapter configuration file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:all>
|
||||
<xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Describes SAML service provider configuration.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="sp-type">
|
||||
<xs:all>
|
||||
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
List of service provider encryption and validation keys.
|
||||
|
||||
If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. For client signed documents you must define both the private and public key or certificate that will be used to sign documents. For encryption, you only have to define the private key that will be used to decrypt.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="RoleIdentifiers" type="role-identifiers-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines what SAML attributes within the assertion received from the user should be used as role identifiers within the Java EE Security Context for the user.
|
||||
By default Role attribute values are converted to Java EE roles. Some IDPs send roles via a member or memberOf attribute assertion. You can define one or more Attribute elements to specify which SAML attributes must be converted into roles.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Describes configuration of SAML identity provider for this service provider.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="entityID" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the identifier for this client. The IDP needs this value to determine who the client is that is communicating with it.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="sslPolicy" type="ssl-policy-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SSL policy the adapter will enforce.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. It must be a standard SAML format identifier, i.e. urn:oasis:names:tc:SAML:2.0:nameid-format:transient. By default, no special format is requested.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="logoutPage" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>URL of the logout page.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="forceAuthentication" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. Default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="isPassive" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to true if you want this. Do not use together with forceAuthentication as they are opposite. Default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true to disable this. It is recommended you do not turn it off. Default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="autodetectBearerOnly" type="xs:boolean" use="optional" default="false">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This should be set to true if your application serves both a web application and web services (e.g. SOAP or REST). It allows you to redirect unauthenticated users of the web application to the Keycloak login page, but send an HTTP 401 status code to unauthenticated SOAP or REST clients instead as they would not understand a redirect to the login page. Keycloak auto-detects SOAP or REST clients based on typical headers like X-Requested-With, SOAPAction or Accept. The default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="keys-type">
|
||||
<xs:sequence>
|
||||
<xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Describes a single key used for signing or encryption.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="key-type">
|
||||
<xs:all>
|
||||
<xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Java keystore to load keys and certificates from.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Private key (PEM format)</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Public key (PEM format)</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Certificate key (PEM format)</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="signing" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Flag defining whether the key should be used for signing.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="encryption" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Flag defining whether the key should be used for encryption</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="key-store-type">
|
||||
<xs:all>
|
||||
<xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Private key declaration</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Certificate declaration</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="file" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>File path to the key store.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="resource" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>WAR resource path to the key store. This is a path used in method call to ServletContext.getResourceAsStream().</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="password" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The password of the key store.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="private-key-type">
|
||||
<xs:attribute name="alias" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="password" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Keystores require an additional password to access private keys. In the PrivateKey element you must define this password within a password attribute.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="certificate-type">
|
||||
<xs:attribute name="alias" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="principal-name-mapping-type">
|
||||
<xs:attribute name="policy" type="principal-name-mapping-policy-type" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Policy used to populate value of Java Principal object obtained from methods like HttpServletRequest.getUserPrincipal().</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="attribute" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Name of the SAML assertion attribute to use within.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="principal-name-mapping-policy-type">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="FROM_NAME_ID">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This policy just uses whatever the SAML subject value is. This is the default setting</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="FROM_ATTRIBUTE">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This will pull the value from one of the attributes declared in the SAML assertion received from the server. You'll need to specify the name of the SAML assertion attribute to use within the attribute XML attribute.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="ssl-policy-type">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="ALL">
|
||||
<xs:annotation>
|
||||
<xs:documentation>All requests must come in via HTTPS.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="EXTERNAL">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Only non-private IP addresses must come over the wire via HTTPS.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="NONE">
|
||||
<xs:annotation>
|
||||
<xs:documentation>no requests are required to come over via HTTPS.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="signature-algorithm-type">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="RSA_SHA1"/>
|
||||
<xs:enumeration value="RSA_SHA256"/>
|
||||
<xs:enumeration value="RSA_SHA512"/>
|
||||
<xs:enumeration value="DSA_SHA1"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="binding-type">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="POST"/>
|
||||
<xs:enumeration value="REDIRECT"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:complexType name="role-identifiers-type">
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies SAML attribute to be converted into roles.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="attribute-type">
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies name of the SAML attribute to be converted into roles.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="idp-type">
|
||||
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Configuration of the login SAML endpoint of the IDP.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Configuration of the logout SAML endpoint of the IDP</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="HttpClient" type="http-client-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Configuration of HTTP client used for automatic obtaining of certificates containing public keys for IDP signature verification via SAML descriptor of the IDP.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="entityID" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>issuer ID of the IDP.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="signaturesRequired" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If set to true, the client adapter will sign every document it sends to the IDP. Also, the client will expect that the IDP will be signing any documents sent to it. This switch sets the default for all request and response types.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="signatureAlgorithm" type="signature-algorithm-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Signature algorithm that the IDP expects signed documents to use. Defaults to RSA_SHA256</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the signature canonicalization method that the IDP expects signed documents to use. The default value is https://www.w3.org/2001/10/xml-exc-c14n# and should be good for most IDPs.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="encryption" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation></xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="sign-on-type">
|
||||
<xs:attribute name="signRequest" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="validateAssertionSignature" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client expect the IDP to sign the individual assertions sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="requestBinding" type="binding-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML binding type used for communicating with the IDP. The default value is POST, but you can set it to REDIRECT as well.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="responseBinding" type="binding-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML allows the client to request what binding type it wants authn responses to use. This value maps to ProtocolBinding attribute in SAML AuthnRequest. The default is that the client will not request a specific binding type for responses.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="bindingUrl" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the URL for the IDP login service that the client will send requests to.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="assertionConsumerServiceUrl" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>URL of the assertion consumer service (ACS) where the IDP login service should send responses to. By default it is unset, relying on the IdP settings. When set, it must end in "/saml". This property is typically accompanied by the responseBinding attribute.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="logout-type">
|
||||
<xs:attribute name="signRequest" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="signResponse" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client sign logout responses it sends to the IDP requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client expect signed logout request documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client expect signed logout response documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="requestBinding" type="binding-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the SAML binding type used for communicating SAML requests to the IDP. The default value is POST.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="responseBinding" type="binding-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the SAML binding type used for communicating SAML responses to the IDP. The default value is POST.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="postBindingUrl" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the URL for the IDP's logout service when using the POST binding. This setting is REQUIRED if using the POST binding.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="redirectBindingUrl" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the URL for the IDP's logout service when using the REDIRECT binding. This setting is REQUIRED if using the REDIRECT binding.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="http-client-type">
|
||||
<xs:attribute name="allowAnyHostname" type="xs:boolean" use="optional" default="false">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If the the IDP server requires HTTPS and this config option is set to true the IDP's certificate
|
||||
is validated via the truststore, but host name validation is not done. This setting should only be used during
|
||||
development and never in production as it will partly disable verification of SSL certificates.
|
||||
This seting may be useful in test environments. The default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="clientKeystore" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the file path to a keystore file. This keystore contains client certificate
|
||||
for two-way SSL when the adapter makes HTTPS requests to the IDP server.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="clientKeystorePassword" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Password for the client keystore and for the client's key.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="connectionPoolSize" type="xs:int" use="optional" default="10">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines number of pooled connections.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="disableTrustManager" type="xs:boolean" use="optional" default="false">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If the the IDP server requires HTTPS and this config option is set to true you do not have to specify a truststore.
|
||||
This setting should only be used during development and never in production as it will disable verification of SSL certificates.
|
||||
The default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="proxyUrl" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>URL to HTTP proxy to use for HTTP connections.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="truststore" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
|
||||
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
|
||||
HTTPS communications to the IDP server. Client making HTTPS requests need
|
||||
a way to verify the host of the server they are talking to. This is what the trustore does.
|
||||
The keystore contains one or more trusted host certificates or certificate authorities.
|
||||
You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="truststorePassword" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Password for the truststore keystore.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
||||
+2
-1
@@ -37,7 +37,7 @@ import org.keycloak.saml.common.exceptions.ParsingException;
|
||||
*/
|
||||
public class KeycloakSamlAdapterXMLParserTest {
|
||||
|
||||
private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_7.xsd";
|
||||
private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_9.xsd";
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
@@ -91,6 +91,7 @@ public class KeycloakSamlAdapterXMLParserTest {
|
||||
assertEquals("format", sp.getNameIDPolicyFormat());
|
||||
assertTrue(sp.isForceAuthentication());
|
||||
assertTrue(sp.isIsPassive());
|
||||
assertFalse(sp.isAutodetectBearerOnly());
|
||||
assertEquals(2, sp.getKeys().size());
|
||||
Key signing = sp.getKeys().get(0);
|
||||
assertTrue(signing.isSigning());
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@
|
||||
|
||||
<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
|
||||
xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_9.xsd">
|
||||
<SP entityID="sp"
|
||||
sslPolicy="EXTERNAL"
|
||||
nameIDPolicyFormat="format"
|
||||
@@ -73,4 +73,4 @@
|
||||
</Keys>
|
||||
</IDP>
|
||||
</SP>
|
||||
</keycloak-saml-adapter>
|
||||
</keycloak-saml-adapter>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak SAML Jetty Integration</name>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak SAML Client Adapter Modules</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.CR1-SNAPSHOT</version>
|
||||
<version>3.2.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
Loaded 100 of 1944 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user