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

View file

@ -83,6 +83,10 @@
<title>Migrating to 1.7.0.CR1</title>
<simplesect>
<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>
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

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 FilterAdapter SYSTEM "modules/servlet-filter-adapter.xml">
<!ENTITY Logout SYSTEM "modules/logout.xml">
<!ENTITY ErrorHandling SYSTEM "modules/adapter_error_handling.xml">
]>
<book>
@ -49,6 +50,7 @@ This one is short
&Jetty8Adapter;
&FilterAdapter;
&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.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.JsonSerialization;
@ -203,6 +205,18 @@ public class OfflineAccessPortalServlet extends HttpServlet {
public String 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) {
List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
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;
}
@ -46,7 +46,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
}
if (tokenString == null) {
challenge = challengeResponse(exchange, null, null);
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
@ -59,7 +59,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
tokenString = atr.getToken();
} catch (Exception 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;
}

View file

@ -45,7 +45,7 @@ public class BearerTokenRequestAuthenticator {
public AuthOutcome authenticate(HttpFacade exchange) {
List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
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;
}
@ -58,7 +58,7 @@ public class BearerTokenRequestAuthenticator {
}
if (tokenString == null) {
challenge = challengeResponse(exchange, null, null);
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
@ -70,12 +70,12 @@ public class BearerTokenRequestAuthenticator {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
} catch (VerificationException 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;
}
if (token.getIssuedAt() < deployment.getNotBefore()) {
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;
}
boolean verifyCaller = false;
@ -112,11 +112,6 @@ public class BearerTokenRequestAuthenticator {
protected AuthChallenge clientCertChallenge() {
return new AuthChallenge() {
@Override
public boolean errorPage() {
return false;
}
@Override
public int getResponseCode() {
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=\"");
header.append(deployment.getRealm()).append("\"");
if (error != null) {
@ -142,11 +137,6 @@ public class BearerTokenRequestAuthenticator {
}
final String challenge = header.toString();
return new AuthChallenge() {
@Override
public boolean errorPage() {
return true;
}
@Override
public int getResponseCode() {
return 401;
@ -154,8 +144,10 @@ public class BearerTokenRequestAuthenticator {
@Override
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().sendError(401);
return true;
}
};

View file

@ -174,15 +174,10 @@ public class OAuthRequestAuthenticator {
final String state = getStateCode();
final String redirect = getRedirectUri(state);
if (redirect == null) {
return challenge(403);
return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
}
return new AuthChallenge() {
@Override
public boolean errorPage() {
return false;
}
@Override
public int getResponseCode() {
return 0;
@ -205,7 +200,7 @@ public class OAuthRequestAuthenticator {
if (stateCookie == null) {
log.warn("No state cookie");
return challenge(400);
return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
}
// reset the cookie
log.debug("** reseting application state cookie");
@ -215,13 +210,13 @@ public class OAuthRequestAuthenticator {
String state = getQueryParamValue(OAuth2Constants.STATE);
if (state == null) {
log.warn("state parameter was null");
return challenge(400);
return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
}
if (!state.equals(stateCookieValue)) {
log.warn("state parameter invalid");
log.warn("cookie: " + stateCookieValue);
log.warn("queryParam: " + state);
return challenge(400);
return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
}
return null;
@ -235,7 +230,7 @@ public class OAuthRequestAuthenticator {
if (error != null) {
// todo how do we send a response?
log.warn("There was an error: " + error);
challenge = challenge(400);
challenge = challenge(400, OIDCAuthenticationError.Reason.OAUTH_ERROR, error);
return AuthOutcome.FAILED;
} else {
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() {
@Override
public boolean errorPage() {
return true;
}
@Override
public int getResponseCode() {
return code;
@ -267,6 +257,8 @@ public class OAuthRequestAuthenticator {
@Override
public boolean challenge(HttpFacade exchange) {
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(code);
return true;
}
@ -289,7 +281,7 @@ public class OAuthRequestAuthenticator {
// abort if not HTTPS
if (!isRequestSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
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");
@ -308,11 +300,11 @@ public class OAuthRequestAuthenticator {
if (failure.getStatus() == 400 && failure.getError() != null) {
log.error(" " + failure.getError());
}
return challenge(403);
return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
} catch (IOException 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();
@ -331,14 +323,14 @@ public class OAuthRequestAuthenticator {
log.debug("Token Verification succeeded!");
} catch (VerificationException e) {
log.error("failed verification of token: " + e.getMessage());
return challenge(403);
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
}
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.setNotBefore(tokenResponse.getNotBeforePolicy());
}
if (token.getIssuedAt() < deployment.getNotBefore()) {
log.error("Stale token");
return challenge(403);
return challenge(403, OIDCAuthenticationError.Reason.STALE_TOKEN, null);
}
log.debug("successful authenticated");
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);
/**
* Whether or not an error page should be displayed if possible along with the challenge
*
* @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.
* Some platforms need the error code that will be sent (i.e. Undertow)
*
* @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();
String getRemoteAddr();
void setError(AuthenticationError error);
void setError(LogoutError error);
}
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.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.HostUtils;
/**
@ -93,6 +95,17 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
// TODO: implement properly
return HostUtils.getIpAddress();
}
@Override
public void setError(AuthenticationError error) {
requestContext.setProperty(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
requestContext.setProperty(LogoutError.class.getName(), error);
}
}
protected class ResponseFacade implements OIDCHttpFacade.Response {

View file

@ -1,6 +1,8 @@
package org.keycloak.adapters.jetty.spi;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.UriUtils;
@ -125,6 +127,18 @@ public class JettyHttpFacade implements HttpFacade {
public String 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 {

View file

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

View file

@ -1,6 +1,8 @@
package org.keycloak.adapters.servlet;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.ServerCookie;
import org.keycloak.common.util.UriUtils;
@ -111,6 +113,18 @@ public class ServletHttpFacade implements HttpFacade {
public String 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() {
return responseFacade.isEnded();

View file

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

View file

@ -6,6 +6,8 @@ import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
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.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
@ -237,6 +239,18 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
public String 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;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade.Cookie;
import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.adapters.spi.LogoutError;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
@ -109,4 +111,17 @@ class WrappedHttpServletRequest implements Request {
public String 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;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.ServerCookie;
import org.keycloak.common.util.UriUtils;
@ -125,6 +127,18 @@ public class CatalinaHttpFacade implements HttpFacade {
public String 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 {

View file

@ -195,12 +195,6 @@ 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

@ -2,9 +2,13 @@ package org.keycloak.adapters.undertow;
import io.undertow.server.HttpServerExchange;
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.HttpServletResponse;
import java.io.IOException;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -18,6 +22,7 @@ public class ServletHttpFacade extends UndertowHttpFacade {
super(exchange);
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
request = (HttpServletRequest)servletRequestContext.getServletRequest();
response = (HttpServletResponse)servletRequestContext.getServletResponse();
}
protected class RequestFacade extends UndertowHttpFacade.RequestFacade {
@ -26,6 +31,47 @@ public class ServletHttpFacade extends UndertowHttpFacade {
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

View file

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

View file

@ -56,11 +56,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
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);
UndertowHttpFacade facade = createFacade(exchange);
if (challenge.challenge(facade)) {
return new ChallengeResult(true, exchange.getResponseCode());
}
@ -68,6 +64,10 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
return new ChallengeResult(false);
}
public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
return new OIDCUndertowHttpFacade(exchange);
}
protected Integer servePage(final HttpServerExchange exchange, final String location) {
sendRedirect(exchange, location);
return StatusCodes.TEMPORARY_REDIRECT;
@ -89,7 +89,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
HttpServerExchange exchange = notification.getExchange();
UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
UndertowHttpFacade facade = createFacade(exchange);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
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
public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
UndertowHttpFacade facade = createFacade(exchange);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (!deployment.isConfigured()) {
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
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);
SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager());
PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
if (handler.handleRequest()) return;
next.handleRequest(exchange);
}
}

View file

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

View file

@ -47,10 +47,14 @@ public class UndertowPreAuthActionsHandler implements HttpHandler {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
UndertowHttpFacade facade = createFacade(exchange);
SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager);
PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
if (handler.handleRequest()) return;
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;
}
@Override
public boolean errorPage() {
return false;
}
@Override
public int getResponseCode() {
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;
}
if (statusResponse instanceof ResponseType) {
try {
if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) {
try {
validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
} catch (VerificationException e) {
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);
}
} else {
if (sessionStore.isLoggingOut()) {
@ -239,18 +258,15 @@ public abstract class SamlAuthenticator {
challenge = new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
exchange.getResponse().sendError(500, statusResponse.getStatus().getStatusCode().getValue().toString());
return true;
}
@Override
public boolean errorPage() {
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.ERROR_STATUS, statusResponse);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(403);
return true;
}
@Override
public int getResponseCode() {
return 500;
return 403;
}
};
return AuthOutcome.FAILED;
@ -280,7 +296,20 @@ public abstract class SamlAuthenticator {
}
} catch (Exception 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();

View file

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

View file

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

View file

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

View file

@ -55,10 +55,6 @@ public abstract class AbstractSamlAuthMech implements AuthenticationMechanism {
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 = createFacade(exchange);
if (challenge.challenge(facade)) {
return new ChallengeResult(true, exchange.getResponseCode());

View file

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

View file

@ -48,12 +48,7 @@ public class SamlAdapterTest {
@Test
public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
@Override
public void check(WebDriver driver) {
Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
}
});
testStrategy.testPostBadRealmSignature();
}
@Test
@ -61,7 +56,8 @@ public class SamlAdapterTest {
testStrategy.testPostSimpleUnauthorized( new SamlAdapterTestStrategy.CheckAuthError() {
@Override
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.junit.Assert;
import org.junit.rules.ExternalResource;
import org.keycloak.adapters.saml.SamlAuthenticationError;
import org.keycloak.adapters.saml.SamlPrincipal;
import org.keycloak.admin.client.Keycloak;
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.pages.LoginPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.ErrorServlet;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
@ -100,6 +102,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
}
public void testErrorHandling() throws Exception {
ErrorServlet.authError = null;
Client client = ClientBuilder.newClient();
// make sure
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();
String errorPage = response.readEntity(String.class);
response.close();
Assert.assertTrue(errorPage.contains(JBossSAMLURIConstants.STATUS_RESPONDER.get()));
Assert.assertTrue(errorPage.contains("Error Page"));
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);
}
public void testPostBadRealmSignature(CheckAuthError error) {
public void testPostBadRealmSignature() {
ErrorServlet.authError = null;
driver.navigate().to(APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
loginPage.login("bburke", "password");
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/bad-realm-sales-post-sig/");
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 {

View file

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

View file

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

View file

@ -1,5 +1,7 @@
package org.keycloak.testsuite.rule;
import org.keycloak.adapters.spi.AuthenticationError;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@ -12,10 +14,11 @@ import java.io.PrintWriter;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ErrorServlet extends HttpServlet {
public static AuthenticationError authError;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
authError = (AuthenticationError)req.getAttribute(AuthenticationError.class.getName());
resp.setContentType("text/html");
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
public void testPostBadRealmSignature() {
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
@Override
public void check(WebDriver driver) {
Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
}
});
testStrategy.testPostBadRealmSignature();
}
@Test
@ -72,7 +67,7 @@ public class SamlAdapterTest {
testStrategy.testPostSimpleUnauthorized(new SamlAdapterTestStrategy.CheckAuthError() {
@Override
public void check(WebDriver driver) {
Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
Assert.assertTrue(driver.getPageSource().contains("Error Page"));
}
});
} finally {

View file

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

View file

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

View file

@ -25,6 +25,26 @@
<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-name>Users</web-resource-name>

View file

@ -10,11 +10,40 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>

View file

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

View file

@ -25,6 +25,26 @@
<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-name>Users</web-resource-name>

View file

@ -10,12 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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-name>Users</web-resource-name>
<url-pattern>/*</url-pattern>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>

View file

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

View file

@ -25,6 +25,26 @@
<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-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>

View file

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

View file

@ -25,6 +25,26 @@
<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-name>Users</web-resource-name>
@ -44,10 +64,6 @@
<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,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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-name>Users</web-resource-name>
<url-pattern>/*</url-pattern>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>

View file

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

View file

@ -25,6 +25,26 @@
<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-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>

View file

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

View file

@ -25,6 +25,26 @@
<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-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>

View file

@ -10,11 +10,39 @@
<servlet-name>SendUsernameServlet</servlet-name>
<servlet-class>org.keycloak.testsuite.keycloaksaml.SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error Servlet</servlet-name>
<servlet-class>org.keycloak.testsuite.rule.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendUsernameServlet</servlet-name>
<url-pattern>/*</url-pattern>
</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>
<web-resource-collection>
<web-resource-name>Users</web-resource-name>