Remove jaxrs-oauth-client
and OIDC servlet-filter
adapters
Closes #28784 Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
aeed5eee17
commit
a6e2ab5523
24 changed files with 0 additions and 2327 deletions
|
@ -1,191 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>999.0.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-jakarta-servlet-filter-adapter</artifactId>
|
|
||||||
<name>Keycloak Servlet Filter Adapter Integration</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
NOTE: This maven module is generated using original: servlet-filter
|
|
||||||
@see: ${jakarta-transformer-sources}
|
|
||||||
Reason is the transition to Jakarta APIs.
|
|
||||||
-->
|
|
||||||
<properties>
|
|
||||||
<keycloak.osgi.export>
|
|
||||||
org.keycloak.adapters.servlet.*
|
|
||||||
</keycloak.osgi.export>
|
|
||||||
<keycloak.osgi.import>
|
|
||||||
jakarta.servlet.*;version="[3.1,5)";resolution:=optional,
|
|
||||||
org.keycloak.*;version="${project.version}",
|
|
||||||
*;resolution:=optional
|
|
||||||
</keycloak.osgi.import>
|
|
||||||
|
|
||||||
<jakarta-transformer-sources>${project.basedir}/../servlet-filter/src</jakarta-transformer-sources>
|
|
||||||
<jakarta-transformer-target>${project.basedir}/src</jakarta-transformer-target>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.logging</groupId>
|
|
||||||
<artifactId>jboss-logging</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-jakarta-servlet-adapter-spi</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcprov-jdk18on</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-annotations</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jakarta.servlet</groupId>
|
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<!-- Jakarta Transformer Plugin -->
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-antrun-plugin</artifactId>
|
|
||||||
<version>3.0.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>transform</id>
|
|
||||||
<phase>initialize</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>run</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<target>
|
|
||||||
<property name="plugin_classpath" refid="maven.plugin.classpath" />
|
|
||||||
<java classname="org.eclipse.transformer.jakarta.JakartaTransformer">
|
|
||||||
<arg value="-o" />
|
|
||||||
<arg value="${jakarta-transformer-sources}" />
|
|
||||||
<arg value="${jakarta-transformer-target}" />
|
|
||||||
<classpath>
|
|
||||||
<pathelement path="${plugin_classpath}" />
|
|
||||||
</classpath>
|
|
||||||
</java>
|
|
||||||
</target>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.transformer</groupId>
|
|
||||||
<artifactId>org.eclipse.transformer.cli</artifactId>
|
|
||||||
<version>0.2.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ant-contrib</groupId>
|
|
||||||
<artifactId>ant-contrib</artifactId>
|
|
||||||
<version>1.0b3</version>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>ant</groupId>
|
|
||||||
<artifactId>ant</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</plugin>
|
|
||||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<archive>
|
|
||||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
|
||||||
</archive>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.felix</groupId>
|
|
||||||
<artifactId>maven-bundle-plugin</artifactId>
|
|
||||||
<extensions>true</extensions>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>bundle-manifest</id>
|
|
||||||
<phase>process-classes</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>manifest</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
<configuration>
|
|
||||||
<instructions>
|
|
||||||
<Bundle-ClassPath>.</Bundle-ClassPath>
|
|
||||||
<Bundle-Name>${project.name}</Bundle-Name>
|
|
||||||
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
|
||||||
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
|
||||||
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
|
||||||
</instructions>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,96 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>999.0.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
|
|
||||||
<name>Keycloak JAX-RS OAuth Client</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.ws.rs</groupId>
|
|
||||||
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-jackson2-provider</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.annotation</groupId>
|
|
||||||
<artifactId>javax.annotation-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.osgi</groupId>
|
|
||||||
<artifactId>osgi.core</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.jaxrs;
|
|
||||||
|
|
||||||
import javax.annotation.Priority;
|
|
||||||
import javax.ws.rs.Priorities;
|
|
||||||
import javax.ws.rs.container.ContainerRequestFilter;
|
|
||||||
import javax.ws.rs.container.PreMatching;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
* @deprecated Class is deprecated and may be removed in the future. If you want to maintain this class for Keycloak community, please
|
|
||||||
* contact Keycloak team on keycloak-dev mailing list. You can fork it into your github repository and
|
|
||||||
* Keycloak team will reference it from "Keycloak Extensions" page.
|
|
||||||
*/
|
|
||||||
@PreMatching
|
|
||||||
@Priority(Priorities.AUTHENTICATION)
|
|
||||||
@Deprecated
|
|
||||||
public interface JaxrsBearerTokenFilter extends ContainerRequestFilter {
|
|
||||||
}
|
|
|
@ -1,317 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.jaxrs;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
|
||||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
|
||||||
import org.keycloak.adapters.BasicAuthRequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
|
||||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
|
||||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
|
||||||
import org.keycloak.common.constants.GenericConstants;
|
|
||||||
|
|
||||||
import javax.annotation.Priority;
|
|
||||||
import javax.ws.rs.Priorities;
|
|
||||||
import javax.ws.rs.container.ContainerRequestContext;
|
|
||||||
import javax.ws.rs.container.PreMatching;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.SecurityContext;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
* @deprecated Class is deprecated and may be removed in the future. If you want to maintain this class for Keycloak community, please
|
|
||||||
* contact Keycloak team on keycloak-dev mailing list. You can fork it into your github repository and
|
|
||||||
* Keycloak team will reference it from "Keycloak Extensions" page.
|
|
||||||
*/
|
|
||||||
@PreMatching
|
|
||||||
@Priority(Priorities.AUTHENTICATION)
|
|
||||||
@Deprecated
|
|
||||||
public class JaxrsBearerTokenFilterImpl implements JaxrsBearerTokenFilter {
|
|
||||||
|
|
||||||
private final static Logger log = Logger.getLogger("" + JaxrsBearerTokenFilterImpl.class);
|
|
||||||
|
|
||||||
private String keycloakConfigFile;
|
|
||||||
private String keycloakConfigResolverClass;
|
|
||||||
protected volatile boolean started;
|
|
||||||
|
|
||||||
protected AdapterDeploymentContext deploymentContext;
|
|
||||||
|
|
||||||
// TODO: Should also somehow handle stop lifecycle for de-registration
|
|
||||||
protected NodesRegistrationManagement nodesRegistrationManagement;
|
|
||||||
protected UserSessionManagement userSessionManagement = new EmptyUserSessionManagement();
|
|
||||||
|
|
||||||
public void setKeycloakConfigFile(String configFile) {
|
|
||||||
this.keycloakConfigFile = configFile;
|
|
||||||
attemptStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeycloakConfigFile() {
|
|
||||||
return this.keycloakConfigFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeycloakConfigResolverClass() {
|
|
||||||
return keycloakConfigResolverClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKeycloakConfigResolverClass(String keycloakConfigResolverClass) {
|
|
||||||
this.keycloakConfigResolverClass = keycloakConfigResolverClass;
|
|
||||||
attemptStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// INITIALIZATION AND STARTUP
|
|
||||||
|
|
||||||
protected void attemptStart() {
|
|
||||||
if (started) {
|
|
||||||
throw new IllegalStateException("Filter already started. Make sure to specify just keycloakConfigResolver or keycloakConfigFile but not both");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInitialized()) {
|
|
||||||
start();
|
|
||||||
} else {
|
|
||||||
log.fine("Not yet initialized");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isInitialized() {
|
|
||||||
return this.keycloakConfigFile != null || this.keycloakConfigResolverClass != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void start() {
|
|
||||||
if (started) {
|
|
||||||
throw new IllegalStateException("Filter already started. Make sure to specify just keycloakConfigResolver or keycloakConfigFile but not both");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keycloakConfigResolverClass != null) {
|
|
||||||
Class<? extends KeycloakConfigResolver> resolverClass = loadResolverClass();
|
|
||||||
|
|
||||||
try {
|
|
||||||
KeycloakConfigResolver resolver = resolverClass.newInstance();
|
|
||||||
log.info("Using " + resolver + " to resolve Keycloak configuration on a per-request basis.");
|
|
||||||
this.deploymentContext = new AdapterDeploymentContext(resolver);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Unable to instantiate resolver " + resolverClass);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (keycloakConfigFile == null) {
|
|
||||||
throw new IllegalArgumentException("You need to specify either keycloakConfigResolverClass or keycloakConfigFile in configuration");
|
|
||||||
}
|
|
||||||
InputStream is = loadKeycloakConfigFile();
|
|
||||||
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
|
|
||||||
deploymentContext = new AdapterDeploymentContext(kd);
|
|
||||||
log.info("Keycloak is using a per-deployment configuration loaded from: " + keycloakConfigFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
nodesRegistrationManagement = new NodesRegistrationManagement();
|
|
||||||
started = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use 'Reflections.classForName'
|
|
||||||
protected Class<? extends KeycloakConfigResolver> loadResolverClass() {
|
|
||||||
try {
|
|
||||||
return (Class<? extends KeycloakConfigResolver>)getClass().getClassLoader().loadClass(keycloakConfigResolverClass);
|
|
||||||
} catch (ClassNotFoundException cnfe) {
|
|
||||||
// Fallback to tccl
|
|
||||||
try {
|
|
||||||
return (Class<? extends KeycloakConfigResolver>)Thread.currentThread().getContextClassLoader().loadClass(keycloakConfigResolverClass);
|
|
||||||
} catch (ClassNotFoundException cnfe2) {
|
|
||||||
throw new RuntimeException("Unable to find resolver class: " + keycloakConfigResolverClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InputStream loadKeycloakConfigFile() {
|
|
||||||
if (keycloakConfigFile.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
|
|
||||||
String classPathLocation = keycloakConfigFile.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
|
|
||||||
log.fine("Loading config from classpath on location: " + classPathLocation);
|
|
||||||
// Try current class classloader first
|
|
||||||
InputStream is = getClass().getClassLoader().getResourceAsStream(classPathLocation);
|
|
||||||
if (is == null) {
|
|
||||||
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is != null) {
|
|
||||||
return is;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unable to find config from classpath: " + keycloakConfigFile);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fallback to file
|
|
||||||
try {
|
|
||||||
log.fine("Loading config from file: " + keycloakConfigFile);
|
|
||||||
return new FileInputStream(keycloakConfigFile);
|
|
||||||
} catch (FileNotFoundException fnfe) {
|
|
||||||
log.severe("Config not found on " + keycloakConfigFile);
|
|
||||||
throw new RuntimeException(fnfe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// REQUEST HANDLING
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void filter(ContainerRequestContext request) throws IOException {
|
|
||||||
SecurityContext securityContext = getRequestSecurityContext(request);
|
|
||||||
JaxrsHttpFacade facade = new JaxrsHttpFacade(request, securityContext);
|
|
||||||
if (handlePreauth(facade)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeycloakDeployment resolvedDeployment = deploymentContext.resolveDeployment(facade);
|
|
||||||
|
|
||||||
nodesRegistrationManagement.tryRegister(resolvedDeployment);
|
|
||||||
|
|
||||||
bearerAuthentication(facade, request, resolvedDeployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean handlePreauth(JaxrsHttpFacade facade) {
|
|
||||||
PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deploymentContext, facade);
|
|
||||||
if (handler.handleRequest()) {
|
|
||||||
// Send response now (if not already sent)
|
|
||||||
if (!facade.isResponseFinished()) {
|
|
||||||
facade.getResponse().end();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void bearerAuthentication(JaxrsHttpFacade facade, ContainerRequestContext request, KeycloakDeployment resolvedDeployment) {
|
|
||||||
BearerTokenRequestAuthenticator authenticator = new BearerTokenRequestAuthenticator(resolvedDeployment);
|
|
||||||
AuthOutcome outcome = authenticator.authenticate(facade);
|
|
||||||
|
|
||||||
if (outcome == AuthOutcome.NOT_ATTEMPTED && resolvedDeployment.isEnableBasicAuth()) {
|
|
||||||
authenticator = new BasicAuthRequestAuthenticator(resolvedDeployment);
|
|
||||||
outcome = authenticator.authenticate(facade);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outcome == AuthOutcome.FAILED || outcome == AuthOutcome.NOT_ATTEMPTED) {
|
|
||||||
AuthChallenge challenge = authenticator.getChallenge();
|
|
||||||
log.fine("Authentication outcome: " + outcome);
|
|
||||||
boolean challengeSent = challenge.challenge(facade);
|
|
||||||
if (!challengeSent) {
|
|
||||||
// Use some default status code
|
|
||||||
facade.getResponse().setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send response now (if not already sent)
|
|
||||||
if (!facade.isResponseFinished()) {
|
|
||||||
facade.getResponse().end();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (verifySslFailed(facade, resolvedDeployment)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
propagateSecurityContext(facade, request, resolvedDeployment, authenticator);
|
|
||||||
handleAuthActions(facade, resolvedDeployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void propagateSecurityContext(JaxrsHttpFacade facade, ContainerRequestContext request, KeycloakDeployment resolvedDeployment, BearerTokenRequestAuthenticator bearer) {
|
|
||||||
RefreshableKeycloakSecurityContext skSession = new RefreshableKeycloakSecurityContext(resolvedDeployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
|
|
||||||
|
|
||||||
// Not needed to do resteasy specifics as KeycloakSecurityContext can be always retrieved from SecurityContext by typecast SecurityContext.getUserPrincipal to KeycloakPrincipal
|
|
||||||
// ResteasyProviderFactory.pushContext(KeycloakSecurityContext.class, skSession);
|
|
||||||
|
|
||||||
facade.setSecurityContext(skSession);
|
|
||||||
String principalName = AdapterUtils.getPrincipalName(resolvedDeployment, bearer.getToken());
|
|
||||||
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(principalName, skSession);
|
|
||||||
SecurityContext anonymousSecurityContext = getRequestSecurityContext(request);
|
|
||||||
final boolean isSecure = anonymousSecurityContext.isSecure();
|
|
||||||
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(skSession);
|
|
||||||
|
|
||||||
SecurityContext ctx = new SecurityContext() {
|
|
||||||
@Override
|
|
||||||
public Principal getUserPrincipal() {
|
|
||||||
return principal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isUserInRole(String role) {
|
|
||||||
return roles.contains(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSecure() {
|
|
||||||
return isSecure;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthenticationScheme() {
|
|
||||||
return "OAUTH_BEARER";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.setSecurityContext(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean verifySslFailed(JaxrsHttpFacade facade, KeycloakDeployment deployment) {
|
|
||||||
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
|
|
||||||
log.warning("SSL is required to authenticate, but request is not secured");
|
|
||||||
facade.getResponse().sendError(403, "SSL required!");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SecurityContext getRequestSecurityContext(ContainerRequestContext request) {
|
|
||||||
return request.getSecurityContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleAuthActions(JaxrsHttpFacade facade, KeycloakDeployment deployment) {
|
|
||||||
AuthenticatedActionsHandler authActionsHandler = new AuthenticatedActionsHandler(deployment, facade);
|
|
||||||
if (authActionsHandler.handledRequest()) {
|
|
||||||
// Send response now (if not already sent)
|
|
||||||
if (!facade.isResponseFinished()) {
|
|
||||||
facade.getResponse().end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't have any sessions to manage with pure jaxrs filter
|
|
||||||
private static class EmptyUserSessionManagement implements UserSessionManagement {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logoutAll() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logoutHttpSessions(List<String> ids) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,240 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.jaxrs;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.AuthenticationError;
|
|
||||||
import org.keycloak.adapters.spi.LogoutError;
|
|
||||||
import org.keycloak.common.util.HostUtils;
|
|
||||||
|
|
||||||
import javax.security.cert.X509Certificate;
|
|
||||||
import javax.ws.rs.container.ContainerRequestContext;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
|
||||||
import javax.ws.rs.core.SecurityContext;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
* @deprecated Class is deprecated and may be removed in the future. If you want to maintain this class for Keycloak community, please
|
|
||||||
* contact Keycloak team on keycloak-dev mailing list. You can fork it into your github repository and
|
|
||||||
* Keycloak team will reference it from "Keycloak Extensions" page.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public class JaxrsHttpFacade implements OIDCHttpFacade {
|
|
||||||
|
|
||||||
protected final ContainerRequestContext requestContext;
|
|
||||||
protected final SecurityContext securityContext;
|
|
||||||
protected final RequestFacade requestFacade = new RequestFacade();
|
|
||||||
protected final ResponseFacade responseFacade = new ResponseFacade();
|
|
||||||
protected KeycloakSecurityContext keycloakSecurityContext;
|
|
||||||
protected boolean responseFinished;
|
|
||||||
|
|
||||||
public JaxrsHttpFacade(ContainerRequestContext containerRequestContext, SecurityContext securityContext) {
|
|
||||||
this.requestContext = containerRequestContext;
|
|
||||||
this.securityContext = securityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class RequestFacade implements OIDCHttpFacade.Request {
|
|
||||||
|
|
||||||
private InputStream inputStream;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFirstParam(String param) {
|
|
||||||
throw new RuntimeException("NOT IMPLEMENTED");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMethod() {
|
|
||||||
return requestContext.getMethod();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getURI() {
|
|
||||||
return requestContext.getUriInfo().getRequestUri().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRelativePath() {
|
|
||||||
return requestContext.getUriInfo().getPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSecure() {
|
|
||||||
return securityContext.isSecure();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getQueryParamValue(String param) {
|
|
||||||
MultivaluedMap<String, String> queryParams = requestContext.getUriInfo().getQueryParameters();
|
|
||||||
if (queryParams == null)
|
|
||||||
return null;
|
|
||||||
return queryParams.getFirst(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cookie getCookie(String cookieName) {
|
|
||||||
Map<String, javax.ws.rs.core.Cookie> cookies = requestContext.getCookies();
|
|
||||||
if (cookies == null)
|
|
||||||
return null;
|
|
||||||
javax.ws.rs.core.Cookie cookie = cookies.get(cookieName);
|
|
||||||
if (cookie == null)
|
|
||||||
return null;
|
|
||||||
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader(String name) {
|
|
||||||
return requestContext.getHeaderString(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getHeaders(String name) {
|
|
||||||
MultivaluedMap<String, String> headers = requestContext.getHeaders();
|
|
||||||
return (headers == null) ? null : headers.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getInputStream() {
|
|
||||||
return getInputStream(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getInputStream(boolean buffered) {
|
|
||||||
if (inputStream != null) {
|
|
||||||
return inputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffered) {
|
|
||||||
return inputStream = new BufferedInputStream(requestContext.getEntityStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestContext.getEntityStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRemoteAddr() {
|
|
||||||
// TODO: implement properly
|
|
||||||
return HostUtils.getIpAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setError(AuthenticationError error) {
|
|
||||||
requestContext.setProperty(AuthenticationError.class.getName(), error);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setError(LogoutError error) {
|
|
||||||
requestContext.setProperty(LogoutError.class.getName(), error);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class ResponseFacade implements OIDCHttpFacade.Response {
|
|
||||||
|
|
||||||
private javax.ws.rs.core.Response.ResponseBuilder responseBuilder = javax.ws.rs.core.Response.status(204);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setStatus(int status) {
|
|
||||||
responseBuilder.status(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addHeader(String name, String value) {
|
|
||||||
responseBuilder.header(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setHeader(String name, String value) {
|
|
||||||
responseBuilder.header(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resetCookie(String name, String path) {
|
|
||||||
// For now doesn't need to be supported
|
|
||||||
throw new IllegalStateException("Not supported yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
|
|
||||||
// For now doesn't need to be supported
|
|
||||||
throw new IllegalStateException("Not supported yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OutputStream getOutputStream() {
|
|
||||||
// For now doesn't need to be supported
|
|
||||||
throw new IllegalStateException("Not supported yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendError(int code) {
|
|
||||||
javax.ws.rs.core.Response response = responseBuilder.status(code).build();
|
|
||||||
requestContext.abortWith(response);
|
|
||||||
responseFinished = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendError(int code, String message) {
|
|
||||||
javax.ws.rs.core.Response response = responseBuilder.status(code).entity(message).build();
|
|
||||||
requestContext.abortWith(response);
|
|
||||||
responseFinished = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void end() {
|
|
||||||
javax.ws.rs.core.Response response = responseBuilder.build();
|
|
||||||
requestContext.abortWith(response);
|
|
||||||
responseFinished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakSecurityContext getSecurityContext() {
|
|
||||||
return keycloakSecurityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSecurityContext(KeycloakSecurityContext securityContext) {
|
|
||||||
this.keycloakSecurityContext = securityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Request getRequest() {
|
|
||||||
return requestFacade;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response getResponse() {
|
|
||||||
return responseFacade;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getCertificateChain() {
|
|
||||||
throw new IllegalStateException("Not supported yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isResponseFinished() {
|
|
||||||
return responseFinished;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.jaxrs;
|
|
||||||
|
|
||||||
import org.keycloak.AbstractOAuthClient;
|
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
|
||||||
import org.keycloak.util.TokenUtil;
|
|
||||||
|
|
||||||
import javax.ws.rs.BadRequestException;
|
|
||||||
import javax.ws.rs.InternalServerErrorException;
|
|
||||||
import javax.ws.rs.client.Client;
|
|
||||||
import javax.ws.rs.client.Entity;
|
|
||||||
import javax.ws.rs.core.Cookie;
|
|
||||||
import javax.ws.rs.core.Form;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.NewCookie;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper code to obtain oauth access tokens via browser redirects
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
* @deprecated Class is deprecated and may be removed in the future. If you want to maintain this class for Keycloak community, please
|
|
||||||
* contact Keycloak team on keycloak-dev mailing list. You can fork it into your github repository and
|
|
||||||
* Keycloak team will reference it from "Keycloak Extensions" page.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public class JaxrsOAuthClient extends AbstractOAuthClient {
|
|
||||||
private final static Logger logger = Logger.getLogger("" + JaxrsOAuthClient.class);
|
|
||||||
protected Client client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* closes client
|
|
||||||
*/
|
|
||||||
public void stop() {
|
|
||||||
if (client != null) client.close();
|
|
||||||
}
|
|
||||||
public Client getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClient(Client client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String resolveBearerToken(String redirectUri, String code) {
|
|
||||||
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
|
||||||
Form codeForm = new Form()
|
|
||||||
.param(OAuth2Constants.GRANT_TYPE, "authorization_code")
|
|
||||||
.param(OAuth2Constants.CODE, code)
|
|
||||||
.param(OAuth2Constants.CLIENT_ID, clientId)
|
|
||||||
.param(OAuth2Constants.REDIRECT_URI, redirectUri);
|
|
||||||
for (Map.Entry<String, Object> entry : credentials.entrySet()) {
|
|
||||||
codeForm.param(entry.getKey(), (String) entry.getValue());
|
|
||||||
}
|
|
||||||
Response res = client.target(tokenUrl).request().post(Entity.form(codeForm));
|
|
||||||
try {
|
|
||||||
if (res.getStatus() == 400) {
|
|
||||||
throw new BadRequestException();
|
|
||||||
} else if (res.getStatus() != 200) {
|
|
||||||
throw new InternalServerErrorException(new Exception("Unknown error when getting acess token"));
|
|
||||||
}
|
|
||||||
AccessTokenResponse tokenResponse = res.readEntity(AccessTokenResponse.class);
|
|
||||||
return tokenResponse.getToken();
|
|
||||||
} finally {
|
|
||||||
res.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
|
||||||
String state = getStateCode();
|
|
||||||
String scopeParam = TokenUtil.attachOIDCScope(scope);
|
|
||||||
|
|
||||||
UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
|
|
||||||
.queryParam(OAuth2Constants.CLIENT_ID, clientId)
|
|
||||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
|
||||||
.queryParam(OAuth2Constants.STATE, state)
|
|
||||||
.queryParam(OAuth2Constants.SCOPE, scopeParam);
|
|
||||||
|
|
||||||
URI url = uriBuilder.build();
|
|
||||||
|
|
||||||
NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
|
|
||||||
logger.fine("NewCookie: " + cookie.toString());
|
|
||||||
logger.fine("Oauth Redirect to: " + url);
|
|
||||||
return Response.status(302)
|
|
||||||
.location(url)
|
|
||||||
.cookie(cookie).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStateCookiePath(UriInfo uriInfo) {
|
|
||||||
if (stateCookiePath != null) return stateCookiePath;
|
|
||||||
return uriInfo.getBaseUri().getRawPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBearerToken(UriInfo uriInfo, HttpHeaders headers) throws BadRequestException, InternalServerErrorException {
|
|
||||||
String error = getError(uriInfo);
|
|
||||||
if (error != null) throw new BadRequestException(new Exception("OAuth error: " + error));
|
|
||||||
checkStateCookie(uriInfo, headers);
|
|
||||||
String code = getAccessCode(uriInfo);
|
|
||||||
if (code == null) throw new BadRequestException(new Exception("code parameter was null"));
|
|
||||||
return resolveBearerToken(uriInfo.getRequestUri().toString(), code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getError(UriInfo uriInfo) {
|
|
||||||
return uriInfo.getQueryParameters().getFirst(OAuth2Constants.ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAccessCode(UriInfo uriInfo) {
|
|
||||||
return uriInfo.getQueryParameters().getFirst(OAuth2Constants.CODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkStateCookie(UriInfo uriInfo, HttpHeaders headers) {
|
|
||||||
Cookie stateCookie = headers.getCookies().get(stateCookieName);
|
|
||||||
if (stateCookie == null) throw new BadRequestException("state cookie not set");
|
|
||||||
String state = uriInfo.getQueryParameters().getFirst(OAuth2Constants.STATE);
|
|
||||||
if (state == null) throw new BadRequestException("state parameter was null");
|
|
||||||
if (!state.equals(stateCookie.getValue())) {
|
|
||||||
throw new BadRequestException("state parameter invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.jaxrs;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.common.constants.GenericConstants;
|
|
||||||
import org.osgi.framework.BundleContext;
|
|
||||||
|
|
||||||
import javax.annotation.Priority;
|
|
||||||
import javax.ws.rs.Priorities;
|
|
||||||
import javax.ws.rs.container.PreMatching;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Variant of JaxrsBearerTokenFilter, which can be used to properly use resources from current osgi bundle
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
* @deprecated Class is deprecated and may be removed in the future. If you want to maintain this class for Keycloak community, please
|
|
||||||
* contact Keycloak team on keycloak-dev mailing list. You can fork it into your github repository and
|
|
||||||
* Keycloak team will reference it from "Keycloak Extensions" page.
|
|
||||||
*/
|
|
||||||
@PreMatching
|
|
||||||
@Priority(Priorities.AUTHENTICATION)
|
|
||||||
@Deprecated
|
|
||||||
public class OsgiJaxrsBearerTokenFilterImpl extends JaxrsBearerTokenFilterImpl {
|
|
||||||
|
|
||||||
private final static Logger log = Logger.getLogger("" + JaxrsBearerTokenFilterImpl.class);
|
|
||||||
|
|
||||||
private BundleContext bundleContext;
|
|
||||||
|
|
||||||
public BundleContext getBundleContext() {
|
|
||||||
return bundleContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBundleContext(BundleContext bundleContext) {
|
|
||||||
this.bundleContext = bundleContext;
|
|
||||||
attemptStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isInitialized() {
|
|
||||||
return super.isInitialized() && bundleContext != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Class<? extends KeycloakConfigResolver> loadResolverClass() {
|
|
||||||
String resolverClass = getKeycloakConfigResolverClass();
|
|
||||||
try {
|
|
||||||
return (Class<? extends KeycloakConfigResolver>) bundleContext.getBundle().loadClass(resolverClass);
|
|
||||||
} catch (ClassNotFoundException cnfe) {
|
|
||||||
log.warning("Not able to find class from bundleContext. Fallback to current classloader");
|
|
||||||
return super.loadResolverClass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected InputStream loadKeycloakConfigFile() {
|
|
||||||
String keycloakConfigFile = getKeycloakConfigFile();
|
|
||||||
if (keycloakConfigFile.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
|
|
||||||
|
|
||||||
// Load from classpath of current bundle
|
|
||||||
String classPathLocation = keycloakConfigFile.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
|
|
||||||
log.fine("Loading config from classpath on location: " + classPathLocation);
|
|
||||||
|
|
||||||
URL cfgUrl = bundleContext.getBundle().getResource(classPathLocation);
|
|
||||||
if (cfgUrl == null) {
|
|
||||||
log.warning("Not able to find configFile from bundleContext. Fallback to current classloader");
|
|
||||||
return super.loadKeycloakConfigFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return cfgUrl.openStream();
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new RuntimeException(ioe);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return super.loadKeycloakConfigFile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,10 +32,7 @@
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>adapter-core</module>
|
<module>adapter-core</module>
|
||||||
<module>jaxrs-oauth-client</module>
|
|
||||||
<module>js</module>
|
<module>js</module>
|
||||||
<module>servlet-filter</module>
|
|
||||||
<module>jakarta-servlet-filter</module>
|
|
||||||
<module>undertow</module>
|
<module>undertow</module>
|
||||||
<module>wildfly</module>
|
<module>wildfly</module>
|
||||||
<module>wildfly-elytron</module>
|
<module>wildfly-elytron</module>
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
~ and other contributors as indicated by the @author tags.
|
|
||||||
~
|
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
~ you may not use this file except in compliance with the License.
|
|
||||||
~ You may obtain a copy of the License at
|
|
||||||
~
|
|
||||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
~
|
|
||||||
~ Unless required by applicable law or agreed to in writing, software
|
|
||||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
~ See the License for the specific language governing permissions and
|
|
||||||
~ limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>999.0.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-servlet-filter-adapter</artifactId>
|
|
||||||
<name>Keycloak Servlet Filter Adapter Integration</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<keycloak.osgi.export>
|
|
||||||
org.keycloak.adapters.servlet.*
|
|
||||||
</keycloak.osgi.export>
|
|
||||||
<keycloak.osgi.import>
|
|
||||||
javax.servlet.*;version="[3.1,5)";resolution:=optional,
|
|
||||||
org.keycloak.*;version="${project.version}",
|
|
||||||
*;resolution:=optional
|
|
||||||
</keycloak.osgi.import>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.logging</groupId>
|
|
||||||
<artifactId>jboss-logging</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-servlet-adapter-spi</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcprov-jdk18on</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-annotations</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<archive>
|
|
||||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
|
||||||
</archive>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.felix</groupId>
|
|
||||||
<artifactId>maven-bundle-plugin</artifactId>
|
|
||||||
<extensions>true</extensions>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>bundle-manifest</id>
|
|
||||||
<phase>process-classes</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>manifest</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
<configuration>
|
|
||||||
<instructions>
|
|
||||||
<Bundle-ClassPath>.</Bundle-ClassPath>
|
|
||||||
<Bundle-Name>${project.name}</Bundle-Name>
|
|
||||||
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
|
||||||
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
|
||||||
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
|
||||||
</instructions>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,124 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.servlet;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class FilterRequestAuthenticator extends RequestAuthenticator {
|
|
||||||
private static final Logger log = Logger.getLogger(""+FilterRequestAuthenticator.class);
|
|
||||||
protected HttpServletRequest request;
|
|
||||||
|
|
||||||
public FilterRequestAuthenticator(KeycloakDeployment deployment,
|
|
||||||
AdapterTokenStore tokenStore,
|
|
||||||
OIDCHttpFacade facade,
|
|
||||||
HttpServletRequest request,
|
|
||||||
int sslRedirectPort) {
|
|
||||||
super(facade, deployment, tokenStore, sslRedirectPort);
|
|
||||||
this.request = request;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
|
|
||||||
return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
|
|
||||||
final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
|
|
||||||
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
|
||||||
OidcKeycloakAccount account = new OidcKeycloakAccount() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Principal getPrincipal() {
|
|
||||||
return skp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getRoles() {
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakSecurityContext getKeycloakSecurityContext() {
|
|
||||||
return securityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
|
||||||
this.tokenStore.saveAccountInfo(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void completeBearerAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
|
|
||||||
final RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
|
|
||||||
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
|
||||||
if (log.isLoggable(Level.FINE)) {
|
|
||||||
log.fine("Completing bearer authentication. Bearer roles: " + roles);
|
|
||||||
}
|
|
||||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
|
||||||
OidcKeycloakAccount account = new OidcKeycloakAccount() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Principal getPrincipal() {
|
|
||||||
return principal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getRoles() {
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakSecurityContext getKeycloakSecurityContext() {
|
|
||||||
return securityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
// need this here to obtain UserPrincipal
|
|
||||||
request.setAttribute(KeycloakAccount.class.getName(), account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String changeHttpSessionId(boolean create) {
|
|
||||||
HttpSession session = request.getSession(create);
|
|
||||||
return session != null ? session.getId() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,273 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.servlet;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
|
||||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
|
||||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
|
|
||||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.FilterConfig;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
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 ID_MAPPER_PARAM = "keycloak.config.idMapper";
|
|
||||||
|
|
||||||
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 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 idMapperClassName = filterConfig.getInitParameter(ID_MAPPER_PARAM);
|
|
||||||
if (idMapperClassName != null) {
|
|
||||||
try {
|
|
||||||
final Class<?> idMapperClass = getClass().getClassLoader().loadClass(idMapperClassName);
|
|
||||||
final Constructor<?> idMapperConstructor = idMapperClass.getDeclaredConstructor();
|
|
||||||
Object idMapperInstance = null;
|
|
||||||
// for KEYCLOAK-13745 test
|
|
||||||
if (idMapperConstructor.getModifiers() == Modifier.PRIVATE) {
|
|
||||||
idMapperInstance = idMapperClass.getMethod("getInstance").invoke(null);
|
|
||||||
} else {
|
|
||||||
idMapperInstance = idMapperConstructor.newInstance();
|
|
||||||
}
|
|
||||||
if(idMapperInstance instanceof SessionIdMapper) {
|
|
||||||
this.idMapper = (SessionIdMapper) idMapperInstance;
|
|
||||||
} else {
|
|
||||||
log.log(Level.WARNING, "SessionIdMapper class {0} is not instance of org.keycloak.adapters.spi.SessionIdMapper", idMapperClassName);
|
|
||||||
}
|
|
||||||
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
|
||||||
log.log(Level.WARNING, "SessionIdMapper class could not be instanced", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 configResolverClass = filterConfig.getInitParameter(CONFIG_RESOLVER_PARAM);
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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");
|
|
||||||
HttpServletRequest request = (HttpServletRequest) req;
|
|
||||||
HttpServletResponse response = (HttpServletResponse) res;
|
|
||||||
|
|
||||||
if (shouldSkip(request)) {
|
|
||||||
chain.doFilter(req, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response);
|
|
||||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
|
||||||
if (deployment == null || !deployment.isConfigured()) {
|
|
||||||
response.sendError(403);
|
|
||||||
log.fine("deployment not configured");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PreAuthActionsHandler preActions = new PreAuthActionsHandler(new IdMapperUserSessionManagement(), deploymentContext, facade);
|
|
||||||
|
|
||||||
if (preActions.handleRequest()) {
|
|
||||||
//System.err.println("**************** preActions.handleRequest happened!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
nodesRegistrationManagement.tryRegister(deployment);
|
|
||||||
OIDCFilterSessionStore tokenStore = new OIDCFilterSessionStore(request, facade, 100000, deployment, idMapper);
|
|
||||||
tokenStore.checkCurrentToken();
|
|
||||||
|
|
||||||
|
|
||||||
FilterRequestAuthenticator authenticator = new FilterRequestAuthenticator(deployment, tokenStore, facade, request, 8443);
|
|
||||||
AuthOutcome outcome = authenticator.authenticate();
|
|
||||||
if (outcome == AuthOutcome.AUTHENTICATED) {
|
|
||||||
log.fine("AUTHENTICATED");
|
|
||||||
if (facade.isEnded()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(deployment, facade);
|
|
||||||
if (actions.handledRequest()) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
HttpServletRequestWrapper wrapper = tokenStore.buildWrapper();
|
|
||||||
chain.doFilter(wrapper, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AuthChallenge challenge = authenticator.getChallenge();
|
|
||||||
if (challenge != null) {
|
|
||||||
log.fine("challenge");
|
|
||||||
challenge.challenge(facade);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
response.sendError(403);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decides whether this {@link Filter} should skip the given {@link HttpServletRequest} based on the configured {@link KeycloakOIDCFilter#skipPattern}.
|
|
||||||
* Patterns are matched against the {@link HttpServletRequest#getRequestURI() requestURI} of a request without the context-path.
|
|
||||||
* A request for {@code /myapp/index.html} would be tested with {@code /index.html} against the skip pattern.
|
|
||||||
* Skipped requests will not be processed further by {@link KeycloakOIDCFilter} and immediately delegated to the {@link FilterChain}.
|
|
||||||
*
|
|
||||||
* @param request the request to check
|
|
||||||
* @return {@code true} if the request should not be handled,
|
|
||||||
* {@code false} otherwise.
|
|
||||||
*/
|
|
||||||
private boolean shouldSkip(HttpServletRequest request) {
|
|
||||||
|
|
||||||
if (skipPattern == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String requestPath = request.getRequestURI().substring(request.getContextPath().length());
|
|
||||||
return skipPattern.matcher(requestPath).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IdMapperUserSessionManagement implements UserSessionManagement {
|
|
||||||
@Override
|
|
||||||
public void logoutAll() {
|
|
||||||
if (idMapper != null) {
|
|
||||||
idMapper.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logoutHttpSessions(List<String> ids) {
|
|
||||||
log.fine("**************** logoutHttpSessions");
|
|
||||||
//System.err.println("**************** logoutHttpSessions");
|
|
||||||
for (String id : ids) {
|
|
||||||
log.finest("removed idMapper: " + id);
|
|
||||||
idMapper.removeSession(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,213 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.servlet;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
|
||||||
import org.keycloak.common.util.DelegatingSerializationFilter;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class OIDCFilterSessionStore extends FilterSessionStore implements AdapterTokenStore {
|
|
||||||
protected final KeycloakDeployment deployment;
|
|
||||||
private static final Logger log = Logger.getLogger("" + OIDCFilterSessionStore.class);
|
|
||||||
protected final SessionIdMapper idMapper;
|
|
||||||
|
|
||||||
public OIDCFilterSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer, KeycloakDeployment deployment, SessionIdMapper idMapper) {
|
|
||||||
super(request, facade, maxBuffer);
|
|
||||||
this.deployment = deployment;
|
|
||||||
this.idMapper = idMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpServletRequestWrapper buildWrapper() {
|
|
||||||
HttpSession session = request.getSession(false);
|
|
||||||
KeycloakAccount account = null;
|
|
||||||
if (session != null) {
|
|
||||||
account = (KeycloakAccount) session.getAttribute(KeycloakAccount.class.getName());
|
|
||||||
if (account == null) {
|
|
||||||
account = (KeycloakAccount) request.getAttribute(KeycloakAccount.class.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (account == null) {
|
|
||||||
account = (KeycloakAccount) request.getAttribute(KeycloakAccount.class.getName());
|
|
||||||
}
|
|
||||||
return buildWrapper(session, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkCurrentToken() {
|
|
||||||
HttpSession httpSession = request.getSession(false);
|
|
||||||
if (httpSession == null) return;
|
|
||||||
SerializableKeycloakAccount account = (SerializableKeycloakAccount)httpSession.getAttribute(KeycloakAccount.class.getName());
|
|
||||||
if (account == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshableKeycloakSecurityContext session = account.getKeycloakSecurityContext();
|
|
||||||
if (session == null) return;
|
|
||||||
|
|
||||||
// just in case session got serialized
|
|
||||||
if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
|
|
||||||
|
|
||||||
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
|
|
||||||
|
|
||||||
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
|
|
||||||
// not be updated
|
|
||||||
boolean success = session.refreshExpiredToken(false);
|
|
||||||
if (success && session.isActive()) return;
|
|
||||||
|
|
||||||
// Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
|
|
||||||
//log.fine("Cleanup and expire session " + httpSession.getId() + " after failed refresh");
|
|
||||||
cleanSession(httpSession);
|
|
||||||
httpSession.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void cleanSession(HttpSession session) {
|
|
||||||
session.removeAttribute(KeycloakAccount.class.getName());
|
|
||||||
session.removeAttribute(KeycloakSecurityContext.class.getName());
|
|
||||||
clearSavedRequest(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCached(RequestAuthenticator authenticator) {
|
|
||||||
HttpSession httpSession = request.getSession(false);
|
|
||||||
if (httpSession == null) return false;
|
|
||||||
SerializableKeycloakAccount account = (SerializableKeycloakAccount) httpSession.getAttribute(KeycloakAccount.class.getName());
|
|
||||||
if (account == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.fine("remote logged in already. Establish state from session");
|
|
||||||
|
|
||||||
RefreshableKeycloakSecurityContext securityContext = account.getKeycloakSecurityContext();
|
|
||||||
|
|
||||||
if (!deployment.getRealm().equals(securityContext.getRealm())) {
|
|
||||||
log.fine("Account from cookie is from a different realm than for the request.");
|
|
||||||
cleanSession(httpSession);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idMapper != null && !idMapper.hasSession(httpSession.getId())) {
|
|
||||||
log.fine("idMapper does not have session: " + httpSession.getId());
|
|
||||||
//System.err.println("idMapper does not have session: " + httpSession.getId());
|
|
||||||
cleanSession(httpSession);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
securityContext.setCurrentRequestInfo(deployment, this);
|
|
||||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
|
||||||
needRequestRestore = restoreRequest();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SerializableKeycloakAccount implements OidcKeycloakAccount, Serializable {
|
|
||||||
protected Set<String> roles;
|
|
||||||
protected Principal principal;
|
|
||||||
protected RefreshableKeycloakSecurityContext securityContext;
|
|
||||||
|
|
||||||
public SerializableKeycloakAccount(Set<String> roles, Principal principal, RefreshableKeycloakSecurityContext securityContext) {
|
|
||||||
this.roles = roles;
|
|
||||||
this.principal = principal;
|
|
||||||
this.securityContext = securityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Principal getPrincipal() {
|
|
||||||
return principal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getRoles() {
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
|
|
||||||
return securityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
|
||||||
DelegatingSerializationFilter.builder()
|
|
||||||
.addAllowedClass(OIDCFilterSessionStore.SerializableKeycloakAccount.class)
|
|
||||||
.addAllowedClass(RefreshableKeycloakSecurityContext.class)
|
|
||||||
.addAllowedClass(KeycloakSecurityContext.class)
|
|
||||||
.addAllowedClass(KeycloakPrincipal.class)
|
|
||||||
.setFilter(in);
|
|
||||||
|
|
||||||
in.defaultReadObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveAccountInfo(OidcKeycloakAccount account) {
|
|
||||||
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext();
|
|
||||||
Set<String> roles = account.getRoles();
|
|
||||||
|
|
||||||
SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext);
|
|
||||||
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().getSessionState(), account.getPrincipal().getName(), httpSession.getId());
|
|
||||||
//String username = securityContext.getToken().getSubject();
|
|
||||||
//log.fine("userSessionManagement.login: " + username);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logout() {
|
|
||||||
HttpSession httpSession = request.getSession(false);
|
|
||||||
if (httpSession != null) {
|
|
||||||
SerializableKeycloakAccount account = (SerializableKeycloakAccount) httpSession.getAttribute(KeycloakAccount.class.getName());
|
|
||||||
if (account != null) {
|
|
||||||
account.getKeycloakSecurityContext().logout(deployment);
|
|
||||||
}
|
|
||||||
cleanSession(httpSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void servletRequestLogout() {
|
|
||||||
logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.adapters.servlet;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class OIDCServletHttpFacade extends ServletHttpFacade implements OIDCHttpFacade {
|
|
||||||
|
|
||||||
public OIDCServletHttpFacade(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
super(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakSecurityContext getSecurityContext() {
|
|
||||||
return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,10 +17,6 @@ include::java-adapter-config.adoc[]
|
||||||
endif::[]
|
endif::[]
|
||||||
include::jboss-adapter.adoc[]
|
include::jboss-adapter.adoc[]
|
||||||
|
|
||||||
ifeval::[{project_community}==true]
|
|
||||||
include::servlet-filter-adapter.adoc[]
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
ifeval::[{project_community}==true]
|
ifeval::[{project_community}==true]
|
||||||
include::adapter-context.adoc[]
|
include::adapter-context.adoc[]
|
||||||
include::adapter_error_handling.adoc[]
|
include::adapter_error_handling.adoc[]
|
||||||
|
|
|
@ -1,173 +0,0 @@
|
||||||
[[_servlet_filter_adapter]]
|
|
||||||
==== Java servlet filter adapter
|
|
||||||
|
|
||||||
include::adapter-deprecation-notice.adoc[]
|
|
||||||
|
|
||||||
If you are deploying your Java Servlet application on a platform where there is no {project_name} adapter you opt to use the servlet filter adapter.
|
|
||||||
This adapter works a bit differently than the other adapters. You do not define security constraints in web.xml.
|
|
||||||
Instead you define a filter mapping using the {project_name} servlet filter adapter to secure the url patterns you want to secure.
|
|
||||||
|
|
||||||
WARNING: Backchannel logout works a bit differently than the standard adapters.
|
|
||||||
Instead of invalidating the HTTP session it marks the session id as logged out.
|
|
||||||
There's no standard way to invalidate an HTTP session based on a session id.
|
|
||||||
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
|
||||||
version="3.0">
|
|
||||||
|
|
||||||
<module-name>application</module-name>
|
|
||||||
|
|
||||||
<filter>
|
|
||||||
<filter-name>Keycloak Filter</filter-name>
|
|
||||||
<filter-class>org.keycloak.adapters.servlet.KeycloakOIDCFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>Keycloak Filter</filter-name>
|
|
||||||
<url-pattern>/keycloak/*</url-pattern>
|
|
||||||
<url-pattern>/protected/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
</web-app>
|
|
||||||
----
|
|
||||||
|
|
||||||
In the snippet above there are two url-patterns.
|
|
||||||
_/protected/*_ are the files we want protected, while the _/keycloak/*_ url-pattern handles callbacks from the {project_name} server.
|
|
||||||
|
|
||||||
If you need to exclude some paths beneath the configured `url-patterns` you can use the Filter init-param `keycloak.config.skipPattern` to configure
|
|
||||||
a regular expression that describes a path-pattern for which the keycloak filter should immediately delegate to the filter-chain.
|
|
||||||
By default no skipPattern is configured.
|
|
||||||
|
|
||||||
Patterns are matched against the `requestURI` without the `context-path`. Given the context-path `/myapp` a request for `/myapp/index.html` will be matched with `/index.html` against the skip pattern.
|
|
||||||
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<init-param>
|
|
||||||
<param-name>keycloak.config.skipPattern</param-name>
|
|
||||||
<param-value>^/(path1|path2|path3).*</param-value>
|
|
||||||
</init-param>
|
|
||||||
----
|
|
||||||
|
|
||||||
Note that you should configure your client in the {project_name} Admin Console with an Admin URL that points to a secured section covered by the filter's url-pattern.
|
|
||||||
|
|
||||||
The Admin URL will make callbacks to the Admin URL to do things like backchannel logout.
|
|
||||||
So, the Admin URL in this example should be `http[s]://hostname/{context-root}/keycloak`.
|
|
||||||
|
|
||||||
If you need to customize the session ID mapper, you can configure the fully qualified name of the class in the Filter init-param keycloak.config.idMapper. Session ID mapper is a mapper that is used to map user IDs and session IDs. By default org.keycloak.adapters.spi.InMemorySessionIdMapper is configured.
|
|
||||||
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<init-param>
|
|
||||||
<param-name>keycloak.config.idMapper</param-name>
|
|
||||||
<param-value>org.keycloak.adapters.spi.InMemorySessionIdMapper</param-value>
|
|
||||||
</init-param>
|
|
||||||
----
|
|
||||||
|
|
||||||
The {project_name} filter has the same configuration parameters as the other adapters except you must define them as filter init params instead of context params.
|
|
||||||
|
|
||||||
To use this filter, include this maven artifact in your WAR poms:
|
|
||||||
|
|
||||||
[source,xml,subs="attributes+"]
|
|
||||||
----
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-servlet-filter-adapter</artifactId>
|
|
||||||
<version>{project_versionMvn}</version>
|
|
||||||
</dependency>
|
|
||||||
----
|
|
||||||
ifeval::[{project_community}==true]
|
|
||||||
===== Using on OSGi
|
|
||||||
|
|
||||||
The servlet filter adapter is packaged as an OSGi bundle, and thus is usable in a generic OSGi environment (R6 and above) with HTTP Service and HTTP Whiteboard.
|
|
||||||
|
|
||||||
====== Installation
|
|
||||||
|
|
||||||
The adapter and its dependencies are distributed as Maven artifacts, so you'll need either working Internet connection to access Maven Central, or have the artifacts cached in your local Maven repo.
|
|
||||||
|
|
||||||
If you are using Apache Karaf, you can simply install a feature from the Keycloak feature repo:
|
|
||||||
|
|
||||||
[source,subs="attributes+"]
|
|
||||||
----
|
|
||||||
karaf@root()> feature:repo-add mvn:org.keycloak/keycloak-osgi-features/{project_versionMvn}/xml/features
|
|
||||||
karaf@root()> feature:install keycloak-servlet-filter-adapter
|
|
||||||
----
|
|
||||||
|
|
||||||
For other OSGi runtimes, please refer to the runtime documentation on how to install the adapter bundle and its dependencies.
|
|
||||||
|
|
||||||
NOTE: If your OSGi platform is Apache Karaf with Pax Web, you should consider using https://access.redhat.com/products/red-hat-single-sign-on/[Red Hat Single Sign-On] 7.x JBoss Fuse 7 adapters instead.
|
|
||||||
|
|
||||||
====== Configuration
|
|
||||||
|
|
||||||
First, the adapter needs to be registered as a servlet filter with the OSGi HTTP Service. The most common ways to do this are programmatic (for example via bundle activator) and declarative (using OSGi annotations).
|
|
||||||
We recommend using the latter since it simplifies the process of dynamically registering and un-registering the filter:
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
package mypackage;
|
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import org.keycloak.adapters.servlet.KeycloakOIDCFilter;
|
|
||||||
import org.osgi.service.component.annotations.Component;
|
|
||||||
import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
|
|
||||||
|
|
||||||
@Component(
|
|
||||||
immediate = true,
|
|
||||||
service = Filter.class,
|
|
||||||
property = {
|
|
||||||
KeycloakOIDCFilter.CONFIG_FILE_PARAM + "=" + "keycloak.json",
|
|
||||||
HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_PATTERN + "=" +"/*",
|
|
||||||
HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT + "=" + "(osgi.http.whiteboard.context.name=mycontext)"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public class KeycloakFilter extends KeycloakOIDCFilter {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
The above snippet uses OSGi declarative service specification to expose the filter as an OSGI service under `javax.servlet.Filter` class.
|
|
||||||
Once the class is published in the OSGi service registry, it is going to be picked up by OSGi HTTP Service implementation and used for filtering requests for the specified servlet context. This will trigger Keycloak adapter for every request that matches servlet context path + filter path.
|
|
||||||
|
|
||||||
Since the component is put under the control of OSGi Configuration Admin Service, it's properties can be configured dynamically.
|
|
||||||
To do that, either create a `mypackage.KeycloakFilter.cfg` file under the standard config location for your OSGi runtime:
|
|
||||||
[source]
|
|
||||||
|
|
||||||
----
|
|
||||||
keycloak.config.file = /path/to/keycloak.json
|
|
||||||
osgi.http.whiteboard.filter.pattern = /secure/*
|
|
||||||
----
|
|
||||||
|
|
||||||
or use interactive console, if your runtime allows for that:
|
|
||||||
|
|
||||||
[source]
|
|
||||||
----
|
|
||||||
karaf@root()> config:edit mypackage.KeycloakFilter
|
|
||||||
karaf@root()> config:property-set keycloak.config.file '${karaf.etc}/keycloak.json'
|
|
||||||
karaf@root()> config:update
|
|
||||||
----
|
|
||||||
|
|
||||||
If you need more control, for example, providing custom `KeycloakConfigResolver` to implement <<_multi_tenancy,multi tenancy>>, you can register the filter programmatically:
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
public class Activator implements BundleActivator {
|
|
||||||
|
|
||||||
private ServiceRegistration registration;
|
|
||||||
|
|
||||||
public void start(BundleContext context) throws Exception {
|
|
||||||
Hashtable props = new Hashtable();
|
|
||||||
props.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_PATTERN, "/secure/*");
|
|
||||||
props.put(KeycloakOIDCFilter.CONFIG_RESOLVER_PARAM, new MyConfigResolver());
|
|
||||||
|
|
||||||
this.registration = context.registerService(Filter.class.getName(), new KeycloakOIDCFilter(), props);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop(BundleContext context) throws Exception {
|
|
||||||
this.registration.unregister();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
Please refer to https://github.com/apache/felix-dev/tree/master/http#using-the-osgi-http-whiteboard[Apache Felix HTTP Service] for more info on programmatic registration.
|
|
||||||
|
|
||||||
endif::[]
|
|
15
pom.xml
15
pom.xml
|
@ -1044,11 +1044,6 @@
|
||||||
<artifactId>keycloak-as7-adapter-spi</artifactId>
|
<artifactId>keycloak-as7-adapter-spi</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-jboss-adapter-core</artifactId>
|
<artifactId>keycloak-jboss-adapter-core</artifactId>
|
||||||
|
@ -1084,16 +1079,6 @@
|
||||||
<artifactId>keycloak-saml-jakarta-servlet-filter-adapter</artifactId>
|
<artifactId>keycloak-saml-jakarta-servlet-filter-adapter</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-servlet-filter-adapter</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-jakarta-servlet-filter-adapter</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-as7-adapter</artifactId>
|
<artifactId>keycloak-saml-as7-adapter</artifactId>
|
||||||
|
|
|
@ -75,10 +75,6 @@
|
||||||
<artifactId>integration-arquillian-testsuite-providers</artifactId>
|
<artifactId>integration-arquillian-testsuite-providers</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-jakarta-servlet-filter-adapter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-services</artifactId>
|
<artifactId>keycloak-services</artifactId>
|
||||||
|
|
|
@ -30,7 +30,6 @@ import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||||
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
|
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
|
||||||
import org.jboss.shrinkwrap.resolver.api.maven.MavenFormatStage;
|
import org.jboss.shrinkwrap.resolver.api.maven.MavenFormatStage;
|
||||||
import org.jboss.shrinkwrap.resolver.api.maven.MavenResolverSystem;
|
import org.jboss.shrinkwrap.resolver.api.maven.MavenResolverSystem;
|
||||||
import org.keycloak.adapters.servlet.KeycloakOIDCFilter;
|
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.testsuite.utils.annotation.UseServletFilter;
|
import org.keycloak.testsuite.utils.annotation.UseServletFilter;
|
||||||
import org.keycloak.testsuite.utils.io.IOUtil;
|
import org.keycloak.testsuite.utils.io.IOUtil;
|
||||||
|
@ -249,10 +248,6 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
|
||||||
filter.appendChild(initParam);
|
filter.appendChild(initParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
appendChildInDocument(webXmlDoc, "web-app", filter);
|
|
||||||
addInitParam(webXmlDoc, filter, KeycloakOIDCFilter.SKIP_PATTERN_PARAM, testClass.getAnnotation(UseServletFilter.class).skipPattern());
|
|
||||||
addInitParam(webXmlDoc, filter, KeycloakOIDCFilter.ID_MAPPER_PARAM, testClass.getAnnotation(UseServletFilter.class).idMapper());
|
|
||||||
|
|
||||||
appendChildInDocument(webXmlDoc, "web-app", filter);
|
appendChildInDocument(webXmlDoc, "web-app", filter);
|
||||||
|
|
||||||
Element filterMapping = webXmlDoc.createElement("filter-mapping");
|
Element filterMapping = webXmlDoc.createElement("filter-mapping");
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.testsuite.adapter.servlet;
|
|
||||||
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
|
||||||
import org.keycloak.testsuite.utils.annotation.UseServletFilter;
|
|
||||||
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
|
||||||
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
|
|
||||||
@UseServletFilter(filterName = "oidc-filter", filterClass = "org.keycloak.adapters.servlet.KeycloakOIDCFilter",
|
|
||||||
filterDependency = "org.keycloak:keycloak-servlet-filter-adapter", skipPattern = "/error.html")
|
|
||||||
public class DemoFilterServletAdapterTest extends DemoServletsAdapterTest {
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore(value = "Can't test because of the way filter works")
|
|
||||||
public void testNullBearerTokenCustomErrorPage() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore(value = "Don't need to test this because HttpServletRequest.authenticate "
|
|
||||||
+ "doesn't make sense with filter implementation")
|
|
||||||
public void testAuthenticated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore
|
|
||||||
public void testAuthenticatedWithCustomSessionConfig() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore
|
|
||||||
public void testOIDCParamsForwarding() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore
|
|
||||||
public void testOIDCUiLocalesParamForwarding() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore
|
|
||||||
public void testTokenInCookieSSO() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore
|
|
||||||
public void testInvalidTokenCookie() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore
|
|
||||||
public void testTokenInCookieRefresh() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore
|
|
||||||
public void testTokenInCookieSSORoot() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.testsuite.adapter.servlet;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
|
|
||||||
import org.keycloak.testsuite.adapter.page.CustomerPortal;
|
|
||||||
import org.keycloak.testsuite.adapter.spi.TestSessionIdMapper;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
|
||||||
import org.keycloak.testsuite.util.JavascriptBrowser;
|
|
||||||
import org.keycloak.testsuite.utils.annotation.UseServletFilter;
|
|
||||||
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.jboss.arquillian.container.test.api.Deployment;
|
|
||||||
import org.jboss.arquillian.drone.api.annotation.Drone;
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
|
||||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
|
|
||||||
/* Execute with JBoss-based app servers after resolving this issue: https://github.com/keycloak/keycloak/issues/19809
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
|
|
||||||
*/
|
|
||||||
@UseServletFilter(filterName = "oidc-filter", filterClass = "org.keycloak.adapters.servlet.KeycloakOIDCFilter",
|
|
||||||
filterDependency = "org.keycloak:keycloak-servlet-filter-adapter", skipPattern = "/error.html",
|
|
||||||
idMapper = "org.keycloak.testsuite.adapter.spi.TestSessionIdMapper")
|
|
||||||
public class DemoFilterServletAdapterTestForCustomizedIdMapper extends AbstractServletsAdapterTest {
|
|
||||||
|
|
||||||
@Drone
|
|
||||||
@JavascriptBrowser
|
|
||||||
protected WebDriver jsDriver;
|
|
||||||
|
|
||||||
@Page
|
|
||||||
protected CustomerPortal customerPortal;
|
|
||||||
|
|
||||||
@Deployment(name = CustomerPortal.DEPLOYMENT_NAME)
|
|
||||||
protected static WebArchive customerPortal() {
|
|
||||||
return servletDeployment(CustomerPortal.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class, ServletTestUtils.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// KEYCLOAK-13745
|
|
||||||
@Test
|
|
||||||
public void testCustomizedSessionIdMapper() {
|
|
||||||
customerPortal.navigateTo();
|
|
||||||
TestSessionIdMapper singleton = TestSessionIdMapper.getInstance();
|
|
||||||
assertTrue(singleton.isCalledBy(getClass().getAnnotation(UseServletFilter.class).filterClass()));
|
|
||||||
singleton.clear();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 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.testsuite.adapter.servlet.cluster;
|
|
||||||
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
|
||||||
import org.keycloak.testsuite.utils.annotation.UseServletFilter;
|
|
||||||
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_CLUSTER)
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP_CLUSTER)
|
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6_CLUSTER)
|
|
||||||
@UseServletFilter(filterName = "oidc-filter", filterClass = "org.keycloak.adapters.servlet.KeycloakOIDCFilter",
|
|
||||||
filterDependency = "org.keycloak:keycloak-servlet-filter-adapter", skipPattern = "/error.html")
|
|
||||||
public class OIDCFilterAdapterClusterTest extends OIDCAdapterClusterTest {
|
|
||||||
}
|
|
|
@ -38,10 +38,6 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-servlet-filter-adapter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak.testsuite</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
<artifactId>integration-arquillian-testsuite-providers</artifactId>
|
<artifactId>integration-arquillian-testsuite-providers</artifactId>
|
||||||
|
|
|
@ -23,7 +23,6 @@ import org.jboss.shrinkwrap.api.Node;
|
||||||
import org.jboss.shrinkwrap.api.asset.Asset;
|
import org.jboss.shrinkwrap.api.asset.Asset;
|
||||||
import org.jboss.shrinkwrap.api.asset.StringAsset;
|
import org.jboss.shrinkwrap.api.asset.StringAsset;
|
||||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||||
import org.keycloak.adapters.servlet.KeycloakOIDCFilter;
|
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.testsuite.utils.annotation.UseServletFilter;
|
import org.keycloak.testsuite.utils.annotation.UseServletFilter;
|
||||||
import org.keycloak.testsuite.utils.io.IOUtil;
|
import org.keycloak.testsuite.utils.io.IOUtil;
|
||||||
|
@ -127,12 +126,6 @@ public class DeploymentArchiveProcessorUtils {
|
||||||
filter.appendChild(initParam);
|
filter.appendChild(initParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limitation that all deployments of annotated class use same skipPattern. Refactor if
|
|
||||||
// something more flexible is needed (would require more tricky web.xml parsing though...)
|
|
||||||
addInitParam(webXmlDoc, filter, KeycloakOIDCFilter.SKIP_PATTERN_PARAM, testClass.getAnnotation(UseServletFilter.class).skipPattern());
|
|
||||||
addInitParam(webXmlDoc, filter, KeycloakOIDCFilter.ID_MAPPER_PARAM, testClass.getAnnotation(UseServletFilter.class).idMapper());
|
|
||||||
|
|
||||||
|
|
||||||
IOUtil.appendChildInDocument(webXmlDoc, "web-app", filter);
|
IOUtil.appendChildInDocument(webXmlDoc, "web-app", filter);
|
||||||
|
|
||||||
Element filterMapping = webXmlDoc.createElement("filter-mapping");
|
Element filterMapping = webXmlDoc.createElement("filter-mapping");
|
||||||
|
|
|
@ -186,19 +186,11 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-authz-client</artifactId>
|
<artifactId>keycloak-authz-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-servlet-filter-adapter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak.testsuite</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
<artifactId>keycloak-saml-undertow-adapter-jakarta</artifactId>
|
<artifactId>keycloak-saml-undertow-adapter-jakarta</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<!-- Dependency on services from integration-arquillian -->
|
<!-- Dependency on services from integration-arquillian -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak.testsuite</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
|
|
Loading…
Reference in a new issue