error page adapter support

This commit is contained in:
Bill Burke
2014-12-23 16:33:08 -05:00
parent a280c11897
commit ec9ce6ef2f
26 changed files with 421 additions and 41 deletions

View File

@@ -11,4 +11,11 @@ public interface AuthChallenge {
* @return challenge sent
*/
boolean challenge(HttpFacade exchange);
/**
* Whether or not an error page should be displayed if possible
*
* @return
*/
boolean errorPage();
}

View File

@@ -109,6 +109,11 @@ public class BearerTokenRequestAuthenticator {
protected AuthChallenge clientCertChallenge() {
return new AuthChallenge() {
@Override
public boolean errorPage() {
return false;
}
@Override
public boolean challenge(HttpFacade exchange) {
// do the same thing as client cert auth
@@ -129,6 +134,11 @@ public class BearerTokenRequestAuthenticator {
}
final String challenge = header.toString();
return new AuthChallenge() {
@Override
public boolean errorPage() {
return false;
}
@Override
public boolean challenge(HttpFacade facade) {
facade.getResponse().setStatus(401);

View File

@@ -146,13 +146,29 @@ public class OAuthRequestAuthenticator {
protected AuthChallenge loginRedirect() {
final String state = getStateCode();
final String redirect = getRedirectUri(state);
return new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
if (redirect == null) {
if (redirect == null) {
return new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
exchange.getResponse().setStatus(403);
return true;
}
@Override
public boolean errorPage() {
return true;
}
};
}
return new AuthChallenge() {
@Override
public boolean errorPage() {
return false;
}
@Override
public boolean challenge(HttpFacade exchange) {
tokenStore.saveRequest();
log.debug("Sending redirect to login page: " + redirect);
exchange.getResponse().setStatus(302);
@@ -218,6 +234,11 @@ public class OAuthRequestAuthenticator {
protected AuthChallenge challenge(final int code) {
return new AuthChallenge() {
@Override
public boolean errorPage() {
return true;
}
@Override
public boolean challenge(HttpFacade exchange) {
exchange.getResponse().setStatus(code);

View File

@@ -11,6 +11,7 @@ import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.util.List;
@@ -22,9 +23,19 @@ import java.util.List;
*/
public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws java.io.IOException {
return authenticateInternal(request, response);
return authenticateInternal(request, response, config);
}
@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;
forwardToErrorPage(request, (Response)response, config);
return true;
}
@Override
public void start() throws LifecycleException {
StandardContext standardContext = (StandardContext) context;

View File

@@ -1,14 +1,18 @@
package org.keycloak.adapters.jetty;
import org.apache.http.HttpVersion;
import org.eclipse.jetty.security.DefaultUserIdentity;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.URIUtil;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
@@ -37,6 +41,7 @@ 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.util.HashSet;
import java.util.Set;
@@ -52,6 +57,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
protected NodesRegistrationManagement nodesRegistrationManagement;
protected AdapterConfig adapterConfig;
protected KeycloakConfigResolver configResolver;
protected String errorPage;
public AbstractKeycloakJettyAuthenticator() {
super();
@@ -66,7 +72,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
}
public AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
AdapterTokenStore store = (AdapterTokenStore)request.getAttribute(TOKEN_STORE_NOTE);
AdapterTokenStore store = (AdapterTokenStore) request.getAttribute(TOKEN_STORE_NOTE);
if (store != null) {
return store;
}
@@ -84,8 +90,8 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
public abstract AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment);
public void logoutCurrent(Request request) {
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext)request.getAttribute(AdapterDeploymentContext.class.getName());
KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getAttribute(AdapterDeploymentContext.class.getName());
KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
if (ksc != null) {
JettyHttpFacade facade = new JettyHttpFacade(request, null);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
@@ -115,11 +121,25 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
public void setConfiguration(AuthConfiguration configuration) {
//super.setConfiguration(configuration);
initializeKeycloak();
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
setErrorPage(error);
}
private void setErrorPage(String path) {
if (path == null || path.trim().length() == 0) {
} else {
if (!path.startsWith("/")) {
path = "/" + path;
}
errorPage = path;
if (errorPage.indexOf('?') > 0)
errorPage = errorPage.substring(0, errorPage.indexOf('?'));
}
}
@Override
public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication.User validatedUser) throws ServerAuthException
{
public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication.User validatedUser) throws ServerAuthException {
return true;
}
@@ -167,12 +187,13 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
InputStream configInputStream = getConfigInputStream(theServletContext);
if (configInputStream != null) {
deploymentContext = new AdapterDeploymentContext(KeycloakDeploymentBuilder.build(configInputStream));
}
}
}
if (deploymentContext == null) {
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
}
if (theServletContext != null) theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
if (theServletContext != null)
theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
}
private InputStream getConfigInputStream(ServletContext servletContext) {
@@ -198,7 +219,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
log.trace("*** authenticate");
}
Request request = resolveRequest(req);
JettyHttpFacade facade = new JettyHttpFacade(request, (HttpServletResponse)res);
JettyHttpFacade facade = new JettyHttpFacade(request, (HttpServletResponse) res);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment == null || !deployment.isConfigured()) {
log.debug("*** deployment isn't configured return false");
@@ -231,17 +252,25 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
if (challenge.errorPage() && errorPage != null) {
Response response = (Response)res;
try {
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), errorPage)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
challenge.challenge(facade);
}
return Authentication.SEND_CONTINUE;
}
protected abstract Request resolveRequest(ServletRequest req);
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
return new JettyRequestAuthenticator(facade, deployment, tokenStore, -1, request);
}
@@ -263,8 +292,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
protected abstract Authentication createAuthentication(UserIdentity userIdentity);
public static abstract class KeycloakAuthentication extends UserAuthentication
{
public static abstract class KeycloakAuthentication extends UserAuthentication {
public KeycloakAuthentication(String method, UserIdentity userIdentity) {
super(method, userIdentity);
}

View File

@@ -10,6 +10,7 @@ import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.authenticator.SavedRequest;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.tomcat.util.buf.ByteChunk;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.constants.AdapterConstants;
@@ -175,8 +176,9 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
}
protected abstract GenericPrincipalFactory createPrincipalFactory();
protected abstract boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException;
protected boolean authenticateInternal(Request request, HttpServletResponse response) {
protected boolean authenticateInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment == null || !deployment.isConfigured()) {
@@ -196,6 +198,12 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
if (loginConfig == null) {
loginConfig = request.getContext().getLoginConfig();
}
if (challenge.errorPage()) {
if (forwardToErrorPageInternal(request, response, loginConfig))return false;
}
challenge.challenge(facade);
}
return false;

View File

@@ -8,6 +8,8 @@ import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.realm.GenericPrincipal;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.util.List;
@@ -20,9 +22,19 @@ import java.util.List;
public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
@Override
public boolean authenticate(Request request, Response response, LoginConfig config) throws java.io.IOException {
return authenticateInternal(request, response);
return authenticateInternal(request, response, config);
}
@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;
forwardToErrorPage(request, (Response)response, config);
return true;
}
@Override
public void start() throws LifecycleException {
StandardContext standardContext = (StandardContext) context;

View File

@@ -1,6 +1,7 @@
package org.keycloak.adapters.tomcat;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.realm.GenericPrincipal;
@@ -19,9 +20,19 @@ import java.util.List;
*/
public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException {
return authenticateInternal(request, response);
return authenticateInternal(request, response, config);
}
@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;
forwardToErrorPage(request, (Response)response, config);
return true;
}
protected void initInternal() {
StandardContext standardContext = (StandardContext) context;
standardContext.addLifecycleListener(this);

View File

@@ -32,6 +32,19 @@
<artifactId>keycloak-adapter-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat-core-adapter</artifactId>

View File

@@ -1,12 +1,19 @@
package org.keycloak.adapters.tomcat;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.descriptor.web.LoginConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.List;
@@ -18,7 +25,32 @@ import java.util.List;
*/
public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
return authenticateInternal(request, response);
return authenticateInternal(request, response, request.getContext().getLoginConfig());
}
@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 = null;
/*
for (Method m : getClass().getDeclaredMethods()) {
if (m.getName().equals("forwardToErrorPage")) {
method = m;
break;
}
}
*/
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;
}
protected void initInternal() {

View File

@@ -23,7 +23,9 @@ import io.undertow.security.api.SecurityNotification;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.session.Session;
import io.undertow.util.AttachmentKey;
import io.undertow.util.Headers;
import io.undertow.util.Sessions;
import io.undertow.util.StatusCodes;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext;
@@ -46,16 +48,22 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
protected AdapterDeploymentContext deploymentContext;
protected UndertowUserSessionManagement sessionManagement;
protected String errorPage;
public AbstractUndertowKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement) {
public AbstractUndertowKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement, String errorPage) {
this.deploymentContext = deploymentContext;
this.sessionManagement = sessionManagement;
this.errorPage = errorPage;
}
@Override
public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
if (challenge != null) {
if (challenge.errorPage() && errorPage != null) {
Integer code = servePage(exchange, errorPage);
return new ChallengeResult(true, code);
}
UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
if (challenge.challenge(facade)) {
return new ChallengeResult(true, exchange.getResponseCode());
@@ -64,6 +72,19 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
return new ChallengeResult(false);
}
protected Integer servePage(final HttpServerExchange exchange, final String location) {
sendRedirect(exchange, location);
return StatusCodes.TEMPORARY_REDIRECT;
}
static void sendRedirect(final HttpServerExchange exchange, final String location) {
// TODO - String concatenation to construct URLS is extremely error prone - switch to a URI which will better handle this.
String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + location;
exchange.getResponseHeaders().put(Headers.LOCATION, loc);
}
protected void registerNotifications(final SecurityContext securityContext) {
final NotificationReceiver logoutReceiver = new NotificationReceiver() {

View File

@@ -32,11 +32,12 @@ import io.undertow.servlet.api.LoginConfig;
import io.undertow.servlet.api.ServletSessionConfig;
import io.undertow.servlet.util.ImmediateInstanceHandle;
import org.jboss.logging.Logger;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.constants.AdapterConstants;
import javax.servlet.ServletContext;
import java.io.ByteArrayInputStream;
@@ -44,7 +45,6 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Map;
import org.keycloak.adapters.KeycloakConfigResolver;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -57,9 +57,9 @@ public class KeycloakServletExtension implements ServletExtension {
// todo when this DeploymentInfo method of the same name is fixed.
public boolean isAuthenticationMechanismPresent(DeploymentInfo deploymentInfo, final String mechanismName) {
LoginConfig loginConfig = deploymentInfo.getLoginConfig();
if(loginConfig != null) {
for(AuthMethodConfig method : loginConfig.getAuthMethods()) {
if(method.getName().equalsIgnoreCase(mechanismName)) {
if (loginConfig != null) {
for (AuthMethodConfig method : loginConfig.getAuthMethods()) {
if (method.getName().equalsIgnoreCase(mechanismName)) {
return true;
}
}
@@ -191,7 +191,17 @@ public class KeycloakServletExtension implements ServletExtension {
protected ServletKeycloakAuthMech createAuthenticationMechanism(DeploymentInfo deploymentInfo, AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement,
NodesRegistrationManagement nodesRegistrationManagement) {
log.debug("creating ServletKeycloakAuthMech");
return new ServletKeycloakAuthMech(deploymentContext, userSessionManagement, nodesRegistrationManagement, deploymentInfo.getConfidentialPortManager());
log.debug("creating ServletKeycloakAuthMech");
String errorPage = getErrorPage(deploymentInfo);
return new ServletKeycloakAuthMech(deploymentContext, userSessionManagement, nodesRegistrationManagement, deploymentInfo.getConfidentialPortManager(), errorPage);
}
protected String getErrorPage(DeploymentInfo deploymentInfo) {
LoginConfig loginConfig = deploymentInfo.getLoginConfig();
String errorPage = null;
if (loginConfig != null) {
errorPage = loginConfig.getErrorPage();
}
return errorPage;
}
}

View File

@@ -16,18 +16,28 @@
*/
package org.keycloak.adapters.undertow;
import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.api.ConfidentialPortManager;
import io.undertow.servlet.handlers.ServletRequestContext;
import io.undertow.util.Headers;
import org.jboss.logging.Logger;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.enums.TokenStore;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
@@ -39,12 +49,36 @@ public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech {
protected NodesRegistrationManagement nodesRegistrationManagement;
protected ConfidentialPortManager portManager;
public ServletKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement, NodesRegistrationManagement nodesRegistrationManagement, ConfidentialPortManager portManager) {
super(deploymentContext, userSessionManagement);
public ServletKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement,
NodesRegistrationManagement nodesRegistrationManagement, ConfidentialPortManager portManager,
String errorPage) {
super(deploymentContext, userSessionManagement, errorPage);
this.nodesRegistrationManagement = nodesRegistrationManagement;
this.portManager = portManager;
}
@Override
protected Integer servePage(HttpServerExchange exchange, String location) {
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
ServletRequest req = servletRequestContext.getServletRequest();
ServletResponse resp = servletRequestContext.getServletResponse();
RequestDispatcher disp = req.getRequestDispatcher(location);
//make sure the login page is never cached
exchange.getResponseHeaders().add(Headers.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
exchange.getResponseHeaders().add(Headers.PRAGMA, "no-cache");
exchange.getResponseHeaders().add(Headers.EXPIRES, "0");
try {
disp.forward(req, resp);
} catch (ServletException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
@Override
public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
UndertowHttpFacade facade = new UndertowHttpFacade(exchange);

View File

@@ -17,8 +17,8 @@ public class UndertowAuthenticationMechanism extends AbstractUndertowKeycloakAut
protected int confidentialPort;
public UndertowAuthenticationMechanism(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
NodesRegistrationManagement nodesRegistrationManagement, int confidentialPort) {
super(deploymentContext, sessionManagement);
NodesRegistrationManagement nodesRegistrationManagement, int confidentialPort, String errorPage) {
super(deploymentContext, sessionManagement, errorPage);
this.nodesRegistrationManagement = nodesRegistrationManagement;
this.confidentialPort = confidentialPort;
}

View File

@@ -21,8 +21,8 @@ public class WildflyAuthenticationMechanism extends ServletKeycloakAuthMech {
public WildflyAuthenticationMechanism(AdapterDeploymentContext deploymentContext,
UndertowUserSessionManagement userSessionManagement,
NodesRegistrationManagement nodesRegistrationManagement,
ConfidentialPortManager portManager) {
super(deploymentContext, userSessionManagement, nodesRegistrationManagement, portManager);
ConfidentialPortManager portManager, String errorPage) {
super(deploymentContext, userSessionManagement, nodesRegistrationManagement, portManager, errorPage);
}
@Override

View File

@@ -19,7 +19,7 @@ public class WildflyKeycloakServletExtension extends KeycloakServletExtension {
protected ServletKeycloakAuthMech createAuthenticationMechanism(DeploymentInfo deploymentInfo, AdapterDeploymentContext deploymentContext,
UndertowUserSessionManagement userSessionManagement, NodesRegistrationManagement nodesRegistrationManagement) {
log.debug("creating WildflyAuthenticationMechanism");
return new WildflyAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, deploymentInfo.getConfidentialPortManager());
return new WildflyAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, deploymentInfo.getConfidentialPortManager(), getErrorPage(deploymentInfo));
}
}

View File

@@ -229,7 +229,7 @@ public class ProxyServerBuilder {
handler = new ConstraintMatcherHandler(matches, handler, toWrap, errorPage);
final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>();
mechanisms.add(new CachedAuthenticatedSessionMechanism());
mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1));
mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1, null));
handler = new AuthenticationMechanismsHandler(handler, mechanisms);
IdentityManager identityManager = new IdentityManager() {
@Override

View File

@@ -224,6 +224,9 @@ public class AdapterTestStrategy extends ExternalResource {
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
driver.navigate().to(APP_SERVER_BASE_URL + "/customer-portal");
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
loginPage.cancel();
System.out.println(driver.getPageSource());
Assert.assertTrue(driver.getPageSource().contains("Error Page"));
}

View File

@@ -5,6 +5,7 @@ import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo;
import io.undertow.servlet.api.LoginConfig;
import io.undertow.servlet.api.SecurityConstraint;
import io.undertow.servlet.api.SecurityInfo;
import io.undertow.servlet.api.ServletInfo;
import io.undertow.servlet.api.WebResourceCollection;
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
@@ -126,6 +127,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
server.getServer().deploy(deploymentInfo);
}
private DeploymentInfo createDeploymentInfo(String name, String contextPath, Class<? extends Servlet> servletClass) {
DeploymentInfo deploymentInfo = new DeploymentInfo();
deploymentInfo.setClassLoader(getClass().getClassLoader());
@@ -168,11 +170,25 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
constraint.addRoleAllowed(role);
di.addSecurityConstraint(constraint);
}
LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo");
LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo", null, "/error.html");
di.setLoginConfig(loginConfig);
addErrorPage(di);
server.getServer().deploy(di);
}
public void addErrorPage(DeploymentInfo di) {
ServletInfo servlet = new ServletInfo("Error Page", ErrorServlet.class);
servlet.addMapping("/error.html");
SecurityConstraint constraint = new SecurityConstraint();
WebResourceCollection collection = new WebResourceCollection();
collection.addUrlPattern("/error.html");
constraint.addWebResourceCollection(collection);
constraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT);
di.addSecurityConstraint(constraint);
di.addServlet(servlet);
}
public void deployJaxrsApplication(String name, String contextPath, Class<? extends Application> applicationClass, Map<String,String> initParams) {
ResteasyDeployment deployment = new ResteasyDeployment();
deployment.setApplicationClass(applicationClass.getName());

View File

@@ -0,0 +1,28 @@
package org.keycloak.testsuite.rule;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ErrorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.printf("<html><head><title>%s</title></head><body>", "Error Page");
pw.print("<h1>There was an error</h1></body></html>");
pw.flush();
}
}

View File

@@ -10,12 +10,21 @@
<servlet-name>Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>
@@ -25,12 +34,23 @@
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Errors</web-resource-name>
<url-pattern>/error.html</url-pattern>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<auth-method>FORM</auth-method>
<realm-name>demo</realm-name>
<form-login-config>
<form-login-page>/error.html</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>

View File

@@ -10,12 +10,21 @@
<servlet-name>Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Errors</web-resource-name>
<url-pattern>/error.html</url-pattern>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<auth-method>FORM</auth-method>
<realm-name>demo</realm-name>
<form-login-config>
<form-login-page>/error.html</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>

View File

@@ -10,12 +10,21 @@
<servlet-name>Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Errors</web-resource-name>
<url-pattern>/error.html</url-pattern>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<auth-method>FORM</auth-method>
<realm-name>demo</realm-name>
<form-login-config>
<form-login-page>/error.html</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>

View File

@@ -10,12 +10,21 @@
<servlet-name>Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Errors</web-resource-name>
<url-pattern>/error.html</url-pattern>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>demo</realm-name>
<form-login-config>
<form-login-page>/error.html</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>

View File

@@ -10,12 +10,21 @@
<servlet-name>Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Errors</web-resource-name>
<url-pattern>/error.html</url-pattern>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>demo</realm-name>
<form-login-config>
<form-login-page>/error.html</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>

View File

@@ -10,12 +10,21 @@
<servlet-name>Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.adapter.CustomerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>
@@ -25,10 +34,20 @@
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Errors</web-resource-name>
<url-pattern>/error.html</url-pattern>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>demo</realm-name>
<form-login-config>
<form-login-page>/error.html</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>