Remove Tomcat SAML adapter

Signed-off-by: Douglas Palmer <dpalmer@redhat.com>

Closes #28783
This commit is contained in:
Douglas Palmer 2024-04-23 08:29:11 -07:00 committed by Marek Posolda
parent 324da02732
commit 98faf6e6a0
24 changed files with 1 additions and 1402 deletions

View file

@ -36,7 +36,6 @@
<module>core-jakarta</module> <module>core-jakarta</module>
<module>jetty</module> <module>jetty</module>
<module>undertow</module> <module>undertow</module>
<module>tomcat</module>
<module>wildfly</module> <module>wildfly</module>
<module>servlet-filter</module> <module>servlet-filter</module>
<module>jakarta-servlet-filter</module> <module>jakarta-servlet-filter</module>

View file

@ -1,37 +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.
-->
<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>
<name>Keycloak SAML Tomcat Integration</name>
<description/>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
<packaging>pom</packaging>
<modules>
<module>tomcat-core</module>
<module>tomcat</module>
</modules>
</project>

View file

@ -1,90 +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-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>999.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-saml-tomcat-adapter-core</artifactId>
<name>Keycloak Tomcat Core SAML Integration</name>
<description />
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>commons-logging-jboss-logging</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-adapter-api-public</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>${tomcat8.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -1,352 +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.saml;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.jboss.logging.Logger;
import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
import org.keycloak.adapters.spi.*;
import org.keycloak.adapters.tomcat.CatalinaHttpFacade;
import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement;
import org.keycloak.adapters.tomcat.PrincipalFactory;
import org.keycloak.saml.common.exceptions.ParsingException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.*;
import java.util.regex.Pattern;
/**
* Keycloak authentication valve
*
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
private final static Logger log = Logger.getLogger(AbstractSamlAuthenticatorValve.class);
protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
protected SamlDeploymentContext deploymentContext;
protected SessionIdMapper mapper = new InMemorySessionIdMapper();
protected SessionIdMapperUpdater idMapperUpdater = SessionIdMapperUpdater.DIRECT;
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (Lifecycle.START_EVENT.equals(event.getType())) {
cache = false;
} else if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
keycloakInit();
} else if (Lifecycle.BEFORE_STOP_EVENT.equals(event.getType())) {
beforeStop();
}
}
protected void logoutInternal(Request request) {
CatalinaHttpFacade facade = new CatalinaHttpFacade(null, request);
SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
SamlSessionStore tokenStore = getSessionStore(request, facade, deployment);
tokenStore.logoutAccount();
request.setUserPrincipal(null);
}
@SuppressWarnings("UseSpecificCatch")
public void keycloakInit() {
// Possible scenarios:
// 1) The deployment has a keycloak.config.resolver specified and it exists:
// Outcome: adapter uses the resolver
// 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exist, isn't a resolver, ...) :
// Outcome: adapter is left unconfigured
// 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent)
// Outcome: adapter uses it
// 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent)
// Outcome: adapter is left unconfigured
String configResolverClass = context.getServletContext().getInitParameter("keycloak.config.resolver");
if (configResolverClass != null) {
try {
SamlConfigResolver configResolver = (SamlConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
deploymentContext = new SamlDeploymentContext(configResolver);
log.infov("Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
} catch (Exception ex) {
log.errorv("The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", configResolverClass, ex.getMessage());
deploymentContext = new SamlDeploymentContext(new DefaultSamlDeployment());
}
} else {
InputStream is = getConfigInputStream(context);
final SamlDeployment deployment;
if (is == null) {
log.error("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
deployment = new DefaultSamlDeployment();
} else {
try {
ResourceLoader loader = new ResourceLoader() {
@Override
public InputStream getResourceAsStream(String resource) {
return context.getServletContext().getResourceAsStream(resource);
}
};
deployment = new DeploymentBuilder().build(is, loader);
} catch (ParsingException e) {
throw new RuntimeException(e);
}
}
deploymentContext = new SamlDeploymentContext(deployment);
log.debug("Keycloak is using a per-deployment configuration.");
}
context.getServletContext().setAttribute(SamlDeploymentContext.class.getName(), deploymentContext);
addTokenStoreUpdaters();
}
protected void beforeStop() {
}
private static InputStream getConfigFromServletContext(ServletContext servletContext) {
String xml = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
if (xml == null) {
return null;
}
log.trace("**** using " + AdapterConstants.AUTH_DATA_PARAM_NAME);
return new ByteArrayInputStream(xml.getBytes());
}
private static InputStream getConfigInputStream(Context context) {
InputStream is = getConfigFromServletContext(context.getServletContext());
if (is == null) {
String path = context.getServletContext().getInitParameter("keycloak.config.file");
if (path == null) {
log.trace("**** using /WEB-INF/keycloak-saml.xml");
is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak-saml.xml");
} else {
try {
is = new FileInputStream(path);
} catch (FileNotFoundException e) {
log.errorv("NOT FOUND {0}", path);
throw new RuntimeException(e);
}
}
}
return is;
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
log.trace("*********************** SAML ************");
CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request);
SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
if (request.getRequestURI().substring(request.getContextPath().length()).endsWith("/saml")) {
if (deployment != null && deployment.isConfigured()) {
SamlSessionStore tokenStore = getSessionStore(request, facade, deployment);
SamlAuthenticator authenticator = new CatalinaSamlEndpoint(facade, deployment, tokenStore);
executeAuthenticator(request, response, facade, deployment, authenticator);
return;
}
}
try {
getSessionStore(request, facade, deployment).isLoggedIn(); // sets request UserPrincipal if logged in. we do this so that the UserPrincipal is available on unsecured, unconstrainted URLs
super.invoke(request, response);
} finally {
}
}
protected abstract PrincipalFactory createPrincipalFactory();
protected abstract boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException;
private static final Pattern PROTOCOL_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9+.-]*:");
protected void forwardToLogoutPage(Request request, HttpServletResponse response, SamlDeployment deployment) {
final String location = deployment.getLogoutPage();
try {
//make sure the login page is never cached
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
if (location == null) {
log.warn("Logout page not set.");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} else if (PROTOCOL_PATTERN.matcher(location).find()) {
response.sendRedirect(response.encodeRedirectURL(location));
} else {
RequestDispatcher disp = request.getRequestDispatcher(location);
disp.forward(request.getRequest(), response);
}
} catch (ServletException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected boolean authenticateInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
log.trace("authenticateInternal");
CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request);
SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment == null || !deployment.isConfigured()) {
log.trace("deployment not configured");
return false;
}
SamlSessionStore tokenStore = getSessionStore(request, facade, deployment);
SamlAuthenticator authenticator = new CatalinaSamlAuthenticator(facade, deployment, tokenStore);
return executeAuthenticator(request, response, facade, deployment, authenticator);
}
protected boolean executeAuthenticator(Request request, HttpServletResponse response, CatalinaHttpFacade facade, SamlDeployment deployment, SamlAuthenticator authenticator) {
AuthOutcome outcome = authenticator.authenticate();
if (outcome == AuthOutcome.AUTHENTICATED) {
log.trace("AUTHENTICATED");
if (facade.isEnded()) {
return false;
}
return true;
}
if (outcome == AuthOutcome.LOGGED_OUT) {
logoutInternal(request);
if (deployment.getLogoutPage() != null) {
forwardToLogoutPage(request, response, deployment);
}
log.trace("Logging OUT");
return false;
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
log.trace("challenge");
challenge.challenge(facade);
}
return false;
}
public void keycloakSaveRequest(Request request) throws IOException {
saveRequest(request, request.getSessionInternal(true));
}
public boolean keycloakRestoreRequest(Request request) {
try {
return restoreRequest(request, request.getSessionInternal());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected SamlSessionStore getSessionStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
SamlSessionStore store = (SamlSessionStore)request.getNote(TOKEN_STORE_NOTE);
if (store != null) {
return store;
}
store = createSessionStore(request, facade, resolvedDeployment);
request.setNote(TOKEN_STORE_NOTE, store);
return store;
}
protected SamlSessionStore createSessionStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
SamlSessionStore store;
store = new CatalinaSamlSessionStore(userSessionManagement, createPrincipalFactory(), mapper, idMapperUpdater, request, this, facade, resolvedDeployment);
return store;
}
protected void addTokenStoreUpdaters() {
SessionIdMapperUpdater updater = getIdMapperUpdater();
try {
String idMapperSessionUpdaterClasses = context.getServletContext().getInitParameter("keycloak.sessionIdMapperUpdater.classes");
if (idMapperSessionUpdaterClasses == null) {
return;
}
for (String clazz : idMapperSessionUpdaterClasses.split("\\s*,\\s*")) {
if (! clazz.isEmpty()) {
updater = invokeAddTokenStoreUpdaterMethod(clazz, updater);
}
}
} finally {
setIdMapperUpdater(updater);
}
}
private SessionIdMapperUpdater invokeAddTokenStoreUpdaterMethod(String idMapperSessionUpdaterClass, SessionIdMapperUpdater previousIdMapperUpdater) {
try {
Class<?> clazz = context.getLoader().getClassLoader().loadClass(idMapperSessionUpdaterClass);
Method addTokenStoreUpdatersMethod = clazz.getMethod("addTokenStoreUpdaters", Context.class, SessionIdMapper.class, SessionIdMapperUpdater.class);
if (! Modifier.isStatic(addTokenStoreUpdatersMethod.getModifiers())
|| ! Modifier.isPublic(addTokenStoreUpdatersMethod.getModifiers())
|| ! SessionIdMapperUpdater.class.isAssignableFrom(addTokenStoreUpdatersMethod.getReturnType())) {
log.errorv("addTokenStoreUpdaters method in class {0} has to be public static. Ignoring class.", idMapperSessionUpdaterClass);
return previousIdMapperUpdater;
}
log.debugv("Initializing sessionIdMapperUpdater class {0}", idMapperSessionUpdaterClass);
return (SessionIdMapperUpdater) addTokenStoreUpdatersMethod.invoke(null, context, mapper, previousIdMapperUpdater);
} catch (ClassNotFoundException ex) {
log.warnv(ex, "Cannot use sessionIdMapperUpdater class {0}", idMapperSessionUpdaterClass);
return previousIdMapperUpdater;
} catch (NoSuchMethodException ex) {
log.warnv(ex, "Cannot use sessionIdMapperUpdater class {0}", idMapperSessionUpdaterClass);
return previousIdMapperUpdater;
} catch (SecurityException ex) {
log.warnv(ex, "Cannot use sessionIdMapperUpdater class {0}", idMapperSessionUpdaterClass);
return previousIdMapperUpdater;
} catch (IllegalAccessException ex) {
log.warnv(ex, "Cannot use {0}.addTokenStoreUpdaters(DeploymentInfo, SessionIdMapper) method", idMapperSessionUpdaterClass);
return previousIdMapperUpdater;
} catch (IllegalArgumentException ex) {
log.warnv(ex, "Cannot use {0}.addTokenStoreUpdaters(DeploymentInfo, SessionIdMapper) method", idMapperSessionUpdaterClass);
return previousIdMapperUpdater;
} catch (InvocationTargetException ex) {
log.warnv(ex, "Cannot use {0}.addTokenStoreUpdaters(DeploymentInfo, SessionIdMapper) method", idMapperSessionUpdaterClass);
return previousIdMapperUpdater;
}
}
public SessionIdMapperUpdater getIdMapperUpdater() {
return idMapperUpdater;
}
public void setIdMapperUpdater(SessionIdMapperUpdater idMapperUpdater) {
this.idMapperUpdater = idMapperUpdater;
}
}

View file

@ -1,43 +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.saml;
import org.keycloak.adapters.saml.profile.SamlAuthenticationHandler;
import org.keycloak.adapters.saml.profile.webbrowsersso.BrowserHandler;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CatalinaSamlAuthenticator extends SamlAuthenticator {
public CatalinaSamlAuthenticator(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
super(facade, deployment, sessionStore);
}
@Override
protected void completeAuthentication(SamlSession account) {
// complete
}
@Override
protected SamlAuthenticationHandler createBrowserHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
return new BrowserHandler(facade, deployment, sessionStore);
}
}

View file

@ -1,44 +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.saml;
import org.keycloak.adapters.saml.profile.SamlAuthenticationHandler;
import org.keycloak.adapters.saml.profile.webbrowsersso.SamlEndpoint;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CatalinaSamlEndpoint extends SamlAuthenticator {
public CatalinaSamlEndpoint(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
super(facade, deployment, sessionStore);
}
@Override
protected void completeAuthentication(SamlSession account) {
// complete
}
@Override
protected SamlAuthenticationHandler createBrowserHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
return new SamlEndpoint(facade, deployment, sessionStore);
}
}

View file

@ -1,249 +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.saml;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.realm.GenericPrincipal;
import org.jboss.logging.Logger;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.SessionIdMapper;
import org.keycloak.adapters.spi.SessionIdMapperUpdater;
import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement;
import org.keycloak.adapters.tomcat.PrincipalFactory;
import org.keycloak.common.util.KeycloakUriBuilder;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CatalinaSamlSessionStore implements SamlSessionStore {
protected static Logger log = Logger.getLogger(SamlSessionStore.class);
public static final String SAML_REDIRECT_URI = "SAML_REDIRECT_URI";
private final CatalinaUserSessionManagement sessionManagement;
protected final PrincipalFactory principalFactory;
private final SessionIdMapper idMapper;
private final SessionIdMapperUpdater idMapperUpdater;
protected final Request request;
protected final AbstractSamlAuthenticatorValve valve;
protected final HttpFacade facade;
protected final SamlDeployment deployment;
public CatalinaSamlSessionStore(CatalinaUserSessionManagement sessionManagement, PrincipalFactory principalFactory,
SessionIdMapper idMapper, SessionIdMapperUpdater idMapperUpdater,
Request request, AbstractSamlAuthenticatorValve valve, HttpFacade facade,
SamlDeployment deployment) {
this.sessionManagement = sessionManagement;
this.principalFactory = principalFactory;
this.idMapper = idMapper;
this.idMapperUpdater = idMapperUpdater;
this.request = request;
this.valve = valve;
this.facade = facade;
this.deployment = deployment;
}
@Override
public void setCurrentAction(CurrentAction action) {
if (action == CurrentAction.NONE && request.getSession(false) == null) return;
request.getSession().setAttribute(CURRENT_ACTION, action);
}
@Override
public boolean isLoggingIn() {
HttpSession session = request.getSession(false);
if (session == null) return false;
CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
return action == CurrentAction.LOGGING_IN;
}
@Override
public boolean isLoggingOut() {
HttpSession session = request.getSession(false);
if (session == null) return false;
CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
return action == CurrentAction.LOGGING_OUT;
}
@Override
public void logoutAccount() {
Session sessionInternal = request.getSessionInternal(false);
if (sessionInternal == null) return;
HttpSession session = sessionInternal.getSession();
List<String> ids = new LinkedList<String>();
if (session != null) {
SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
if (samlSession != null) {
if (samlSession.getSessionIndex() != null) {
ids.add(session.getId());
idMapperUpdater.removeSession(idMapper, session.getId());
}
session.removeAttribute(SamlSession.class.getName());
}
session.removeAttribute(SAML_REDIRECT_URI);
}
sessionInternal.setPrincipal(null);
sessionInternal.setAuthType(null);
logoutSessionIds(ids);
}
@Override
public void logoutByPrincipal(String principal) {
Set<String> sessions = idMapper.getUserSessions(principal);
if (sessions != null) {
List<String> ids = new LinkedList<String>();
ids.addAll(sessions);
logoutSessionIds(ids);
for (String id : ids) {
idMapperUpdater.removeSession(idMapper, id);
}
}
}
@Override
public void logoutBySsoId(List<String> ssoIds) {
if (ssoIds == null) return;
List<String> sessionIds = new LinkedList<String>();
for (String id : ssoIds) {
String sessionId = idMapper.getSessionFromSSO(id);
if (sessionId != null) {
sessionIds.add(sessionId);
idMapperUpdater.removeSession(idMapper, sessionId);
}
}
logoutSessionIds(sessionIds);
}
protected void logoutSessionIds(List<String> sessionIds) {
if (sessionIds == null || sessionIds.isEmpty()) return;
Manager sessionManager = request.getContext().getManager();
sessionManagement.logoutHttpSessions(sessionManager, sessionIds);
}
@Override
public boolean isLoggedIn() {
Session session = request.getSessionInternal(false);
if (session == null) {
log.debug("session was null, returning null");
return false;
}
final SamlSession samlSession = SamlUtil.validateSamlSession(session.getSession().getAttribute(SamlSession.class.getName()), deployment);
if (samlSession == null) {
return false;
}
GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
// in clustered environment in JBossWeb, principal is not serialized or saved
if (principal == null) {
principal = principalFactory.createPrincipal(request.getContext().getRealm(), samlSession.getPrincipal(), samlSession.getRoles());
session.setPrincipal(principal);
session.setAuthType("KEYCLOAK-SAML");
}
else if (samlSession.getPrincipal().getName().equals(principal.getName())){
if (!principal.getUserPrincipal().getName().equals(samlSession.getPrincipal().getName())) {
throw new RuntimeException("Unknown State");
}
log.debug("************principal already in");
if (log.isDebugEnabled()) {
for (String role : principal.getRoles()) {
log.debug("principal role: " + role);
}
}
}
request.setUserPrincipal(principal);
request.setAuthType("KEYCLOAK-SAML");
restoreRequest();
return true;
}
@Override
public void saveAccount(SamlSession account) {
Session session = request.getSessionInternal(true);
session.getSession().setAttribute(SamlSession.class.getName(), account);
GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
// in clustered environment in JBossWeb, principal is not serialized or saved
if (principal == null) {
principal = principalFactory.createPrincipal(request.getContext().getRealm(), account.getPrincipal(), account.getRoles());
session.setPrincipal(principal);
session.setAuthType("KEYCLOAK-SAML");
}
request.setUserPrincipal(principal);
request.setAuthType("KEYCLOAK-SAML");
String newId = changeSessionId(session);
idMapperUpdater.map(idMapper, account.getSessionIndex(), account.getPrincipal().getSamlSubject(), newId);
}
protected String changeSessionId(Session session) {
return session.getId();
}
@Override
public SamlSession getAccount() {
HttpSession session = getSession(true);
return (SamlSession)session.getAttribute(SamlSession.class.getName());
}
@Override
public String getRedirectUri() {
String redirect = (String)getSession(true).getAttribute(SAML_REDIRECT_URI);
if (redirect == null) {
String contextPath = request.getContextPath();
String baseUri = KeycloakUriBuilder.fromUri(request.getRequestURL().toString()).replacePath(contextPath).build().toString();
return SamlUtil.getRedirectTo(facade, contextPath, baseUri);
}
return redirect;
}
@Override
public void saveRequest() {
try {
valve.keycloakSaveRequest(request);
} catch (IOException e) {
throw new RuntimeException(e);
}
getSession(true).setAttribute(SAML_REDIRECT_URI, facade.getRequest().getURI());
}
@Override
public boolean restoreRequest() {
getSession(true).removeAttribute(SAML_REDIRECT_URI);
return valve.keycloakRestoreRequest(request);
}
protected HttpSession getSession(boolean create) {
Session session = request.getSessionInternal(create);
if (session == null) return null;
return session.getSession();
}
}

View file

@ -1,93 +0,0 @@
/*
* Copyright 2017 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.saml;
import org.keycloak.adapters.spi.SessionIdMapper;
import java.util.Objects;
import javax.servlet.http.*;
/**
*
* @author hmlnarik
*/
public class IdMapperUpdaterSessionListener implements HttpSessionListener, HttpSessionAttributeListener {
private final SessionIdMapper idMapper;
public IdMapperUpdaterSessionListener(SessionIdMapper idMapper) {
this.idMapper = idMapper;
}
@Override
public void sessionCreated(HttpSessionEvent hse) {
HttpSession session = hse.getSession();
Object value = session.getAttribute(SamlSession.class.getName());
map(session.getId(), value);
}
@Override
public void sessionDestroyed(HttpSessionEvent hse) {
HttpSession session = hse.getSession();
unmap(session.getId(), session.getAttribute(SamlSession.class.getName()));
}
@Override
public void attributeAdded(HttpSessionBindingEvent hsbe) {
HttpSession session = hsbe.getSession();
if (Objects.equals(hsbe.getName(), SamlSession.class.getName())) {
map(session.getId(), hsbe.getValue());
}
}
@Override
public void attributeRemoved(HttpSessionBindingEvent hsbe) {
HttpSession session = hsbe.getSession();
if (Objects.equals(hsbe.getName(), SamlSession.class.getName())) {
unmap(session.getId(), hsbe.getValue());
}
}
@Override
public void attributeReplaced(HttpSessionBindingEvent hsbe) {
HttpSession session = hsbe.getSession();
if (Objects.equals(hsbe.getName(), SamlSession.class.getName())) {
unmap(session.getId(), hsbe.getValue());
map(session.getId(), session.getAttribute(SamlSession.class.getName()));
}
}
private void map(String sessionId, Object value) {
if (! (value instanceof SamlSession) || sessionId == null) {
return;
}
SamlSession account = (SamlSession) value;
idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), sessionId);
}
private void unmap(String sessionId, Object value) {
if (! (value instanceof SamlSession) || sessionId == null) {
return;
}
SamlSession samlSession = (SamlSession) value;
if (samlSession.getSessionIndex() != null) {
idMapper.removeSession(sessionId);
}
}
}

View file

@ -1,89 +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-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>999.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-saml-tomcat-adapter</artifactId>
<name>Keycloak Tomcat SAML Integration</name>
<description />
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>commons-logging-jboss-logging</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat8.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>${tomcat8.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-tomcat-adapter-core</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>catalina</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -1,104 +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.saml.tomcat;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.tomcat.util.descriptor.web.LoginConfig;
import org.keycloak.adapters.saml.AbstractSamlAuthenticatorValve;
import org.keycloak.adapters.saml.SamlDeployment;
import org.keycloak.adapters.saml.SamlSessionStore;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.List;
/**
* Keycloak authentication valve
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SamlAuthenticatorValve extends AbstractSamlAuthenticatorValve {
/**
* Method called by Tomcat &lt; 8.5.5
*/
@Override
public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
return authenticateInternal(request, response, request.getContext().getLoginConfig());
}
/**
* Method called by Tomcat &gt;= 8.5.5
*/
@Override
protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException {
return this.authenticate(request, response);
}
@Override
protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
if (loginConfig == null) return false;
LoginConfig config = (LoginConfig)loginConfig;
if (config.getErrorPage() == null) return false;
// had to do this to get around compiler/IDE issues :(
try {
Method method = FormAuthenticator.class.getDeclaredMethod("forwardToErrorPage", Request.class, HttpServletResponse.class, LoginConfig.class);
method.setAccessible(true);
method.invoke(this, request, response, config);
} catch (Exception e) {
throw new RuntimeException(e);
}
return true;
}
@Override
protected void initInternal() {
StandardContext standardContext = (StandardContext) context;
standardContext.addLifecycleListener(this);
}
@Override
public void logout(Request request) {
logoutInternal(request);
}
@Override
protected GenericPrincipalFactory createPrincipalFactory() {
return new GenericPrincipalFactory() {
@Override
protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) {
return new GenericPrincipal(userPrincipal.getName(), null, roles, userPrincipal, null);
}
};
}
@Override
protected SamlSessionStore createSessionStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
SamlSessionStore store;
store = new TomcatSamlSessionStore(userSessionManagement, createPrincipalFactory(), mapper, request, this, facade, resolvedDeployment);
return store;
}
}

View file

@ -1,46 +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.saml.tomcat;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.keycloak.adapters.saml.AbstractSamlAuthenticatorValve;
import org.keycloak.adapters.saml.CatalinaSamlSessionStore;
import org.keycloak.adapters.saml.SamlDeployment;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.SessionIdMapper;
import org.keycloak.adapters.spi.SessionIdMapperUpdater;
import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement;
import org.keycloak.adapters.tomcat.PrincipalFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class TomcatSamlSessionStore extends CatalinaSamlSessionStore {
public TomcatSamlSessionStore(CatalinaUserSessionManagement sessionManagement, PrincipalFactory principalFactory, SessionIdMapper idMapper, Request request, AbstractSamlAuthenticatorValve valve, HttpFacade facade, SamlDeployment deployment) {
super(sessionManagement, principalFactory, idMapper, SessionIdMapperUpdater.DIRECT, request, valve, facade, deployment);
}
@Override
protected String changeSessionId(Session session) {
Request request = this.request;
if (!deployment.turnOffChangeSessionIdOnLogin()) return request.changeSessionId();
else return session.getId();
}
}

View file

@ -3,8 +3,6 @@ mvn:keycloak-api-docs-dist:keycloak-api-docs
mvn:keycloak-tomcat-adapter-dist:keycloak-oidc-tomcat-adapter mvn:keycloak-tomcat-adapter-dist:keycloak-oidc-tomcat-adapter
mvn:keycloak-saml-tomcat-adapter-dist:keycloak-saml-tomcat-adapter
mvn:documentation/keycloak-documentation:keycloak-documentation mvn:documentation/keycloak-documentation:keycloak-documentation
npm:js/libs/keycloak-admin-client/target/keycloak-keycloak-admin-client-$$VERSION$$.tgz:keycloak-admin-client-$$VERSION$$.tgz npm:js/libs/keycloak-admin-client/target/keycloak-keycloak-admin-client-$$VERSION$$.tgz:keycloak-admin-client-$$VERSION$$.tgz

View file

@ -1,42 +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.
-->
<assembly>
<id>war-dist</id>
<formats>
<format>zip</format>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<unpack>false</unpack>
<useTransitiveDependencies>true</useTransitiveDependencies>
<useTransitiveFiltering>true</useTransitiveFiltering>
<includes>
<include>org.keycloak:keycloak-saml-tomcat-adapter</include>
</includes>
<excludes>
<exclude>org.apache.tomcat:tomcat-servlet-api</exclude>
<exclude>org.apache.tomcat:tomcat-catalina</exclude>
</excludes>
<outputDirectory></outputDirectory>
</dependencySet>
</dependencySets>
</assembly>

View file

@ -1,68 +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.
-->
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>999.0.0-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<artifactId>keycloak-saml-tomcat-adapter-dist</artifactId>
<packaging>pom</packaging>
<name>Keycloak SAML Tomcat Adapter Distro</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-tomcat-adapter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<outputDirectory>
target
</outputDirectory>
<workDirectory>
target/assembly/work
</workDirectory>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -26,8 +26,7 @@ please take a look at link:{upgradingguide_link_latest}[{upgradingguide_name}].
The `SameSite` value `None` for `JSESSIONID` cookie is necessary for correct behavior of the {project_name} SAML adapter. The `SameSite` value `None` for `JSESSIONID` cookie is necessary for correct behavior of the {project_name} SAML adapter.
Usage of a different value is causing resetting of the container's session with each request to {project_name}, when Usage of a different value is causing resetting of the container's session with each request to {project_name}, when
the SAML POST binging is used. Refer to the following steps for the SAML POST binging is used. Refer to the following steps for
link:{adapterguide_link}#_saml-jboss-adapter-samesite-setting[Wildfly] and link:{adapterguide_link}#_saml-jboss-adapter-samesite-setting[Wildfly] to keep the correct behavior. Notice, that this
link:{adapterguide_link}#_saml-tomcat-adapter-samesite-setting[Tomcat] to keep the correct behavior. Notice, that this
workaround should be working also with the previous versions of the adapter. workaround should be working also with the previous versions of the adapter.
== Other improvements == Other improvements

View file

@ -51,7 +51,6 @@ endif::[]
* <<_saml_jboss_adapter,JBoss EAP>> * <<_saml_jboss_adapter,JBoss EAP>>
ifeval::[{project_community}==true] ifeval::[{project_community}==true]
* <<_saml_jboss_adapter,WildFly>> * <<_saml_jboss_adapter,WildFly>>
* <<_saml-tomcat-adapter,Tomcat>>
endif::[] endif::[]
ifeval::[{project_community}==true] ifeval::[{project_community}==true]
* <<_java-servlet-filter-adapter,Servlet filter>> * <<_java-servlet-filter-adapter,Servlet filter>>

View file

@ -23,10 +23,6 @@ include::jboss-adapter/jboss-adapter-samesite-setting.adoc[]
include::jboss-adapter/required_per_war_configuration.adoc[] include::jboss-adapter/required_per_war_configuration.adoc[]
include::jboss-adapter/securing_wars.adoc[] include::jboss-adapter/securing_wars.adoc[]
ifeval::[{project_community}==true] ifeval::[{project_community}==true]
include::tomcat-adapter.adoc[]
include::tomcat-adapter/tomcat_adapter_installation.adoc[]
include::tomcat-adapter/tomcat_adapter_per_war_config.adoc[]
include::tomcat-adapter/tomcat-adapter-samesite-setting.adoc[]
endif::[] endif::[]
include::servlet-filter-adapter.adoc[] include::servlet-filter-adapter.adoc[]

View file

@ -1,10 +0,0 @@
[[_saml-tomcat-adapter]]
==== Tomcat SAML adapters
WARNING: The {project_name} Tomcat SAML adapter is deprecated. We recommend that you use another client adapter if possible.
To be able to secure WAR apps deployed on Tomcat 8 or 9 you must install the Keycloak Tomcat SAML adapter into your Tomcat installation.
You then have to provide some extra configuration in each WAR you deploy to Tomcat.

View file

@ -1,24 +0,0 @@
[[_saml-tomcat-adapter-samesite-setting]]
===== Setting SameSite value for JSESSIONID cookie
Browsers are planning to set the default value for the `SameSite` attribute for cookies to `Lax`. This setting means
that cookies will be sent to applications only if the request originates in the same domain. This behavior can affect
the SAML POST binding which may become non-functional. To preserve full functionality of the SAML adapter, we recommend
setting the `SameSite` value to `None` for the `JSESSIONID` cookie created by your container. Not doing so may result in
resetting the container's session with each request to {project_name}.
NOTE: To avoid setting the `SameSite` attribute to `None`, consider switching to the REDIRECT binding
if it is acceptable, or to OIDC protocol where this workaround is not necessary.
To set the `SameSite` value to `None` for `JSESSIONID` cookie in Tomcat add following configuration to the`context.xml`
of your application. Note, this will set the `SameSite` value to `None` for all cookies created by Tomcat container.
[source,xml]
----
<CookieProcessor sameSiteCookies="None" />
----
WARNING: It is not possible to set the `SameSite` attribute only to a subset of cookies, therefore all cookies created
for your application will have this attribute set to `None`.
The support for this feature is available in Tomcat from versions 9.0.29 and 8.5.49.

View file

@ -1,26 +0,0 @@
[[_saml-tomcat-adapter-installation]]
===== Installing the adapter
Adapters are no longer included with the appliance or war distribution.
Each adapter is a separate download on the Keycloak Downloads site.
They are also available as a maven artifact.
.Procedure
. Download the adapter for the Tomcat version on your system from the link:https://www.keycloak.org/downloads[Keycloak Downloads] site:
. Install on the Tomcat version on your system:
* Install on Tomcat 8 or 9:
+
[source]
----
$ cd $TOMCAT_HOME/lib
$ unzip keycloak-saml-tomcat-adapter-dist.zip
----
====
[NOTE]
Including the adapter's jars within your WEB-INF/lib directory will not work. The Keycloak SAML adapter is implemented as a Valve and valve code must reside in Tomcat's main lib/ directory.
====

View file

@ -1,57 +0,0 @@
===== Securing a WAR
Use this procedure to secure a WAR directly by adding config and editing files within your WAR package.
.Procedure
. Create a `META-INF/context.xml` file in your WAR package.
This is a Tomcat specific config file and you must define a Keycloak specific Valve.
+
[source,xml]
----
<Context path="/your-context-path">
<Valve className="org.keycloak.adapters.saml.tomcat.SamlAuthenticatorValve"/>
</Context>
----
. Create a `keycloak-saml.xml` adapter config file within the `WEB-INF` directory of your WAR.
The format of this config file is described in the <<_saml-general-config,General Adapter Config>> section.
. Specify both a `login-config` and use standard servlet security to specify role-base constraints on your URLs.
Here's an example:
+
[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>customer-portal</module-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>Customers</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>this is ignored currently</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>
----
If the `keycloak-saml.xml` does not explicitly set `assertionConsumerServiceUrl`, the SAML adapter will implicitly listen for SAML assertions at the location `/my-context-path/saml`. This has to match `Master SAML Processing URL` in the IDP realm/client settings, for example `\http://sp.domain.com/my-context-path/saml`. If not, Tomcat will probably redirect infinitely to the IDP login service, as it does not receive the SAML assertion after the user logged in.

16
pom.xml
View file

@ -1152,11 +1152,6 @@
<artifactId>keycloak-saml-as7-subsystem</artifactId> <artifactId>keycloak-saml-as7-subsystem</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-tomcat-adapter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat-adapter</artifactId> <artifactId>keycloak-tomcat-adapter</artifactId>
@ -1268,11 +1263,6 @@
<version>${project.version}</version> <version>${project.version}</version>
<type>zip</type> <type>zip</type>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-tomcat-adapter-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-jetty-adapter-core</artifactId> <artifactId>keycloak-saml-jetty-adapter-core</artifactId>
@ -1477,12 +1467,6 @@
<version>${project.version}</version> <version>${project.version}</version>
<type>zip</type> <type>zip</type>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-tomcat-adapter-dist</artifactId>
<version>${project.version}</version>
<type>zip</type>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-jetty94-adapter-dist</artifactId> <artifactId>keycloak-saml-jetty94-adapter-dist</artifactId>

View file

@ -37,7 +37,6 @@
<app.server.tomcat.unpacked.folder.name>apache-tomcat-${tomcat8.version}</app.server.tomcat.unpacked.folder.name> <app.server.tomcat.unpacked.folder.name>apache-tomcat-${tomcat8.version}</app.server.tomcat.unpacked.folder.name>
<app.server.oidc.adapter.artifactId>keycloak-tomcat-adapter-dist</app.server.oidc.adapter.artifactId> <app.server.oidc.adapter.artifactId>keycloak-tomcat-adapter-dist</app.server.oidc.adapter.artifactId>
<app.server.saml.adapter.artifactId>keycloak-saml-tomcat-adapter-dist</app.server.saml.adapter.artifactId>
<skip.dependencies.for.tomcat8>false</skip.dependencies.for.tomcat8> <skip.dependencies.for.tomcat8>false</skip.dependencies.for.tomcat8>
</properties> </properties>

View file

@ -37,7 +37,6 @@
<app.server.tomcat.unpacked.folder.name>apache-tomcat-${tomcat9.version}</app.server.tomcat.unpacked.folder.name> <app.server.tomcat.unpacked.folder.name>apache-tomcat-${tomcat9.version}</app.server.tomcat.unpacked.folder.name>
<app.server.oidc.adapter.artifactId>keycloak-tomcat-adapter-dist</app.server.oidc.adapter.artifactId> <app.server.oidc.adapter.artifactId>keycloak-tomcat-adapter-dist</app.server.oidc.adapter.artifactId>
<app.server.saml.adapter.artifactId>keycloak-saml-tomcat-adapter-dist</app.server.saml.adapter.artifactId>
<skip.dependencies.for.tomcat8>false</skip.dependencies.for.tomcat8> <skip.dependencies.for.tomcat8>false</skip.dependencies.for.tomcat8>
</properties> </properties>