sendError() handling'

This commit is contained in:
Bill Burke 2015-11-24 16:48:24 -05:00
parent b1deed67df
commit ff63c5552a
70 changed files with 1023 additions and 188 deletions

View file

@ -20,6 +20,7 @@
<!ENTITY SpringSecurityAdapter SYSTEM "modules/spring-security-adapter.xml"> <!ENTITY SpringSecurityAdapter SYSTEM "modules/spring-security-adapter.xml">
<!ENTITY InstalledApplications SYSTEM "modules/installed-applications.xml"> <!ENTITY InstalledApplications SYSTEM "modules/installed-applications.xml">
<!ENTITY Logout SYSTEM "modules/logout.xml"> <!ENTITY Logout SYSTEM "modules/logout.xml">
<!ENTITY ErrorHandling SYSTEM "modules/adapter_error_handling.xml">
<!ENTITY SAML SYSTEM "modules/saml.xml"> <!ENTITY SAML SYSTEM "modules/saml.xml">
<!ENTITY JAAS SYSTEM "modules/jaas.xml"> <!ENTITY JAAS SYSTEM "modules/jaas.xml">
<!ENTITY IdentityBroker SYSTEM "modules/identity-broker.xml"> <!ENTITY IdentityBroker SYSTEM "modules/identity-broker.xml">
@ -115,6 +116,7 @@ This one is short
&SpringSecurityAdapter; &SpringSecurityAdapter;
&InstalledApplications; &InstalledApplications;
&Logout; &Logout;
&ErrorHandling;
&MultiTenancy; &MultiTenancy;
&JAAS; &JAAS;
</chapter> </chapter>

View file

@ -83,6 +83,10 @@
<title>Migrating to 1.7.0.CR1</title> <title>Migrating to 1.7.0.CR1</title>
<simplesect> <simplesect>
<title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title> <title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title>
<para>
form-error-page in web.xml will no longer work for client adapter authentication errors. You must define an error-page for
the the various HTTP error codes. See documentation for more details if you want to catch and handle adapter error conditions.
</para>
<para> <para>
In this version, we added <literal>First Broker Login</literal>, which allows you to specify what exactly should be done In this version, we added <literal>First Broker Login</literal>, which allows you to specify what exactly should be done
when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user

View file

@ -0,0 +1,58 @@
<section id="adapter_error_handling">
<title>Error Handling</title>
<para>
Keycloak has some error handling facilities for servlet based client adapters. When an error is encountered in
authentication, keycloak will call <literal>HttpServletResponse.sendError()</literal>. You can set up an error-page
within your <literal>web.xml</literal> file to handle the error however you want. Keycloak may throw
400, 401, 403, and 500 errors.
</para>
<para>
<programlisting>
<![CDATA[
<error-page>
<error-code>404</error-code>
<location>/ErrorHandler</location>
</error-page>]]>
</programlisting>
</para>
<para>
Keycloak also sets an <literal>HttpServletRequest</literal> attribute that you can retrieve. The attribute name
is <literal>org.keycloak.adapters.spi.AuthenticationError</literal>. Typecast this object to:
<literal>org.keycloak.adapters.OIDCAuthenticationError</literal>. This class can tell you exactly what happened.
If this attribute is not set, then the adapter was not responsible for the error code.
</para>
<para>
<programlisting>
public class OIDCAuthenticationError implements AuthenticationError {
public static enum Reason {
NO_BEARER_TOKEN,
NO_REDIRECT_URI,
INVALID_STATE_COOKIE,
OAUTH_ERROR,
SSL_REQUIRED,
CODE_TO_TOKEN_FAILURE,
INVALID_TOKEN,
STALE_TOKEN,
NO_AUTHORIZATION_HEADER
}
private Reason reason;
private String description;
public OIDCAuthenticationError(Reason reason, String description) {
this.reason = reason;
this.description = description;
}
public Reason getReason() {
return reason;
}
public String getDescription() {
return description;
}
}
</programlisting>
</para>
</section>

View file

@ -9,6 +9,7 @@
<!ENTITY Jetty8Adapter SYSTEM "modules/jetty8-adapter.xml"> <!ENTITY Jetty8Adapter SYSTEM "modules/jetty8-adapter.xml">
<!ENTITY FilterAdapter SYSTEM "modules/servlet-filter-adapter.xml"> <!ENTITY FilterAdapter SYSTEM "modules/servlet-filter-adapter.xml">
<!ENTITY Logout SYSTEM "modules/logout.xml"> <!ENTITY Logout SYSTEM "modules/logout.xml">
<!ENTITY ErrorHandling SYSTEM "modules/adapter_error_handling.xml">
]> ]>
<book> <book>
@ -49,6 +50,7 @@ This one is short
&Jetty8Adapter; &Jetty8Adapter;
&FilterAdapter; &FilterAdapter;
&Logout; &Logout;
&ErrorHandling;

View file

@ -0,0 +1,42 @@
<chapter id="adapter_error_handling">
<title>Error Handling</title>
<para>
Keycloak has some error handling facilities for servlet based client adapters. When an error is encountered in
authentication, keycloak will call <literal>HttpServletResponse.sendError()</literal>. You can set up an error-page
within your <literal>web.xml</literal> file to handle the error however you want. Keycloak may throw
400, 401, 403, and 500 errors.
</para>
<para>
<programlisting>
<![CDATA[
<error-page>
<error-code>404</error-code>
<location>/ErrorHandler</location>
</error-page>]]>
</programlisting>
</para>
<para>
Keycloak also sets an <literal>HttpServletRequest</literal> attribute that you can retrieve. The attribute name
is <literal>org.keycloak.adapters.spi.AuthenticationError</literal>. Typecast this object to:
<literal>org.keycloak.adapters.saml.SamlAuthenticationError</literal>. This class can tell you exactly what happened.
If this attribute is not set, then the adapter was not responsible for the error code.
</para>
<para>
<programlisting>
public class SamlAuthenticationError implements AuthenticationError {
public static enum Reason {
EXTRACTION_FAILURE,
INVALID_SIGNATURE,
ERROR_STATUS
}
public Reason getReason() {
return reason;
}
public StatusResponseType getStatus() {
return status;
}
}
</programlisting>
</para>
</chapter>

View file

@ -17,10 +17,12 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.ServerRequest; import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@ -203,6 +205,18 @@ public class OfflineAccessPortalServlet extends HttpServlet {
public String getRemoteAddr() { public String getRemoteAddr() {
return servletRequest.getRemoteAddr(); return servletRequest.getRemoteAddr();
} }
@Override
public void setError(AuthenticationError error) {
servletRequest.setAttribute(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
servletRequest.setAttribute(LogoutError.class.getName(), error);
}
}; };
} }

View file

@ -33,7 +33,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
public AuthOutcome authenticate(HttpFacade exchange) { public AuthOutcome authenticate(HttpFacade exchange) {
List<String> authHeaders = exchange.getRequest().getHeaders("Authorization"); List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
if (authHeaders == null || authHeaders.size() == 0) { if (authHeaders == null || authHeaders.size() == 0) {
challenge = challengeResponse(exchange, null, null); challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_AUTHORIZATION_HEADER, null, null);
return AuthOutcome.NOT_ATTEMPTED; return AuthOutcome.NOT_ATTEMPTED;
} }
@ -46,7 +46,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
} }
if (tokenString == null) { if (tokenString == null) {
challenge = challengeResponse(exchange, null, null); challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED; return AuthOutcome.NOT_ATTEMPTED;
} }
@ -59,7 +59,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
tokenString = atr.getToken(); tokenString = atr.getToken();
} catch (Exception e) { } catch (Exception e) {
log.debug("Failed to obtain token", e); log.debug("Failed to obtain token", e);
challenge = challengeResponse(exchange, "no_token", e.getMessage()); challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "no_token", e.getMessage());
return AuthOutcome.FAILED; return AuthOutcome.FAILED;
} }

View file

@ -45,7 +45,7 @@ public class BearerTokenRequestAuthenticator {
public AuthOutcome authenticate(HttpFacade exchange) { public AuthOutcome authenticate(HttpFacade exchange) {
List<String> authHeaders = exchange.getRequest().getHeaders("Authorization"); List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
if (authHeaders == null || authHeaders.size() == 0) { if (authHeaders == null || authHeaders.size() == 0) {
challenge = challengeResponse(exchange, null, null); challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED; return AuthOutcome.NOT_ATTEMPTED;
} }
@ -58,7 +58,7 @@ public class BearerTokenRequestAuthenticator {
} }
if (tokenString == null) { if (tokenString == null) {
challenge = challengeResponse(exchange, null, null); challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED; return AuthOutcome.NOT_ATTEMPTED;
} }
@ -70,12 +70,12 @@ public class BearerTokenRequestAuthenticator {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
} catch (VerificationException e) { } catch (VerificationException e) {
log.error("Failed to verify token", e); log.error("Failed to verify token", e);
challenge = challengeResponse(exchange, "invalid_token", e.getMessage()); challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
return AuthOutcome.FAILED; return AuthOutcome.FAILED;
} }
if (token.getIssuedAt() < deployment.getNotBefore()) { if (token.getIssuedAt() < deployment.getNotBefore()) {
log.error("Stale token"); log.error("Stale token");
challenge = challengeResponse(exchange, "invalid_token", "Stale token"); challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.STALE_TOKEN, "invalid_token", "Stale token");
return AuthOutcome.FAILED; return AuthOutcome.FAILED;
} }
boolean verifyCaller = false; boolean verifyCaller = false;
@ -112,11 +112,6 @@ public class BearerTokenRequestAuthenticator {
protected AuthChallenge clientCertChallenge() { protected AuthChallenge clientCertChallenge() {
return new AuthChallenge() { return new AuthChallenge() {
@Override
public boolean errorPage() {
return false;
}
@Override @Override
public int getResponseCode() { public int getResponseCode() {
return 0; return 0;
@ -131,7 +126,7 @@ public class BearerTokenRequestAuthenticator {
} }
protected AuthChallenge challengeResponse(HttpFacade facade, String error, String description) { protected AuthChallenge challengeResponse(HttpFacade facade, final OIDCAuthenticationError.Reason reason, final String error, final String description) {
StringBuilder header = new StringBuilder("Bearer realm=\""); StringBuilder header = new StringBuilder("Bearer realm=\"");
header.append(deployment.getRealm()).append("\""); header.append(deployment.getRealm()).append("\"");
if (error != null) { if (error != null) {
@ -142,11 +137,6 @@ public class BearerTokenRequestAuthenticator {
} }
final String challenge = header.toString(); final String challenge = header.toString();
return new AuthChallenge() { return new AuthChallenge() {
@Override
public boolean errorPage() {
return true;
}
@Override @Override
public int getResponseCode() { public int getResponseCode() {
return 401; return 401;
@ -154,8 +144,10 @@ public class BearerTokenRequestAuthenticator {
@Override @Override
public boolean challenge(HttpFacade facade) { public boolean challenge(HttpFacade facade) {
facade.getResponse().setStatus(401); OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
facade.getRequest().setError(error);
facade.getResponse().addHeader("WWW-Authenticate", challenge); facade.getResponse().addHeader("WWW-Authenticate", challenge);
facade.getResponse().sendError(401);
return true; return true;
} }
}; };

View file

@ -174,15 +174,10 @@ public class OAuthRequestAuthenticator {
final String state = getStateCode(); final String state = getStateCode();
final String redirect = getRedirectUri(state); final String redirect = getRedirectUri(state);
if (redirect == null) { if (redirect == null) {
return challenge(403); return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
} }
return new AuthChallenge() { return new AuthChallenge() {
@Override
public boolean errorPage() {
return false;
}
@Override @Override
public int getResponseCode() { public int getResponseCode() {
return 0; return 0;
@ -205,7 +200,7 @@ public class OAuthRequestAuthenticator {
if (stateCookie == null) { if (stateCookie == null) {
log.warn("No state cookie"); log.warn("No state cookie");
return challenge(400); return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
} }
// reset the cookie // reset the cookie
log.debug("** reseting application state cookie"); log.debug("** reseting application state cookie");
@ -215,13 +210,13 @@ public class OAuthRequestAuthenticator {
String state = getQueryParamValue(OAuth2Constants.STATE); String state = getQueryParamValue(OAuth2Constants.STATE);
if (state == null) { if (state == null) {
log.warn("state parameter was null"); log.warn("state parameter was null");
return challenge(400); return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
} }
if (!state.equals(stateCookieValue)) { if (!state.equals(stateCookieValue)) {
log.warn("state parameter invalid"); log.warn("state parameter invalid");
log.warn("cookie: " + stateCookieValue); log.warn("cookie: " + stateCookieValue);
log.warn("queryParam: " + state); log.warn("queryParam: " + state);
return challenge(400); return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
} }
return null; return null;
@ -235,7 +230,7 @@ public class OAuthRequestAuthenticator {
if (error != null) { if (error != null) {
// todo how do we send a response? // todo how do we send a response?
log.warn("There was an error: " + error); log.warn("There was an error: " + error);
challenge = challenge(400); challenge = challenge(400, OIDCAuthenticationError.Reason.OAUTH_ERROR, error);
return AuthOutcome.FAILED; return AuthOutcome.FAILED;
} else { } else {
log.debug("redirecting to auth server"); log.debug("redirecting to auth server");
@ -253,13 +248,8 @@ public class OAuthRequestAuthenticator {
} }
protected AuthChallenge challenge(final int code) { protected AuthChallenge challenge(final int code, final OIDCAuthenticationError.Reason reason, final String description) {
return new AuthChallenge() { return new AuthChallenge() {
@Override
public boolean errorPage() {
return true;
}
@Override @Override
public int getResponseCode() { public int getResponseCode() {
return code; return code;
@ -267,6 +257,8 @@ public class OAuthRequestAuthenticator {
@Override @Override
public boolean challenge(HttpFacade exchange) { public boolean challenge(HttpFacade exchange) {
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(code); exchange.getResponse().sendError(code);
return true; return true;
} }
@ -289,7 +281,7 @@ public class OAuthRequestAuthenticator {
// abort if not HTTPS // abort if not HTTPS
if (!isRequestSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { if (!isRequestSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
log.error("Adapter requires SSL. Request: " + facade.getRequest().getURI()); log.error("Adapter requires SSL. Request: " + facade.getRequest().getURI());
return challenge(403); return challenge(403, OIDCAuthenticationError.Reason.SSL_REQUIRED, null);
} }
log.debug("checking state cookie for after code"); log.debug("checking state cookie for after code");
@ -308,11 +300,11 @@ public class OAuthRequestAuthenticator {
if (failure.getStatus() == 400 && failure.getError() != null) { if (failure.getStatus() == 400 && failure.getError() != null) {
log.error(" " + failure.getError()); log.error(" " + failure.getError());
} }
return challenge(403); return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
} catch (IOException e) { } catch (IOException e) {
log.error("failed to turn code into token", e); log.error("failed to turn code into token", e);
return challenge(403); return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
} }
tokenString = tokenResponse.getToken(); tokenString = tokenResponse.getToken();
@ -331,14 +323,14 @@ public class OAuthRequestAuthenticator {
log.debug("Token Verification succeeded!"); log.debug("Token Verification succeeded!");
} catch (VerificationException e) { } catch (VerificationException e) {
log.error("failed verification of token: " + e.getMessage()); log.error("failed verification of token: " + e.getMessage());
return challenge(403); return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
} }
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) { if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.setNotBefore(tokenResponse.getNotBeforePolicy()); deployment.setNotBefore(tokenResponse.getNotBeforePolicy());
} }
if (token.getIssuedAt() < deployment.getNotBefore()) { if (token.getIssuedAt() < deployment.getNotBefore()) {
log.error("Stale token"); log.error("Stale token");
return challenge(403); return challenge(403, OIDCAuthenticationError.Reason.STALE_TOKEN, null);
} }
log.debug("successful authenticated"); log.debug("successful authenticated");
return null; return null;

View file

@ -0,0 +1,39 @@
package org.keycloak.adapters;
import org.keycloak.adapters.spi.AuthenticationError;
/**
* Object that describes the OIDC error that happened.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCAuthenticationError implements AuthenticationError {
public static enum Reason {
NO_BEARER_TOKEN,
NO_REDIRECT_URI,
INVALID_STATE_COOKIE,
OAUTH_ERROR,
SSL_REQUIRED,
CODE_TO_TOKEN_FAILURE,
INVALID_TOKEN,
STALE_TOKEN,
NO_AUTHORIZATION_HEADER
}
private Reason reason;
private String description;
public OIDCAuthenticationError(Reason reason, String description) {
this.reason = reason;
this.description = description;
}
public Reason getReason() {
return reason;
}
public String getDescription() {
return description;
}
}

View file

@ -13,15 +13,7 @@ public interface AuthChallenge {
boolean challenge(HttpFacade exchange); boolean challenge(HttpFacade exchange);
/** /**
* Whether or not an error page should be displayed if possible along with the challenge * Some platforms need the error code that will be sent (i.e. Undertow)
*
* @return
*/
boolean errorPage();
/**
* If errorPage is true, this is the response code the challenge will send. This is used by platforms
* that call HttpServletResponse.sendError() to forward to error page.
* *
* @return * @return
*/ */

View file

@ -0,0 +1,13 @@
package org.keycloak.adapters.spi;
/**
* Common marker interface used by keycloak client adapters when there is an error. For servlets, you'll be able
* to extract this error from the HttpServletRequest.getAttribute(AuthenticationError.class.getName()). Each protocol
* will have their own subclass of this interface.
*
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface AuthenticationError {
}

View file

@ -47,6 +47,8 @@ public interface HttpFacade {
InputStream getInputStream(); InputStream getInputStream();
String getRemoteAddr(); String getRemoteAddr();
void setError(AuthenticationError error);
void setError(LogoutError error);
} }
interface Response { interface Response {

View file

@ -0,0 +1,12 @@
package org.keycloak.adapters.spi;
/**
* Common marker interface used by keycloak client adapters when there is an error. For servlets, you'll be able
* to extract this error from the HttpServletRequest.getAttribute(LogoutError.class.getName()). Each protocol
* will have their own subclass of this interface.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface LogoutError {
}

View file

@ -12,6 +12,8 @@ import javax.ws.rs.core.SecurityContext;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.HostUtils; import org.keycloak.common.util.HostUtils;
/** /**
@ -93,6 +95,17 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
// TODO: implement properly // TODO: implement properly
return HostUtils.getIpAddress(); 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 { protected class ResponseFacade implements OIDCHttpFacade.Response {

View file

@ -1,6 +1,8 @@
package org.keycloak.adapters.jetty.spi; package org.keycloak.adapters.jetty.spi;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
@ -125,6 +127,18 @@ public class JettyHttpFacade implements HttpFacade {
public String getRemoteAddr() { public String getRemoteAddr() {
return request.getRemoteAddr(); return request.getRemoteAddr();
} }
@Override
public void setError(AuthenticationError error) {
request.setAttribute(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
request.setAttribute(LogoutError.class.getName(), error);
}
} }
protected class ResponseFacade implements Response { protected class ResponseFacade implements Response {

View file

@ -265,15 +265,6 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
AuthChallenge challenge = authenticator.getChallenge(); AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) { if (challenge != null) {
challenge.challenge(facade); challenge.challenge(facade);
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);
}
}
} }
return Authentication.SEND_CONTINUE; return Authentication.SEND_CONTINUE;
} }

View file

@ -1,6 +1,8 @@
package org.keycloak.adapters.servlet; package org.keycloak.adapters.servlet;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.ServerCookie; import org.keycloak.common.util.ServerCookie;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
@ -111,6 +113,18 @@ public class ServletHttpFacade implements HttpFacade {
public String getRemoteAddr() { public String getRemoteAddr() {
return request.getRemoteAddr(); return request.getRemoteAddr();
} }
@Override
public void setError(AuthenticationError error) {
request.setAttribute(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
request.setAttribute(LogoutError.class.getName(), error);
}
} }
public boolean isEnded() { public boolean isEnded() {
return responseFacade.isEnded(); return responseFacade.isEnded();

View file

@ -142,11 +142,6 @@ public class KeycloakOIDCFilter implements Filter {
if (challenge != null) { if (challenge != null) {
log.fine("challenge"); log.fine("challenge");
challenge.challenge(facade); challenge.challenge(facade);
if (challenge.errorPage()) {
response.sendError(challenge.getResponseCode());
return;
}
log.fine("sending challenge");
return; return;
} }
response.sendError(403); response.sendError(403);

View file

@ -6,6 +6,8 @@ import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.ServerRequest; import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
@ -237,6 +239,18 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
public String getRemoteAddr() { public String getRemoteAddr() {
return servletRequest.getRemoteAddr(); return servletRequest.getRemoteAddr();
} }
@Override
public void setError(AuthenticationError error) {
servletRequest.setAttribute(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
servletRequest.setAttribute(LogoutError.class.getName(), error);
}
}; };
} }

View file

@ -1,7 +1,9 @@
package org.keycloak.adapters.springsecurity.facade; package org.keycloak.adapters.springsecurity.facade;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade.Cookie; import org.keycloak.adapters.spi.HttpFacade.Cookie;
import org.keycloak.adapters.spi.HttpFacade.Request; import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.adapters.spi.LogoutError;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -109,4 +111,17 @@ class WrappedHttpServletRequest implements Request {
public String getRemoteAddr() { public String getRemoteAddr() {
return request.getRemoteAddr(); return request.getRemoteAddr();
} }
@Override
public void setError(AuthenticationError error) {
request.setAttribute(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
request.setAttribute(LogoutError.class.getName(), error);
}
} }

View file

@ -1,6 +1,8 @@
package org.keycloak.adapters.tomcat; package org.keycloak.adapters.tomcat;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.ServerCookie; import org.keycloak.common.util.ServerCookie;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
@ -125,6 +127,18 @@ public class CatalinaHttpFacade implements HttpFacade {
public String getRemoteAddr() { public String getRemoteAddr() {
return request.getRemoteAddr(); return request.getRemoteAddr();
} }
@Override
public void setError(AuthenticationError error) {
request.setAttribute(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
request.setAttribute(LogoutError.class.getName(), error);
}
} }
protected class ResponseFacade implements Response { protected class ResponseFacade implements Response {

View file

@ -195,12 +195,6 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
} }
AuthChallenge challenge = authenticator.getChallenge(); AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) { if (challenge != null) {
if (loginConfig == null) {
loginConfig = request.getContext().getLoginConfig();
}
if (challenge.errorPage()) {
if (forwardToErrorPageInternal(request, response, loginConfig))return false;
}
challenge.challenge(facade); challenge.challenge(facade);
} }
return false; return false;

View file

@ -2,9 +2,13 @@ package org.keycloak.adapters.undertow;
import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.handlers.ServletRequestContext; import io.undertow.servlet.handlers.ServletRequestContext;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.LogoutError;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -18,6 +22,7 @@ public class ServletHttpFacade extends UndertowHttpFacade {
super(exchange); super(exchange);
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
request = (HttpServletRequest)servletRequestContext.getServletRequest(); request = (HttpServletRequest)servletRequestContext.getServletRequest();
response = (HttpServletResponse)servletRequestContext.getServletResponse();
} }
protected class RequestFacade extends UndertowHttpFacade.RequestFacade { protected class RequestFacade extends UndertowHttpFacade.RequestFacade {
@ -26,6 +31,47 @@ public class ServletHttpFacade extends UndertowHttpFacade {
return request.getParameter(param); return request.getParameter(param);
} }
@Override
public void setError(AuthenticationError error) {
request.setAttribute(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
request.setAttribute(LogoutError.class.getName(), error);
}
}
protected class ResponseFacade extends UndertowHttpFacade.ResponseFacade {
// can't call sendError from a challenge. Undertow ends up calling send error.
/*
@Override
public void sendError(int code) {
try {
response.sendError(code);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void sendError(int code, String message) {
try {
response.sendError(code, message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
*/
}
@Override
public Response getResponse() {
return new ResponseFacade();
} }
@Override @Override

View file

@ -2,9 +2,12 @@ package org.keycloak.adapters.undertow;
import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.CookieImpl; import io.undertow.server.handlers.CookieImpl;
import io.undertow.util.AttachmentKey;
import io.undertow.util.Headers; import io.undertow.util.Headers;
import io.undertow.util.HttpString; import io.undertow.util.HttpString;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import javax.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
@ -22,6 +25,9 @@ import java.util.Map;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class UndertowHttpFacade implements HttpFacade { public class UndertowHttpFacade implements HttpFacade {
public static final AttachmentKey<AuthenticationError> AUTH_ERROR_ATTACHMENT_KEY = AttachmentKey.create(AuthenticationError.class);
public static final AttachmentKey<LogoutError> LOGOUT_ERROR_ATTACHMENT_KEY = AttachmentKey.create(LogoutError.class);
protected HttpServerExchange exchange; protected HttpServerExchange exchange;
protected RequestFacade requestFacade = new RequestFacade(); protected RequestFacade requestFacade = new RequestFacade();
protected ResponseFacade responseFacade = new ResponseFacade(); protected ResponseFacade responseFacade = new ResponseFacade();
@ -127,6 +133,17 @@ public class UndertowHttpFacade implements HttpFacade {
} }
return address.getHostAddress(); return address.getHostAddress();
} }
@Override
public void setError(AuthenticationError error) {
exchange.putAttachment(AUTH_ERROR_ATTACHMENT_KEY, error);
}
@Override
public void setError(LogoutError error) {
exchange.putAttachment(LOGOUT_ERROR_ATTACHMENT_KEY, error);
}
} }
protected class ResponseFacade implements Response { protected class ResponseFacade implements Response {
@ -173,7 +190,6 @@ public class UndertowHttpFacade implements HttpFacade {
@Override @Override
public void sendError(int code) { public void sendError(int code) {
exchange.setResponseCode(code); exchange.setResponseCode(code);
exchange.endExchange();
} }
@Override @Override

View file

@ -56,11 +56,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY); AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
if (challenge != null) { if (challenge != null) {
if (challenge.errorPage() && errorPage != null) { UndertowHttpFacade facade = createFacade(exchange);
Integer code = servePage(exchange, errorPage);
return new ChallengeResult(true, code);
}
UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
if (challenge.challenge(facade)) { if (challenge.challenge(facade)) {
return new ChallengeResult(true, exchange.getResponseCode()); return new ChallengeResult(true, exchange.getResponseCode());
} }
@ -68,6 +64,10 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
return new ChallengeResult(false); return new ChallengeResult(false);
} }
public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
return new OIDCUndertowHttpFacade(exchange);
}
protected Integer servePage(final HttpServerExchange exchange, final String location) { protected Integer servePage(final HttpServerExchange exchange, final String location) {
sendRedirect(exchange, location); sendRedirect(exchange, location);
return StatusCodes.TEMPORARY_REDIRECT; return StatusCodes.TEMPORARY_REDIRECT;
@ -89,7 +89,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return; if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
HttpServerExchange exchange = notification.getExchange(); HttpServerExchange exchange = notification.getExchange();
UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); UndertowHttpFacade facade = createFacade(exchange);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY); KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) { if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {

View file

@ -0,0 +1,40 @@
/*
* Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* 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.undertow;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCServletUndertowHttpFacade extends ServletHttpFacade implements OIDCHttpFacade {
public static final AttachmentKey<KeycloakSecurityContext> KEYCLOAK_SECURITY_CONTEXT_KEY = AttachmentKey.create(KeycloakSecurityContext.class);
public OIDCServletUndertowHttpFacade(HttpServerExchange exchange) {
super(exchange);
}
@Override
public KeycloakSecurityContext getSecurityContext() {
return exchange.getAttachment(KEYCLOAK_SECURITY_CONTEXT_KEY);
}
}

View file

@ -79,7 +79,7 @@ public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech {
@Override @Override
public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); UndertowHttpFacade facade = createFacade(exchange);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (!deployment.isConfigured()) { if (!deployment.isConfigured()) {
return AuthenticationMechanismOutcome.NOT_ATTEMPTED; return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
@ -119,4 +119,8 @@ public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech {
} }
} }
@Override
public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
return new OIDCServletUndertowHttpFacade(exchange);
}
} }

View file

@ -61,11 +61,13 @@ public class ServletPreAuthActionsHandler implements HttpHandler {
@Override @Override
public void handleRequest(HttpServerExchange exchange) throws Exception { public void handleRequest(HttpServerExchange exchange) throws Exception {
UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); UndertowHttpFacade facade = new OIDCServletUndertowHttpFacade(exchange);
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager()); SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager());
PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade); PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
if (handler.handleRequest()) return; if (handler.handleRequest()) return;
next.handleRequest(exchange); next.handleRequest(exchange);
} }
} }

View file

@ -25,7 +25,7 @@ public class UndertowAuthenticationMechanism extends AbstractUndertowKeycloakAut
@Override @Override
public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); UndertowHttpFacade facade = createFacade(exchange);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (!deployment.isConfigured()) { if (!deployment.isConfigured()) {
return AuthenticationMechanismOutcome.NOT_ATTEMPTED; return AuthenticationMechanismOutcome.NOT_ATTEMPTED;

View file

@ -47,10 +47,14 @@ public class UndertowPreAuthActionsHandler implements HttpHandler {
@Override @Override
public void handleRequest(HttpServerExchange exchange) throws Exception { public void handleRequest(HttpServerExchange exchange) throws Exception {
UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange); UndertowHttpFacade facade = createFacade(exchange);
SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager); SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager);
PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade); PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
if (handler.handleRequest()) return; if (handler.handleRequest()) return;
next.handleRequest(exchange); next.handleRequest(exchange);
} }
public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
return new OIDCUndertowHttpFacade(exchange);
}
} }

View file

@ -26,11 +26,6 @@ public class InitiateLogin implements AuthChallenge {
this.sessionStore = sessionStore; this.sessionStore = sessionStore;
} }
@Override
public boolean errorPage() {
return false;
}
@Override @Override
public int getResponseCode() { public int getResponseCode() {
return 0; return 0;

View file

@ -0,0 +1,43 @@
package org.keycloak.adapters.saml;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.dom.saml.v2.protocol.StatusType;
/**
* Object that describes the SAML error that happened.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SamlAuthenticationError implements AuthenticationError {
public static enum Reason {
EXTRACTION_FAILURE,
INVALID_SIGNATURE,
ERROR_STATUS
}
private Reason reason;
private StatusResponseType status;
public SamlAuthenticationError(Reason reason) {
this.reason = reason;
}
public SamlAuthenticationError(Reason reason, StatusResponseType status) {
this.reason = reason;
this.status = status;
}
public SamlAuthenticationError(StatusResponseType statusType) {
this.status = statusType;
}
public Reason getReason() {
return reason;
}
public StatusResponseType getStatus() {
return status;
}
}

View file

@ -208,15 +208,34 @@ public abstract class SamlAuthenticator {
return AuthOutcome.FAILED; return AuthOutcome.FAILED;
} }
if (statusResponse instanceof ResponseType) { if (statusResponse instanceof ResponseType) {
if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) { try {
try { if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) {
validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY); try {
} catch (VerificationException e) { validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
log.error("Failed to verify saml response signature", e); } catch (VerificationException e) {
return AuthOutcome.FAILED; log.error("Failed to verify saml response signature", e);
challenge = new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(403);
return true;
}
@Override
public int getResponseCode() {
return 403;
}
};
return AuthOutcome.FAILED;
}
} }
return handleLoginResponse((ResponseType) statusResponse);
} finally {
sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
} }
return handleLoginResponse((ResponseType) statusResponse);
} else { } else {
if (sessionStore.isLoggingOut()) { if (sessionStore.isLoggingOut()) {
@ -239,18 +258,15 @@ public abstract class SamlAuthenticator {
challenge = new AuthChallenge() { challenge = new AuthChallenge() {
@Override @Override
public boolean challenge(HttpFacade exchange) { public boolean challenge(HttpFacade exchange) {
exchange.getResponse().sendError(500, statusResponse.getStatus().getStatusCode().getValue().toString()); SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.ERROR_STATUS, statusResponse);
return true; exchange.getRequest().setError(error);
} exchange.getResponse().sendError(403);
@Override
public boolean errorPage() {
return true; return true;
} }
@Override @Override
public int getResponseCode() { public int getResponseCode() {
return 500; return 403;
} }
}; };
return AuthOutcome.FAILED; return AuthOutcome.FAILED;
@ -280,7 +296,20 @@ public abstract class SamlAuthenticator {
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Error extracting SAML assertion, e"); log.error("Error extracting SAML assertion, e");
return AuthOutcome.FAILED; challenge = new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(403);
return true;
}
@Override
public int getResponseCode() {
return 403;
}
};
} }
SubjectType subject = assertion.getSubject(); SubjectType subject = assertion.getSubject();

View file

@ -260,15 +260,6 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
AuthChallenge challenge = authenticator.getChallenge(); AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) { if (challenge != null) {
challenge.challenge(facade); challenge.challenge(facade);
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);
}
}
} }
return Authentication.SEND_CONTINUE; return Authentication.SEND_CONTINUE;
} }

View file

@ -137,11 +137,6 @@ public class SamlFilter implements Filter {
if (challenge != null) { if (challenge != null) {
log.fine("challenge"); log.fine("challenge");
challenge.challenge(facade); challenge.challenge(facade);
if (challenge.errorPage()) {
response.sendError(challenge.getResponseCode());
return;
}
log.fine("sending challenge");
return; return;
} }
if (!facade.isEnded()) { if (!facade.isEnded()) {

View file

@ -213,10 +213,6 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
loginConfig = request.getContext().getLoginConfig(); loginConfig = request.getContext().getLoginConfig();
} }
challenge.challenge(facade); challenge.challenge(facade);
if (challenge.errorPage()) {
log.fine("error page");
if (forwardToErrorPageInternal(request, response, loginConfig))return false;
}
} }
return false; return false;
} }

View file

@ -55,10 +55,6 @@ public abstract class AbstractSamlAuthMech implements AuthenticationMechanism {
public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY); AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
if (challenge != null) { if (challenge != null) {
if (challenge.errorPage() && errorPage != null) {
Integer code = servePage(exchange, errorPage);
return new ChallengeResult(true, code);
}
UndertowHttpFacade facade = createFacade(exchange); UndertowHttpFacade facade = createFacade(exchange);
if (challenge.challenge(facade)) { if (challenge.challenge(facade)) {
return new ChallengeResult(true, exchange.getResponseCode()); return new ChallengeResult(true, exchange.getResponseCode());

View file

@ -24,6 +24,7 @@ package org.keycloak.testsuite.adapter;
import org.junit.Assert; import org.junit.Assert;
import org.junit.rules.ExternalResource; import org.junit.rules.ExternalResource;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.OIDCAuthenticationError;
import org.keycloak.common.Version; import org.keycloak.common.Version;
import org.keycloak.representations.VersionRepresentation; import org.keycloak.representations.VersionRepresentation;
import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.Keycloak;
@ -42,6 +43,7 @@ import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AccountSessionsPage; import org.keycloak.testsuite.pages.AccountSessionsPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule; import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.ErrorServlet;
import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule; import org.keycloak.testsuite.rule.WebRule;
@ -385,6 +387,7 @@ public class AdapterTestStrategy extends ExternalResource {
* @throws Exception * @throws Exception
*/ */
public void testNullBearerTokenCustomErrorPage() throws Exception { public void testNullBearerTokenCustomErrorPage() throws Exception {
ErrorServlet.authError = null;
Client client = ClientBuilder.newClient(); Client client = ClientBuilder.newClient();
WebTarget target = client.target(APP_SERVER_BASE_URL + "/customer-db-error-page/"); WebTarget target = client.target(APP_SERVER_BASE_URL + "/customer-db-error-page/");
@ -396,11 +399,15 @@ public class AdapterTestStrategy extends ExternalResource {
response.close(); response.close();
response = client.target(location).request().get(); response = client.target(location).request().get();
} }
Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(401, response.getStatus());
String errorPageResponse = response.readEntity(String.class); String errorPageResponse = response.readEntity(String.class);
Assert.assertTrue(errorPageResponse.contains("Error Page")); Assert.assertTrue(errorPageResponse.contains("Error Page"));
response.close(); response.close();
Assert.assertNotNull(ErrorServlet.authError);
OIDCAuthenticationError error = (OIDCAuthenticationError)ErrorServlet.authError;
Assert.assertEquals(OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, error.getReason());
ErrorServlet.authError = null;
response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get(); response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get();
// TODO: follow redirects automatically if possible // TODO: follow redirects automatically if possible
if (response.getStatus() == 302) { if (response.getStatus() == 302) {
@ -408,10 +415,13 @@ public class AdapterTestStrategy extends ExternalResource {
response.close(); response.close();
response = client.target(location).request().get(); response = client.target(location).request().get();
} }
Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(401, response.getStatus());
errorPageResponse = response.readEntity(String.class); errorPageResponse = response.readEntity(String.class);
Assert.assertTrue(errorPageResponse.contains("Error Page")); Assert.assertTrue(errorPageResponse.contains("Error Page"));
response.close(); response.close();
Assert.assertNotNull(ErrorServlet.authError);
error = (OIDCAuthenticationError)ErrorServlet.authError;
Assert.assertEquals(OIDCAuthenticationError.Reason.INVALID_TOKEN, error.getReason());
client.close(); client.close();

View file

@ -48,12 +48,7 @@ public class SamlAdapterTest {
@Test @Test
public void testPostBadRealmSignature() { public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostBadRealmSignature();
@Override
public void check(WebDriver driver) {
Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
}
});
} }
@Test @Test
@ -61,7 +56,8 @@ public class SamlAdapterTest {
testStrategy.testPostSimpleUnauthorized( new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostSimpleUnauthorized( new SamlAdapterTestStrategy.CheckAuthError() {
@Override @Override
public void check(WebDriver driver) { public void check(WebDriver driver) {
Assert.assertTrue(driver.getPageSource().contains("Forbidden")); String pageSource = driver.getPageSource();
Assert.assertTrue(pageSource.contains("Error Page"));
} }
}); });
} }

View file

@ -3,6 +3,7 @@ package org.keycloak.testsuite.keycloaksaml;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.junit.Assert; import org.junit.Assert;
import org.junit.rules.ExternalResource; import org.junit.rules.ExternalResource;
import org.keycloak.adapters.saml.SamlAuthenticationError;
import org.keycloak.adapters.saml.SamlPrincipal; import org.keycloak.adapters.saml.SamlPrincipal;
import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
@ -28,6 +29,7 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.KeycloakServer; import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule; import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.ErrorServlet;
import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule; import org.keycloak.testsuite.rule.WebRule;
@ -100,6 +102,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
} }
public void testErrorHandling() throws Exception { public void testErrorHandling() throws Exception {
ErrorServlet.authError = null;
Client client = ClientBuilder.newClient(); Client client = ClientBuilder.newClient();
// make sure // make sure
Response response = client.target(APP_SERVER_BASE_URL + "/employee-sig/").request().get(); Response response = client.target(APP_SERVER_BASE_URL + "/employee-sig/").request().get();
@ -115,8 +118,13 @@ public class SamlAdapterTestStrategy extends ExternalResource {
response = client.target(uri).request().get(); response = client.target(uri).request().get();
String errorPage = response.readEntity(String.class); String errorPage = response.readEntity(String.class);
response.close(); response.close();
Assert.assertTrue(errorPage.contains(JBossSAMLURIConstants.STATUS_RESPONDER.get())); Assert.assertTrue(errorPage.contains("Error Page"));
client.close(); client.close();
Assert.assertNotNull(ErrorServlet.authError);
SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
Assert.assertEquals(SamlAuthenticationError.Reason.ERROR_STATUS, error.getReason());
Assert.assertNotNull(error.getStatus());
ErrorServlet.authError = null;
} }
@ -388,13 +396,17 @@ public class SamlAdapterTestStrategy extends ExternalResource {
void check(WebDriver driver); void check(WebDriver driver);
} }
public void testPostBadRealmSignature(CheckAuthError error) { public void testPostBadRealmSignature() {
ErrorServlet.authError = null;
driver.navigate().to(APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/"); driver.navigate().to(APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml"); assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
loginPage.login("bburke", "password"); loginPage.login("bburke", "password");
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/"); assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
System.out.println(driver.getPageSource()); System.out.println(driver.getPageSource());
error.check(driver); Assert.assertNotNull(ErrorServlet.authError);
SamlAuthenticationError error = (SamlAuthenticationError)ErrorServlet.authError;
Assert.assertEquals(SamlAuthenticationError.Reason.INVALID_SIGNATURE, error.getReason());
ErrorServlet.authError = null;
} }
public void testMetadataPostSignedLoginLogout() throws Exception { public void testMetadataPostSignedLoginLogout() throws Exception {

View file

@ -115,6 +115,7 @@ public abstract class SamlKeycloakRule extends AbstractKeycloakRule {
.addServlets(regularServletInfo) .addServlets(regularServletInfo)
.addSecurityConstraint(constraint) .addSecurityConstraint(constraint)
.addServletExtension(new SamlServletExtension()); .addServletExtension(new SamlServletExtension());
addErrorPage("/error.html", deploymentInfo);
server.getServer().deploy(deploymentInfo); server.getServer().deploy(deploymentInfo);
} }

View file

@ -1,6 +1,7 @@
package org.keycloak.testsuite.rule; package org.keycloak.testsuite.rule;
import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.ErrorPage;
import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.FilterInfo;
import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.LoginConfig;
import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityConstraint;
@ -156,7 +157,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
return new DeploymentBuilder(); return new DeploymentBuilder();
} }
public void addErrorPage(DeploymentInfo di) { public void addErrorPage(String errorPage, DeploymentInfo di) {
ServletInfo servlet = new ServletInfo("Error Page", ErrorServlet.class); ServletInfo servlet = new ServletInfo("Error Page", ErrorServlet.class);
servlet.addMapping("/error.html"); servlet.addMapping("/error.html");
SecurityConstraint constraint = new SecurityConstraint(); SecurityConstraint constraint = new SecurityConstraint();
@ -166,6 +167,11 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
constraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT); constraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT);
di.addSecurityConstraint(constraint); di.addSecurityConstraint(constraint);
di.addServlet(servlet); di.addServlet(servlet);
di
.addErrorPage(new ErrorPage(errorPage, 400))
.addErrorPage(new ErrorPage(errorPage, 401))
.addErrorPage(new ErrorPage(errorPage, 403))
.addErrorPage(new ErrorPage(errorPage, 500));
} }
public void deployJaxrsApplication(String name, String contextPath, Class<? extends Application> applicationClass, Map<String,String> initParams) { public void deployJaxrsApplication(String name, String contextPath, Class<? extends Application> applicationClass, Map<String,String> initParams) {
@ -346,9 +352,9 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
constraint.addRoleAllowed(role); constraint.addRoleAllowed(role);
di.addSecurityConstraint(constraint); di.addSecurityConstraint(constraint);
} }
LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo", null, errorPage); LoginConfig loginConfig = new LoginConfig("KEYCLOAK", "demo", null, null);
di.setLoginConfig(loginConfig); di.setLoginConfig(loginConfig);
addErrorPage(di); addErrorPage(errorPage, di);
server.getServer().deploy(di); server.getServer().deploy(di);
} }

View file

@ -1,5 +1,7 @@
package org.keycloak.testsuite.rule; package org.keycloak.testsuite.rule;
import org.keycloak.adapters.spi.AuthenticationError;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -12,10 +14,11 @@ import java.io.PrintWriter;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class ErrorServlet extends HttpServlet { public class ErrorServlet extends HttpServlet {
public static AuthenticationError authError;
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
authError = (AuthenticationError)req.getAttribute(AuthenticationError.class.getName());
resp.setContentType("text/html"); resp.setContentType("text/html");
PrintWriter pw = resp.getWriter(); PrintWriter pw = resp.getWriter();
@ -25,4 +28,9 @@ public class ErrorServlet extends HttpServlet {
} }
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
} }

View file

@ -53,12 +53,7 @@ public class SamlAdapterTest {
@Test @Test
public void testPostBadRealmSignature() { public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostBadRealmSignature();
@Override
public void check(WebDriver driver) {
Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
}
});
} }
@Test @Test
@ -72,7 +67,7 @@ public class SamlAdapterTest {
testStrategy.testPostSimpleUnauthorized(new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostSimpleUnauthorized(new SamlAdapterTestStrategy.CheckAuthError() {
@Override @Override
public void check(WebDriver driver) { public void check(WebDriver driver) {
Assert.assertTrue(driver.getPageSource().contains("Forbidden")); Assert.assertTrue(driver.getPageSource().contains("Error Page"));
} }
}); });
} finally { } finally {

View file

@ -114,6 +114,7 @@ public abstract class SamlKeycloakRule extends AbstractKeycloakRule {
.addFilter(samlFilter) .addFilter(samlFilter)
.addFilterUrlMapping("saml-filter", "/*", DispatcherType.REQUEST) .addFilterUrlMapping("saml-filter", "/*", DispatcherType.REQUEST)
.addServletExtension(new SamlServletExtension()); .addServletExtension(new SamlServletExtension());
addErrorPage("/error.html", deploymentInfo);
server.getServer().deploy(deploymentInfo); server.getServer().deploy(deploymentInfo);
} }

View file

@ -171,12 +171,7 @@ public class JettySamlTest {
@Test @Test
public void testPostBadRealmSignature() { public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostBadRealmSignature();
@Override
public void check(WebDriver driver) {
Assert.assertEquals(driver.getPageSource(), "");
}
});
} }
@Test @Test

View file

@ -25,6 +25,26 @@
<url-pattern>/error.html</url-pattern> <url-pattern>/error.html</url-pattern>
</servlet-mapping> </servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,40 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -170,12 +170,7 @@ public class JettySamlTest {
@Test @Test
public void testPostBadRealmSignature() { public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostBadRealmSignature();
@Override
public void check(WebDriver driver) {
Assert.assertEquals(driver.getPageSource(), "");
}
});
} }
@Test @Test

View file

@ -25,6 +25,26 @@
<url-pattern>/error.html</url-pattern> <url-pattern>/error.html</url-pattern>
</servlet-mapping> </servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -10,12 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<security-constraint> <servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -170,12 +170,7 @@ public class JettySamlTest {
@Test @Test
public void testPostBadRealmSignature() { public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostBadRealmSignature( );
@Override
public void check(WebDriver driver) {
Assert.assertEquals(driver.getPageSource(), "");
}
});
} }
@Test @Test

View file

@ -25,6 +25,26 @@
<url-pattern>/error.html</url-pattern> <url-pattern>/error.html</url-pattern>
</servlet-mapping> </servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -154,12 +154,7 @@ public class TomcatSamlTest {
@Test @Test
public void testPostBadRealmSignature() { public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostBadRealmSignature();
@Override
public void check(WebDriver driver) {
Assert.assertEquals(driver.getPageSource(), "");
}
});
} }
@Test @Test

View file

@ -25,6 +25,26 @@
<url-pattern>/error.html</url-pattern> <url-pattern>/error.html</url-pattern>
</servlet-mapping> </servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>
@ -44,11 +64,7 @@
<login-config> <login-config>
<auth-method>BASIC</auth-method> <auth-method>BASIC</auth-method>
<realm-name>demo</realm-name> <realm-name>demo</realm-name>
<form-login-config> </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> <security-role>
<role-name>admin</role-name> <role-name>admin</role-name>

View file

@ -10,12 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<security-constraint> <servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -159,12 +159,7 @@ public class TomcatSamlTest {
@Test @Test
public void testPostBadRealmSignature() { public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostBadRealmSignature();
@Override
public void check(WebDriver driver) {
Assert.assertEquals(driver.getPageSource(), "");
}
});
} }
@Test @Test

View file

@ -25,6 +25,26 @@
<url-pattern>/error.html</url-pattern> <url-pattern>/error.html</url-pattern>
</servlet-mapping> </servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -159,12 +159,7 @@ public class TomcatSamlTest {
@Test @Test
public void testPostBadRealmSignature() { public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() { testStrategy.testPostBadRealmSignature();
@Override
public void check(WebDriver driver) {
Assert.assertEquals(driver.getPageSource(), "");
}
});
} }
@Test @Test

View file

@ -25,6 +25,26 @@
<url-pattern>/error.html</url-pattern> <url-pattern>/error.html</url-pattern>
</servlet-mapping> </servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet> </servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name> <servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>Error Servlet</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>400</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.html</location>
</error-page>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Users</web-resource-name> <web-resource-name>Users</web-resource-name>