keycloak-uncached
Changes
docbook/reference/en/en-US/modules/proxy.xml 19(+19 -0)
Details
docbook/reference/en/en-US/modules/proxy.xml 19(+19 -0)
diff --git a/docbook/reference/en/en-US/modules/proxy.xml b/docbook/reference/en/en-US/modules/proxy.xml
index f5f6053..3a17557 100755
--- a/docbook/reference/en/en-US/modules/proxy.xml
+++ b/docbook/reference/en/en-US/modules/proxy.xml
@@ -30,6 +30,7 @@ $ java -jar bin/launcher.jar [your-config.json]
<programlisting><![CDATA[
{
"target-url": "http://localhost:8082",
+ "send-access-token": true,
"bind-address": "localhost",
"http-port": "8080",
"https-port": "8443",
@@ -93,6 +94,15 @@ $ java -jar bin/launcher.jar [your-config.json]
</listitem>
</varlistentry>
<varlistentry>
+ <term>send-access-token</term>
+ <listitem>
+ <para>
+ Boolean flag. If true, this will send the access token via the KEYCLOAK_ACCESS_TOKEN header to the
+ proxied server. <emphasis>OPTIONAL.</emphasis>. Default is false.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
<term>bind-address</term>
<listitem>
<para>
@@ -313,6 +323,15 @@ $ java -jar bin/launcher.jar [your-config.json]
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>KEYCLOAK_ACCESS_TOKEN</term>
+ <listitem>
+ <para>
+ Send the access token in this header if the proxy was configured to send it. This token can
+ be used to make bearer token requests.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</para>
</section>
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
index d0ff18b..9889d51 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
@@ -17,13 +17,16 @@ public class ConstraintAuthorizationHandler implements HttpHandler {
public static final HttpString KEYCLOAK_USERNAME = new HttpString("KEYCLOAK_USERNAME");
public static final HttpString KEYCLOAK_EMAIL = new HttpString("KEYCLOAK_EMAIL");
public static final HttpString KEYCLOAK_NAME = new HttpString("KEYCLOAK_NAME");
+ public static final HttpString KEYCLOAK_ACCESS_TOKEN = new HttpString("KEYCLOAK_ACCESS_TOKEN");
protected HttpHandler next;
protected String errorPage;
+ protected boolean sendAccessToken;
- public ConstraintAuthorizationHandler(String errorPage, HttpHandler next) {
- this.errorPage = errorPage;
+ public ConstraintAuthorizationHandler(HttpHandler next, String errorPage, boolean sendAccessToken) {
this.next = next;
+ this.errorPage = errorPage;
+ this.sendAccessToken = sendAccessToken;
}
@Override
@@ -57,7 +60,8 @@ public class ConstraintAuthorizationHandler implements HttpHandler {
public void authenticatedRequest(KeycloakUndertowAccount account, HttpServerExchange exchange) throws Exception {
if (account != null) {
- IDToken idToken = account.getKeycloakSecurityContext().getIdToken();
+ IDToken idToken = account.getKeycloakSecurityContext().getToken();
+ if (idToken == null) return;
if (idToken.getSubject() != null) {
exchange.getRequestHeaders().put(KEYCLOAK_SUBJECT, idToken.getSubject());
}
@@ -70,6 +74,9 @@ public class ConstraintAuthorizationHandler implements HttpHandler {
if (idToken.getName() != null) {
exchange.getRequestHeaders().put(KEYCLOAK_NAME, idToken.getName());
}
+ if (sendAccessToken) {
+ exchange.getRequestHeaders().put(KEYCLOAK_ACCESS_TOKEN, account.getKeycloakSecurityContext().getTokenString());
+ }
}
next.handleRequest(exchange);
}
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
index 2fd260b..998302d 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
@@ -29,7 +29,7 @@ public class ConstraintMatcherHandler implements HttpHandler {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
log.debugv("ConstraintMatcherHandler: {0}", exchange.getRelativePath());
- SingleConstraintMatch match = matcher.getSecurityInfo(exchange.getRelativePath(), exchange.getRequestMethod().toString()).getMergedConstraint();
+ SingleConstraintMatch match = matcher.getSecurityInfo(exchange.getRelativePath(), exchange.getRequestMethod().toString());
if (match == null || (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.PERMIT)) {
unsecuredHandler.handleRequest(exchange);
return;
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyAuthenticationCallHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyAuthenticationCallHandler.java
new file mode 100755
index 0000000..7246693
--- /dev/null
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyAuthenticationCallHandler.java
@@ -0,0 +1,35 @@
+package org.keycloak.proxy;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ProxyAuthenticationCallHandler implements HttpHandler {
+
+ private final HttpHandler next;
+
+ public ProxyAuthenticationCallHandler(final HttpHandler next) {
+ this.next = next;
+ }
+
+ /**
+ * Only allow the request through if successfully authenticated or if authentication is not required.
+ *
+ * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange)
+ */
+ @Override
+ public void handleRequest(final HttpServerExchange exchange) throws Exception {
+ SecurityContext context = exchange.getSecurityContext();
+ if (context.authenticate()) {
+ if(!exchange.isComplete()) {
+ next.handleRequest(exchange);
+ }
+ } else {
+ exchange.endExchange();
+ }
+ }
+}
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java
index 66b7f52..dd41a05 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java
@@ -37,6 +37,8 @@ public class ProxyConfig {
protected Boolean directBuffers;
@JsonProperty("target-url")
protected String targetUrl;
+ @JsonProperty("send-access-token")
+ protected boolean sendAccessToken;
@JsonProperty("applications")
protected List<Application> applications = new LinkedList<Application>();
@@ -144,6 +146,14 @@ public class ProxyConfig {
this.applications = applications;
}
+ public boolean isSendAccessToken() {
+ return sendAccessToken;
+ }
+
+ public void setSendAccessToken(boolean sendAccessToken) {
+ this.sendAccessToken = sendAccessToken;
+ }
+
public static class Application {
@JsonProperty("base-path")
protected String basePath;
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
index 379656b..8835856 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
@@ -76,6 +76,7 @@ public class ProxyServerBuilder {
protected PathHandler root = new PathHandler(NOT_FOUND);
protected HttpHandler proxyHandler;
+ protected boolean sendAccessToken;
public ProxyServerBuilder target(String uri) {
SimpleProxyClientProvider provider = null;
@@ -95,6 +96,10 @@ public class ProxyServerBuilder {
return this;
}
+ public ProxyServerBuilder sendAccessToken(boolean flag) {
+ this.sendAccessToken = flag;
+ return this;
+ }
public ApplicationBuilder application(AdapterConfig config) {
return new ApplicationBuilder(config);
}
@@ -219,8 +224,8 @@ public class ProxyServerBuilder {
errorPage = base + "/" + errorPage;
}
}
- handler = new ConstraintAuthorizationHandler(errorPage, handler);
- handler = new AuthenticationCallHandler(handler);
+ handler = new ConstraintAuthorizationHandler(handler, errorPage, sendAccessToken);
+ handler = new ProxyAuthenticationCallHandler(handler);
handler = new ConstraintMatcherHandler(matches, handler, toWrap, errorPage);
final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>();
mechanisms.add(new CachedAuthenticatedSessionMechanism());
@@ -379,6 +384,7 @@ public class ProxyServerBuilder {
}
public static void initOptions(ProxyConfig config, ProxyServerBuilder builder) {
+ builder.sendAccessToken(config.isSendAccessToken());
if (config.getBufferSize() != null) builder.setBufferSize(config.getBufferSize());
if (config.getBuffersPerRegion() != null) builder.setBuffersPerRegion(config.getBuffersPerRegion());
if (config.getIoThreads() != null) builder.setIoThreads(config.getIoThreads());
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java
index c5c39a0..d894829 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java
@@ -58,19 +58,19 @@ public class SecurityPathMatches {
extensionRoleInformation.isEmpty();
}
- public SecurityPathMatch getSecurityInfo(final String path, final String method) {
+ public SingleConstraintMatch getSecurityInfo(final String path, final String method) {
RuntimeMatch currentMatch = new RuntimeMatch();
handleMatch(method, defaultPathSecurityInformation, currentMatch);
PathSecurityInformation match = exactPathRoleInformation.get(path);
if (match != null) {
handleMatch(method, match, currentMatch);
- return new SecurityPathMatch(mergeConstraints(currentMatch));
+ return mergeConstraints(currentMatch);
}
match = prefixPathRoleInformation.get(path);
if (match != null) {
handleMatch(method, match, currentMatch);
- return new SecurityPathMatch(mergeConstraints(currentMatch));
+ return mergeConstraints(currentMatch);
}
int qsPos = -1;
@@ -83,7 +83,7 @@ public class SecurityPathMatches {
match = exactPathRoleInformation.get(part);
if (match != null) {
handleMatch(method, match, currentMatch);
- return new SecurityPathMatch(mergeConstraints(currentMatch));
+ return mergeConstraints(currentMatch);
}
qsPos = i;
extension = false;
@@ -93,7 +93,7 @@ public class SecurityPathMatches {
match = prefixPathRoleInformation.get(part);
if (match != null) {
handleMatch(method, match, currentMatch);
- return new SecurityPathMatch(mergeConstraints(currentMatch));
+ return mergeConstraints(currentMatch);
}
} else if (c == '.') {
if (!extension) {
@@ -107,12 +107,12 @@ public class SecurityPathMatches {
match = extensionRoleInformation.get(ext);
if (match != null) {
handleMatch(method, match, currentMatch);
- return new SecurityPathMatch(mergeConstraints(currentMatch));
+ return mergeConstraints(currentMatch);
}
}
}
}
- return new SecurityPathMatch(mergeConstraints(currentMatch));
+ return mergeConstraints(currentMatch);
}
/**
diff --git a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
index b025ba8..c6e3698 100755
--- a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
+++ b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
@@ -62,6 +62,11 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.io.File;
import java.io.IOException;
@@ -90,6 +95,7 @@ public class ProxyTest {
public static class SendUsernameServlet extends HttpServlet {
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+ String requestURI = req.getRequestURI();
resp.setContentType("text/plain");
OutputStream stream = resp.getOutputStream();
stream.write(req.getRequestURL().toString().getBytes());
@@ -104,6 +110,34 @@ public class ProxyTest {
String name = headers.nextElement();
System.out.println(name +": " + req.getHeader(name));
}
+
+ if (requestURI.contains("/bearer")) {
+ Client client = ClientBuilder.newClient();
+
+ try {
+ String appBase = "http://localhost:8080/customer-portal";
+ WebTarget target = client.target(appBase + "/call-bearer");
+
+ Response response = null;
+ response = target.request()
+ .header(HttpHeaders.AUTHORIZATION, "Bearer CRAP")
+ .get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+ response = target.request()
+ .header(HttpHeaders.AUTHORIZATION, "Bearer " + req.getHeader("KEYCLOAK_ACCESS_TOKEN"))
+ .get();
+ Assert.assertEquals(200, response.getStatus());
+ String data = response.readEntity(String.class);
+ response.close();
+ stream.write(data.getBytes());
+ } finally {
+ client.close();
+ }
+
+ } else if (requestURI.contains("/call-bearer")) {
+ stream.write("bearer called".getBytes());
+ }
}
@Override
protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
@@ -151,20 +185,6 @@ public class ProxyTest {
public static void initProxy() throws Exception {
initTomcat();
InputStream is = ProxyTest.class.getResourceAsStream("/proxy-config.json");
- /*
- ProxyServerBuilder builder = new ProxyServerBuilder().addHttpListener(8080, "localhost");
- AdapterConfig config = KeycloakDeploymentBuilder.loadAdapterConfig(is);
-
- builder.target("http://localhost:8082")
- .application(config)
- .base("/customer-portal")
- .errorPage("/error.html")
- .constraint("/users/*").role("user").add()
- .constraint("/admins/*").role("admin").add()
- .constraint("/users/permit").permit().add()
- .constraint("/users/deny").deny().add()
- .add();
- */
proxyServer = ProxyServerBuilder.build(is);
proxyServer.start();
@@ -221,6 +241,11 @@ public class ProxyTest {
Assert.assertTrue(pageSource.contains("customer-portal/users"));
Assert.assertTrue(pageSource.contains("count:1")); // test http session
+ driver.navigate().to(baseUrl + "/customer-portal/bearer");
+ pageSource = driver.getPageSource();
+ Assert.assertTrue(pageSource.contains("bearer called"));
+
+
driver.navigate().to(baseUrl + "/customer-portal/users/deny");
Assert.assertEquals(driver.getCurrentUrl(), baseUrl + "/customer-portal/users/deny");
pageSource = driver.getPageSource();
diff --git a/testsuite/proxy/src/test/resources/proxy-config.json b/testsuite/proxy/src/test/resources/proxy-config.json
index c6f583b..06c9eda 100755
--- a/testsuite/proxy/src/test/resources/proxy-config.json
+++ b/testsuite/proxy/src/test/resources/proxy-config.json
@@ -6,6 +6,7 @@
"keystore-password": "password",
"key-password": "password",
"target-url": "http://localhost:8082",
+ "send-access-token": true,
"applications": [
{
"base-path": "/customer-portal",
@@ -30,6 +31,18 @@
]
},
{
+ "pattern": "/call-bearer/*",
+ "roles-allowed": [
+ "user"
+ ]
+ },
+ {
+ "pattern": "/bearer/*",
+ "roles-allowed": [
+ "user"
+ ]
+ },
+ {
"pattern": "/admins/*",
"roles-allowed": [
"admin"