keycloak-uncached
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java 1(+1 -0)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index b9ee4c6..8664800 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -57,6 +57,7 @@ public class KeycloakDeployment {
protected String resourceName;
protected boolean bearerOnly;
+ protected boolean autodetectBearerOnly;
protected boolean enableBasicAuth;
protected boolean publicClient;
protected Map<String, Object> resourceCredentials = new HashMap<>();
@@ -201,6 +202,14 @@ public class KeycloakDeployment {
this.bearerOnly = bearerOnly;
}
+ public boolean isAutodetectBearerOnly() {
+ return autodetectBearerOnly;
+ }
+
+ public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+ this.autodetectBearerOnly = autodetectBearerOnly;
+ }
+
public boolean isEnableBasicAuth() {
return enableBasicAuth;
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 85b19ca..65e9456 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -99,6 +99,7 @@ public class KeycloakDeploymentBuilder {
}
deployment.setBearerOnly(adapterConfig.isBearerOnly());
+ deployment.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
index c04f21c..0cbe687 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
@@ -17,6 +17,8 @@
package org.keycloak.adapters;
+import java.util.Collections;
+import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.spi.AuthChallenge;
@@ -116,6 +118,12 @@ public abstract class RequestAuthenticator {
return AuthOutcome.NOT_ATTEMPTED;
}
+ if (isAutodetectedBearerOnly(facade.getRequest())) {
+ challenge = bearer.getChallenge();
+ log.debug("NOT_ATTEMPTED: Treating as bearer only");
+ return AuthOutcome.NOT_ATTEMPTED;
+ }
+
if (log.isTraceEnabled()) {
log.trace("try oauth");
}
@@ -158,6 +166,36 @@ public abstract class RequestAuthenticator {
return false;
}
+ protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
+ if (!deployment.isAutodetectBearerOnly()) return false;
+
+ String headerValue = facade.getRequest().getHeader("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;
+ }
+
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index e4065bc..0a107bb 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -30,7 +30,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
"resource", "public-client", "credentials",
"use-resource-role-mappings",
"enable-cors", "cors-max-age", "cors-allowed-methods",
- "expose-token", "bearer-only",
+ "expose-token", "bearer-only", "autodetect-bearer-only",
"connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password",
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
index f9138a1..3cef2a0 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
@@ -33,7 +33,7 @@ import java.util.Map;
"resource", "public-client", "credentials",
"use-resource-role-mappings",
"enable-cors", "cors-max-age", "cors-allowed-methods",
- "expose-token", "bearer-only", "enable-basic-auth"})
+ "expose-token", "bearer-only", "autodetect-bearer-only", "enable-basic-auth"})
public class BaseAdapterConfig extends BaseRealmConfig {
@JsonProperty("resource")
protected String resource;
@@ -51,6 +51,8 @@ public class BaseAdapterConfig extends BaseRealmConfig {
protected boolean exposeToken;
@JsonProperty("bearer-only")
protected boolean bearerOnly;
+ @JsonProperty("autodetect-bearer-only")
+ protected boolean autodetectBearerOnly;
@JsonProperty("enable-basic-auth")
protected boolean enableBasicAuth;
@JsonProperty("public-client")
@@ -123,6 +125,14 @@ public class BaseAdapterConfig extends BaseRealmConfig {
this.bearerOnly = bearerOnly;
}
+ public boolean isAutodetectBearerOnly() {
+ return autodetectBearerOnly;
+ }
+
+ public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+ this.autodetectBearerOnly = autodetectBearerOnly;
+ }
+
public boolean isEnableBasicAuth() {
return enableBasicAuth;
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 404d24d..1f2f620 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -72,6 +72,12 @@ public class AdapterTest {
.name("product-portal").contextPath("/product-portal")
.servletClass(ProductServlet.class).adapterConfigPath(url.getPath())
.role("user").deployApplication();
+
+ url = getClass().getResource("/adapter-test/product-autodetect-bearer-only-keycloak.json");
+ createApplicationDeployment()
+ .name("product-portal-autodetect-bearer-only").contextPath("/product-portal-autodetect-bearer-only")
+ .servletClass(ProductServlet.class).adapterConfigPath(url.getPath())
+ .role("user").deployApplication();
// Test that replacing system properties works for adapters
System.setProperty("app.server.base.url", "http://localhost:8081");
@@ -150,6 +156,11 @@ public class AdapterTest {
}
@Test
+ public void testAutodetectBearerOnly() throws Exception {
+ testStrategy.testAutodetectBearerOnly();
+ }
+
+ @Test
public void testBasicAuthErrorHandling() throws Exception {
testStrategy.testBasicAuthErrorHandling();
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
index 9341df2..bd0a144 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
@@ -400,6 +400,55 @@ public class AdapterTestStrategy extends ExternalResource {
Time.setOffset(0);
}
+ public void testAutodetectBearerOnly() throws Exception {
+ Client client = ClientBuilder.newClient();
+
+ // Do not redirect client to login page if it's an XHR
+ WebTarget target = client.target(APP_SERVER_BASE_URL + "/product-portal-autodetect-bearer-only");
+ Response response = target.request().header("X-Requested-With", "XMLHttpRequest").get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Do not redirect client to login page if it's a partial Faces request
+ response = target.request().header("Faces-Request", "partial/ajax").get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Do not redirect client to login page if it's a SOAP request
+ response = target.request().header("SOAPAction", "").get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Do not redirect client to login page if Accept header is missing
+ response = target.request().get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Do not redirect client to login page if client does not understand HTML reponses
+ response = target.request().header(HttpHeaders.ACCEPT, "application/json,text/xml").get();
+ Assert.assertEquals(401, response.getStatus());
+ response.close();
+
+ // Redirect client to login page if it's not an XHR
+ response = target.request().header("X-Requested-With", "Dont-Know").header(HttpHeaders.ACCEPT, "*/*").get();
+ Assert.assertEquals(302, response.getStatus());
+ Assert.assertTrue(response.getHeaderString(HttpHeaders.LOCATION).contains("response_type=code"));
+ response.close();
+
+ // Redirect client to login page if client explicitely understands HTML responses
+ response = target.request().header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9").get();
+ Assert.assertEquals(302, response.getStatus());
+ Assert.assertTrue(response.getHeaderString(HttpHeaders.LOCATION).contains("response_type=code"));
+ response.close();
+
+ // Redirect client to login page if client understands all response types
+ response = target.request().header(HttpHeaders.ACCEPT, "*/*").get();
+ Assert.assertEquals(302, response.getStatus());
+ Assert.assertTrue(response.getHeaderString(HttpHeaders.LOCATION).contains("response_type=code"));
+ response.close();
+ client.close();
+ }
+
/**
* KEYCLOAK-518
* @throws Exception
diff --git a/testsuite/integration/src/test/resources/adapter-test/product-autodetect-bearer-only-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/product-autodetect-bearer-only-keycloak.json
new file mode 100644
index 0000000..b92abc6
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/product-autodetect-bearer-only-keycloak.json
@@ -0,0 +1,11 @@
+{
+ "realm" : "demo",
+ "resource" : "product-portal",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url" : "http://localhost:8081/auth",
+ "ssl-required" : "external",
+ "credentials" : {
+ "secret": "password"
+ },
+ "autodetect-bearer-only" : true
+}