From b2df872ad4b528a244be917949951eecfa1d0c30 Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Fri, 18 May 2018 15:14:04 +0200 Subject: [PATCH] KEYCLOAK-7278 KEYCLOAK-7280 CXF/Undertow integration --- adapters/oidc/fuse7/undertow/pom.xml | 3 +- .../osgi/undertow/CxfKeycloakAuthHandler.java | 174 ++++++++++++++++++ 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 adapters/oidc/fuse7/undertow/src/main/java/org/keycloak/adapters/osgi/undertow/CxfKeycloakAuthHandler.java diff --git a/adapters/oidc/fuse7/undertow/pom.xml b/adapters/oidc/fuse7/undertow/pom.xml index 3f00a8d3b7..8dba93774e 100644 --- a/adapters/oidc/fuse7/undertow/pom.xml +++ b/adapters/oidc/fuse7/undertow/pom.xml @@ -41,6 +41,7 @@ org.ops4j.pax.web.*;version="[3.0,8)", javax.servlet.*;version="[2.5,4)";resolution:=optional, org.apache.cxf.transport.http;resolution:=optional;version="[3,4)", + org.apache.cxf.transport.http_undertow;resolution:=optional;version="[3,4)", org.apache.cxf.transport.servlet;resolution:=optional;version="[3,4)", io.undertow.*, *;resolution:=optional @@ -88,7 +89,7 @@ org.apache.cxf cxf-rt-transports-http-undertow - 3.1.11.fuse-000199-redhat-1 + ${cxf.version} provided diff --git a/adapters/oidc/fuse7/undertow/src/main/java/org/keycloak/adapters/osgi/undertow/CxfKeycloakAuthHandler.java b/adapters/oidc/fuse7/undertow/src/main/java/org/keycloak/adapters/osgi/undertow/CxfKeycloakAuthHandler.java new file mode 100644 index 0000000000..2c6fab765d --- /dev/null +++ b/adapters/oidc/fuse7/undertow/src/main/java/org/keycloak/adapters/osgi/undertow/CxfKeycloakAuthHandler.java @@ -0,0 +1,174 @@ +/* + * Copyright 2018 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.osgi.undertow; + +import org.keycloak.adapters.AdapterDeploymentContext; +import org.keycloak.adapters.KeycloakConfigResolver; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.KeycloakDeploymentBuilder; +import org.keycloak.adapters.NodesRegistrationManagement; +import org.keycloak.adapters.spi.InMemorySessionIdMapper; +import org.keycloak.adapters.spi.SessionIdMapper; +import org.keycloak.adapters.undertow.UndertowAuthenticationMechanism; +import org.keycloak.adapters.undertow.UndertowUserSessionManagement; +import org.keycloak.representations.adapters.config.AdapterConfig; +import io.undertow.security.api.AuthenticationMechanism; +import io.undertow.security.api.AuthenticationMode; +import io.undertow.security.handlers.AuthenticationCallHandler; +import io.undertow.security.handlers.AuthenticationConstraintHandler; +import io.undertow.security.handlers.AuthenticationMechanismsHandler; +import io.undertow.security.handlers.SecurityInitialHandler; +import io.undertow.security.idm.Account; +import io.undertow.security.idm.Credential; +import io.undertow.security.idm.IdentityManager; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import org.apache.cxf.transport.http_undertow.CXFUndertowHttpHandler; + +/** + * + * @author hmlnarik + */ +public class CxfKeycloakAuthHandler implements CXFUndertowHttpHandler { + + private static final Logger LOG = Logger.getLogger(CxfKeycloakAuthHandler.class.getName()); + + private static final IdentityManager IDENTITY_MANAGER = new IdentityManager() { + @Override + public Account verify(Account account) { + return account; + } + + @Override + public Account verify(String id, Credential credential) { + throw new IllegalStateException("Should never be called in Keycloak flow"); + } + + @Override + public Account verify(Credential credential) { + throw new IllegalStateException("Should never be called in Keycloak flow"); + } + }; + + private final UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement(); + + protected final NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement(); + + protected final SessionIdMapper idMapper = new InMemorySessionIdMapper(); + + private final AtomicReference securityHandler = new AtomicReference<>(); + + private Pattern skipPattern; + + private int confidentialPort = 8443; + + private HttpHandler next; + + private KeycloakConfigResolver configResolver; + + private AdapterConfig adapterConfig; + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + if (shouldSkip(exchange.getRequestPath())) { + next.handleRequest(exchange); + } else { + getSecurityHandler().handleRequest(exchange); + } + } + + private HttpHandler getSecurityHandler() { + if (this.securityHandler.get() == null) { + HttpHandler handler = this.next; + + handler = new AuthenticationCallHandler(handler); + handler = new AuthenticationConstraintHandler(handler); + + AdapterDeploymentContext deploymentContext = buildDeploymentContext(); + + final List mechanisms + = Collections.singletonList( + new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, confidentialPort, null)); + handler = new AuthenticationMechanismsHandler(handler, mechanisms); + + this.securityHandler.compareAndSet(null, new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, IDENTITY_MANAGER, "KEYCLOAK", handler)); + } + + return this.securityHandler.get(); + } + + private AdapterDeploymentContext buildDeploymentContext() { + if (configResolver != null) { + LOG.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolver.getClass()); + return new AdapterDeploymentContext(configResolver); + } else if (adapterConfig != null) { + KeycloakDeployment kd = KeycloakDeploymentBuilder.build(adapterConfig); + return new AdapterDeploymentContext(kd); + } + + LOG.warning("Adapter is unconfigured, Keycloak will deny every request"); + return new AdapterDeploymentContext(); + } + + @Override + public void setNext(HttpHandler nextHandler) { + this.next = nextHandler; + } + + private boolean shouldSkip(String requestPath) { + return skipPattern != null && skipPattern.matcher(requestPath).matches(); + } + + public KeycloakConfigResolver getConfigResolver() { + return configResolver; + } + + public void setConfigResolver(KeycloakConfigResolver configResolver) { + this.configResolver = configResolver; + } + + public int getConfidentialPort() { + return confidentialPort; + } + + public void setConfidentialPort(int confidentialPort) { + this.confidentialPort = confidentialPort; + } + + public AdapterConfig getAdapterConfig() { + return adapterConfig; + } + + public void setAdapterConfig(AdapterConfig adapterConfig) { + this.adapterConfig = adapterConfig; + } + + public String getSkipPattern() { + return skipPattern.pattern(); + } + + public void setSkipPattern(String skipPattern) { + this.skipPattern = Pattern.compile(skipPattern, Pattern.DOTALL); + } + +}