keycloak-aplcache
Changes
integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java 10(+10 -0)
integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java 15(+15 -0)
integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java 4(+2 -2)
integration/pom.xml 1(+1 -0)
integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java 304(+303 -1)
integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java 7(+7 -0)
integration/servlet-filter/pom.xml 83(+83 -0)
integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/FilterRequestAuthenticator.java 86(+86 -0)
integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java 162(+162 -0)
integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java 167(+167 -0)
integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCServletHttpFacade.java 23(+23 -0)
integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java 12(+7 -5)
pom.xml 7(+6 -1)
saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java 2(+1 -1)
saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java 90(+3 -87)
saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java 30(+20 -10)
saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java 4(+1 -3)
testsuite/integration/pom.xml 6(+5 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java 1(+1 -0)
Details
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
index 54395eb..c377b1f 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
@@ -115,6 +115,11 @@ public class BearerTokenRequestAuthenticator {
}
@Override
+ public int getResponseCode() {
+ return 0;
+ }
+
+ @Override
public boolean challenge(HttpFacade exchange) {
// do the same thing as client cert auth
return false;
@@ -140,6 +145,11 @@ public class BearerTokenRequestAuthenticator {
}
@Override
+ public int getResponseCode() {
+ return 401;
+ }
+
+ @Override
public boolean challenge(HttpFacade facade) {
facade.getResponse().setStatus(401);
facade.getResponse().addHeader("WWW-Authenticate", challenge);
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index d077d7d..c6ffce5 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -181,6 +181,11 @@ public class OAuthRequestAuthenticator {
public boolean errorPage() {
return true;
}
+
+ @Override
+ public int getResponseCode() {
+ return 403;
+ }
};
}
return new AuthChallenge() {
@@ -191,6 +196,11 @@ public class OAuthRequestAuthenticator {
}
@Override
+ public int getResponseCode() {
+ return 0;
+ }
+
+ @Override
public boolean challenge(HttpFacade exchange) {
tokenStore.saveRequest();
log.debug("Sending redirect to login page: " + redirect);
@@ -263,6 +273,11 @@ public class OAuthRequestAuthenticator {
}
@Override
+ public int getResponseCode() {
+ return code;
+ }
+
+ @Override
public boolean challenge(HttpFacade exchange) {
exchange.getResponse().setStatus(code);
return true;
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthChallenge.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthChallenge.java
index 94385f0..47c07c2 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthChallenge.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthChallenge.java
@@ -13,9 +13,17 @@ public interface AuthChallenge {
boolean challenge(HttpFacade exchange);
/**
- * Whether or not an error page should be displayed if possible
+ * 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.
+ *
+ * @return
+ */
+ int getResponseCode();
}
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java
index 1be7159..c4a8081 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java
@@ -23,6 +23,14 @@ public class InMemorySessionIdMapper implements SessionIdMapper {
}
@Override
+ public void clear() {
+ ssoToSession.clear();
+ sessionToSso.clear();
+ principalToSession.clear();
+ sessionToPrincipal.clear();
+ }
+
+ @Override
public Set<String> getUserSessions(String principal) {
Set<String> lookup = principalToSession.get(principal);
if (lookup == null) return null;
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMapper.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMapper.java
index d52e029..0f87155 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMapper.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMapper.java
@@ -9,6 +9,8 @@ import java.util.Set;
public interface SessionIdMapper {
boolean hasSession(String id);
+ void clear();
+
Set<String> getUserSessions(String principal);
String getSessionFromSSO(String sso);
diff --git a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
index c11f30a..6b0233c 100755
--- a/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
@@ -262,16 +262,16 @@ 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)));
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), errorPage)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- challenge.challenge(facade);
}
return Authentication.SEND_CONTINUE;
}
integration/pom.xml 1(+1 -0)
diff --git a/integration/pom.xml b/integration/pom.xml
index c1d3346..6257c28 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -26,6 +26,7 @@
<module>undertow-adapter-spi</module>
<module>undertow</module>
<module>wildfly</module>
+ <module>servlet-filter</module>
<module>js</module>
<module>installed</module>
<module>admin-client</module>
diff --git a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
index 51c6909..b6ddbc7 100755
--- a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
+++ b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
@@ -2,14 +2,32 @@ package org.keycloak.adapters.servlet;
import org.keycloak.adapters.AdapterSessionStore;
import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.util.Encode;
import org.keycloak.util.MultivaluedHashMap;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -24,6 +42,7 @@ public class FilterSessionStore implements AdapterSessionStore {
protected final HttpFacade facade;
protected final int maxBuffer;
protected byte[] restoredBuffer = null;
+ protected boolean needRequestRestore;
public FilterSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer) {
this.request = request;
@@ -38,6 +57,249 @@ public class FilterSessionStore implements AdapterSessionStore {
session.removeAttribute(SAVED_BODY);
}
+ public void servletRequestLogout() {
+
+ }
+
+ public static String getCharsetFromContentType(String contentType) {
+
+ if (contentType == null)
+ return (null);
+ int start = contentType.indexOf("charset=");
+ if (start < 0)
+ return (null);
+ String encoding = contentType.substring(start + 8);
+ int end = encoding.indexOf(';');
+ if (end >= 0)
+ encoding = encoding.substring(0, end);
+ encoding = encoding.trim();
+ if ((encoding.length() > 2) && (encoding.startsWith("\""))
+ && (encoding.endsWith("\"")))
+ encoding = encoding.substring(1, encoding.length() - 1);
+ return (encoding.trim());
+
+ }
+
+
+ public HttpServletRequestWrapper buildWrapper(HttpSession session, final KeycloakAccount account) {
+ if (needRequestRestore) {
+ final String method = (String)session.getAttribute(SAVED_METHOD);
+ final byte[] body = (byte[])session.getAttribute(SAVED_BODY);
+ final MultivaluedHashMap<String, String> headers = (MultivaluedHashMap<String, String>)session.getAttribute(SAVED_HEADERS);
+ clearSavedRequest(session);
+ HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
+ protected MultivaluedHashMap<String, String> parameters;
+
+ MultivaluedHashMap<String, String> getParams() {
+ if (parameters != null) return parameters;
+ String contentType = getContentType();
+ contentType = contentType.toLowerCase();
+ if (contentType.startsWith("application/x-www-form-urlencoded")) {
+ ByteArrayInputStream is = new ByteArrayInputStream(body);
+ try {
+ parameters = parseForm(is);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return parameters;
+
+ }
+ @Override
+ public boolean isUserInRole(String role) {
+ return account.getRoles().contains(role);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return account.getPrincipal();
+ }
+
+ @Override
+ public String getMethod() {
+ if (needRequestRestore) {
+ return method;
+ } else {
+ return super.getMethod();
+
+ }
+ }
+
+ @Override
+ public String getHeader(String name) {
+ if (needRequestRestore && headers != null) {
+ return headers.getFirst(name.toLowerCase());
+ }
+ return super.getHeader(name);
+ }
+
+ @Override
+ public Enumeration<String> getHeaders(String name) {
+ if (needRequestRestore && headers != null) {
+ List<String> values = headers.getList(name.toLowerCase());
+ if (values == null) return Collections.emptyEnumeration();
+ else return Collections.enumeration(values);
+ }
+ return super.getHeaders(name);
+ }
+
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ if (needRequestRestore && headers != null) {
+ return Collections.enumeration(headers.keySet());
+ }
+ return super.getHeaderNames();
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+
+ if (needRequestRestore && body != null) {
+ final ByteArrayInputStream is = new ByteArrayInputStream(body);
+ return new ServletInputStream() {
+ @Override
+ public int read() throws IOException {
+ return is.read();
+ }
+ };
+ }
+ return super.getInputStream();
+ }
+
+ @Override
+ public void logout() throws ServletException {
+ servletRequestLogout();
+ }
+
+ @Override
+ public long getDateHeader(String name) {
+ if (!needRequestRestore) return super.getDateHeader(name);
+ throw new RuntimeException("This method is not supported in a restored authenticated request");
+ }
+
+ @Override
+ public int getIntHeader(String name) {
+ if (!needRequestRestore) return super.getIntHeader(name);
+ String value = getHeader(name);
+ if (value == null) return -1;
+ return Integer.valueOf(value);
+
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ if (!needRequestRestore) return super.getParameterValues(name);
+ MultivaluedHashMap<String, String> formParams = getParams();
+ if (formParams == null) {
+ return super.getParameterValues(name);
+ }
+ String[] values = request.getParameterValues(name);
+ List<String> list = new LinkedList<>();
+ if (values != null) {
+ for (String val : values) list.add(val);
+ }
+ List<String> vals = formParams.get(name);
+ if (vals != null) list.addAll(vals);
+ return list.toArray(new String[list.size()]);
+ }
+
+ @Override
+ public Enumeration<String> getParameterNames() {
+ if (!needRequestRestore) return super.getParameterNames();
+ MultivaluedHashMap<String, String> formParams = getParams();
+ if (formParams == null) {
+ return super.getParameterNames();
+ }
+ Set<String> names = new HashSet<>();
+ Enumeration<String> qnames = super.getParameterNames();
+ while (qnames.hasMoreElements()) names.add(qnames.nextElement());
+ names.addAll(formParams.keySet());
+ return Collections.enumeration(names);
+
+ }
+
+ @Override
+ public Map<String, String[]> getParameterMap() {
+ if (!needRequestRestore) return super.getParameterMap();
+ MultivaluedHashMap<String, String> formParams = getParams();
+ if (formParams == null) {
+ return super.getParameterMap();
+ }
+ Map<String, String[]> map = new HashMap<>();
+ Enumeration<String> names = getParameterNames();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ String[] values = getParameterValues(name);
+ if (values != null) {
+ map.put(name, values);
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public String getParameter(String name) {
+ if (!needRequestRestore) return super.getParameter(name);
+ String param = super.getParameter(name);
+ if (param != null) return param;
+ MultivaluedHashMap<String, String> formParams = getParams();
+ if (formParams == null) {
+ return null;
+ }
+ return formParams.getFirst(name);
+
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ if (!needRequestRestore) return super.getReader();
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+ @Override
+ public int getContentLength() {
+ if (!needRequestRestore) return super.getContentLength();
+ String header = getHeader("content-length");
+ if (header == null) return -1;
+ return Integer.valueOf(header);
+ }
+
+ @Override
+ public String getContentType() {
+ if (!needRequestRestore) return super.getContentType();
+ return getHeader("content-type");
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ if (!needRequestRestore) return super.getCharacterEncoding();
+ return getCharsetFromContentType(getContentType());
+ }
+
+ };
+ return wrapper;
+ } else {
+ return new HttpServletRequestWrapper(request) {
+ @Override
+ public boolean isUserInRole(String role) {
+ return account.getRoles().contains(role);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return account.getPrincipal();
+ }
+
+ @Override
+ public void logout() throws ServletException {
+ servletRequestLogout();
+ }
+
+
+ };
+ }
+ }
+
public String getRedirectUri() {
HttpSession session = request.getSession(true);
return (String)session.getAttribute(REDIRECT_URI);
@@ -50,6 +312,44 @@ public class FilterSessionStore implements AdapterSessionStore {
return session.getAttribute(REDIRECT_URI) != null;
}
+ public static MultivaluedHashMap<String, String> parseForm(InputStream entityStream)
+ throws IOException
+ {
+ char[] buffer = new char[100];
+ StringBuffer buf = new StringBuffer();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(entityStream));
+
+ int wasRead = 0;
+ do
+ {
+ wasRead = reader.read(buffer, 0, 100);
+ if (wasRead > 0) buf.append(buffer, 0, wasRead);
+ } while (wasRead > -1);
+
+ String form = buf.toString();
+
+ MultivaluedHashMap<String, String> formData = new MultivaluedHashMap<String, String>();
+ if ("".equals(form)) return formData;
+
+ String[] params = form.split("&");
+
+ for (String param : params)
+ {
+ if (param.indexOf('=') >= 0)
+ {
+ String[] nv = param.split("=");
+ String val = nv.length > 1 ? nv[1] : "";
+ formData.add(Encode.decode(nv[0]), Encode.decode(val));
+ }
+ else
+ {
+ formData.add(Encode.decode(param), "");
+ }
+ }
+ return formData;
+ }
+
+
@Override
public void saveRequest() {
@@ -62,7 +362,7 @@ public class FilterSessionStore implements AdapterSessionStore {
String name = names.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
- headers.add(name, values.nextElement());
+ headers.add(name.toLowerCase(), values.nextElement());
}
}
session.setAttribute(SAVED_HEADERS, headers);
@@ -93,6 +393,8 @@ public class FilterSessionStore implements AdapterSessionStore {
if (body.length > 0) {
session.setAttribute(SAVED_BODY, body);
}
+
+
}
}
diff --git a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
index bc4eaa3..f12c9ca 100755
--- a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
+++ b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java
@@ -67,6 +67,13 @@ public class ServletHttpFacade implements HttpFacade {
return queryParameters.getFirst(param);
}
+ public MultivaluedHashMap<String, String> getQueryParameters() {
+ if (queryParameters == null) {
+ queryParameters = UriUtils.decodeQueryString(request.getQueryString());
+ }
+ return queryParameters;
+ }
+
@Override
public Cookie getCookie(String cookieName) {
if (request.getCookies() == null) return null;
integration/servlet-filter/pom.xml 83(+83 -0)
diff --git a/integration/servlet-filter/pom.xml b/integration/servlet-filter/pom.xml
new file mode 100755
index 0000000..8448d59
--- /dev/null
+++ b/integration/servlet-filter/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.6.0.Final-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-servlet-filter-adapter</artifactId>
+ <name>Keycloak Servlet Filter Adapter Integration</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-servlet-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-core-asl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-xc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/FilterRequestAuthenticator.java b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/FilterRequestAuthenticator.java
new file mode 100755
index 0000000..70cb7e9
--- /dev/null
+++ b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/FilterRequestAuthenticator.java
@@ -0,0 +1,86 @@
+package org.keycloak.adapters.servlet;
+
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import java.security.Principal;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class FilterRequestAuthenticator extends RequestAuthenticator {
+ private static final Logger log = Logger.getLogger(""+FilterRequestAuthenticator.class);
+ protected HttpServletRequest request;
+
+ public FilterRequestAuthenticator(KeycloakDeployment deployment,
+ AdapterTokenStore tokenStore,
+ OIDCHttpFacade facade,
+ HttpServletRequest request,
+ int sslRedirectPort) {
+ super(facade, deployment, tokenStore, sslRedirectPort);
+ this.request = request;
+ }
+
+ @Override
+ protected OAuthRequestAuthenticator createOAuthAuthenticator() {
+ return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
+ }
+
+ @Override
+ protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
+ final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+ final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+ OidcKeycloakAccount account = new OidcKeycloakAccount() {
+
+ @Override
+ public Principal getPrincipal() {
+ return skp;
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return roles;
+ }
+
+ @Override
+ public KeycloakSecurityContext getKeycloakSecurityContext() {
+ return securityContext;
+ }
+
+ };
+
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ this.tokenStore.saveAccountInfo(account);
+ }
+
+ @Override
+ protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
+ RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
+ Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+ if (log.isLoggable(Level.FINE)) {
+ log.fine("Completing bearer authentication. Bearer roles: " + roles);
+ }
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ }
+
+ @Override
+ protected String getHttpSessionId(boolean create) {
+ HttpSession session = request.getSession(create);
+ return session != null ? session.getId() : null;
+ }
+
+}
diff --git a/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java
new file mode 100755
index 0000000..1dab8c3
--- /dev/null
+++ b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java
@@ -0,0 +1,162 @@
+package org.keycloak.adapters.servlet;
+
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AuthChallenge;
+import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
+import org.keycloak.adapters.InMemorySessionIdMapper;
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.PreAuthActionsHandler;
+import org.keycloak.adapters.SessionIdMapper;
+import org.keycloak.adapters.UserSessionManagement;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakOIDCFilter implements Filter {
+ protected AdapterDeploymentContext deploymentContext;
+ protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
+ protected NodesRegistrationManagement nodesRegistrationManagement;
+ private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
+
+ @Override
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
+ if (configResolverClass != null) {
+ try {
+ KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
+ deploymentContext = new AdapterDeploymentContext(configResolver);
+ log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
+ } catch (Exception ex) {
+ log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
+ deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+ }
+ } else {
+ String fp = filterConfig.getInitParameter("keycloak.config.file");
+ InputStream is = null;
+ if (fp != null) {
+ try {
+ is = new FileInputStream(fp);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ String path = "/WEB-INF/keycloak.json";
+ String pathParam = filterConfig.getInitParameter("keycloak.config.path");
+ if (pathParam != null) path = pathParam;
+ is = filterConfig.getServletContext().getResourceAsStream(path);
+ }
+ KeycloakDeployment kd;
+ if (is == null) {
+ log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
+ kd = new KeycloakDeployment();
+ } else {
+ kd = KeycloakDeploymentBuilder.build(is);
+ }
+ deploymentContext = new AdapterDeploymentContext(kd);
+ log.fine("Keycloak is using a per-deployment configuration.");
+ }
+ filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
+ nodesRegistrationManagement = new NodesRegistrationManagement();
+ }
+
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
+ OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (deployment == null || !deployment.isConfigured()) {
+ response.sendError(403);
+ log.fine("deployment not configured");
+ return;
+ }
+
+ PreAuthActionsHandler preActions = new PreAuthActionsHandler(new UserSessionManagement() {
+ @Override
+ public void logoutAll() {
+ if (idMapper != null) {
+ idMapper.clear();
+ }
+ }
+
+ @Override
+ public void logoutHttpSessions(List<String> ids) {
+ for (String id : ids) {
+ idMapper.removeSession(id);
+ }
+
+ }
+ }, deploymentContext, facade);
+
+ if (preActions.handleRequest()) {
+ return;
+ }
+
+
+ nodesRegistrationManagement.tryRegister(deployment);
+ OIDCFilterSessionStore tokenStore = new OIDCFilterSessionStore(request, facade, 100000, deployment, idMapper);
+ tokenStore.checkCurrentToken();
+
+
+ FilterRequestAuthenticator authenticator = new FilterRequestAuthenticator(deployment, tokenStore, facade, request, 8443);
+ AuthOutcome outcome = authenticator.authenticate();
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ log.fine("AUTHENTICATED");
+ if (facade.isEnded()) {
+ return;
+ }
+ AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(deployment, facade);
+ if (actions.handledRequest()) {
+ return;
+ } else {
+ HttpServletRequestWrapper wrapper = tokenStore.buildWrapper();
+ chain.doFilter(wrapper, res);
+ return;
+ }
+ }
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ log.fine("challenge");
+ challenge.challenge(facade);
+ if (challenge.errorPage()) {
+ response.sendError(challenge.getResponseCode());
+ return;
+ }
+ log.fine("sending challenge");
+ return;
+ }
+ response.sendError(403);
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
new file mode 100755
index 0000000..bab5cbe
--- /dev/null
+++ b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
@@ -0,0 +1,167 @@
+package org.keycloak.adapters.servlet;
+
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.adapters.SessionIdMapper;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCFilterSessionStore extends FilterSessionStore implements AdapterTokenStore {
+ protected final KeycloakDeployment deployment;
+ private static final Logger log = Logger.getLogger("" + OIDCFilterSessionStore.class);
+ protected final SessionIdMapper idMapper;
+
+ public OIDCFilterSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer, KeycloakDeployment deployment, SessionIdMapper idMapper) {
+ super(request, facade, maxBuffer);
+ this.deployment = deployment;
+ this.idMapper = idMapper;
+ }
+
+ public HttpServletRequestWrapper buildWrapper() {
+ HttpSession session = request.getSession();
+ KeycloakAccount account = (KeycloakAccount)session.getAttribute((KeycloakAccount.class.getName()));
+ return buildWrapper(session, account);
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ HttpSession httpSession = request.getSession(false);
+ if (httpSession == null) return;
+ SerializableKeycloakAccount account = (SerializableKeycloakAccount)httpSession.getAttribute(KeycloakAccount.class.getName());
+ if (account == null) {
+ return;
+ }
+
+ RefreshableKeycloakSecurityContext session = account.getKeycloakSecurityContext();
+ if (session == null) return;
+
+ // just in case session got serialized
+ if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
+
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
+
+ // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
+ // not be updated
+ boolean success = session.refreshExpiredToken(false);
+ if (success && session.isActive()) return;
+
+ // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
+ //log.fine("Cleanup and expire session " + httpSession.getId() + " after failed refresh");
+ cleanSession(httpSession);
+ httpSession.invalidate();
+ }
+
+ protected void cleanSession(HttpSession session) {
+ session.removeAttribute(KeycloakAccount.class.getName());
+ clearSavedRequest(session);
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ HttpSession httpSession = request.getSession(false);
+ if (httpSession == null) return false;
+ SerializableKeycloakAccount account = (SerializableKeycloakAccount) httpSession.getAttribute(KeycloakAccount.class.getName());
+ if (account == null) {
+ return false;
+ }
+
+ log.fine("remote logged in already. Establish state from session");
+
+ RefreshableKeycloakSecurityContext securityContext = account.getKeycloakSecurityContext();
+
+ if (!deployment.getRealm().equals(securityContext.getRealm())) {
+ log.fine("Account from cookie is from a different realm than for the request.");
+ cleanSession(httpSession);
+ return false;
+ }
+
+ if (idMapper != null && !idMapper.hasSession(httpSession.getId())) {
+ cleanSession(httpSession);
+ return false;
+ }
+
+
+ securityContext.setCurrentRequestInfo(deployment, this);
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ needRequestRestore = restoreRequest();
+ return true;
+ }
+
+ public static class SerializableKeycloakAccount implements OidcKeycloakAccount, Serializable {
+ protected Set<String> roles;
+ protected Principal principal;
+ protected RefreshableKeycloakSecurityContext securityContext;
+
+ public SerializableKeycloakAccount(Set<String> roles, Principal principal, RefreshableKeycloakSecurityContext securityContext) {
+ this.roles = roles;
+ this.principal = principal;
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ public Principal getPrincipal() {
+ return principal;
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return roles;
+ }
+
+ @Override
+ public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
+ return securityContext;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(OidcKeycloakAccount account) {
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext();
+ Set<String> roles = account.getRoles();
+
+ SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext);
+ HttpSession httpSession = request.getSession();
+ httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
+ if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
+ //String username = securityContext.getToken().getSubject();
+ //log.fine("userSessionManagement.login: " + username);
+ }
+
+ @Override
+ public void logout() {
+ HttpSession httpSession = request.getSession(false);
+ if (httpSession != null) {
+ SerializableKeycloakAccount account = (SerializableKeycloakAccount) httpSession.getAttribute(KeycloakAccount.class.getName());
+ if (account != null) {
+ account.getKeycloakSecurityContext().logout(deployment);
+ }
+ cleanSession(httpSession);
+ }
+ }
+
+ @Override
+ public void servletRequestLogout() {
+ logout();
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ // no-op
+ }
+}
diff --git a/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCServletHttpFacade.java b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCServletHttpFacade.java
new file mode 100755
index 0000000..1232625
--- /dev/null
+++ b/integration/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCServletHttpFacade.java
@@ -0,0 +1,23 @@
+package org.keycloak.adapters.servlet;
+
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.OIDCHttpFacade;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCServletHttpFacade extends ServletHttpFacade implements OIDCHttpFacade {
+
+ public OIDCServletHttpFacade(HttpServletRequest request, HttpServletResponse response) {
+ super(request, response);
+ }
+
+ @Override
+ public KeycloakSecurityContext getSecurityContext() {
+ return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+ }
+}
diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
index 3347978..51b89f8 100755
--- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
+++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
@@ -77,6 +77,13 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
request.setUserPrincipal(null);
}
+ protected void beforeStop() {
+ if (nodesRegistrationManagement != null) {
+ nodesRegistrationManagement.stop();
+ }
+ }
+
+
@SuppressWarnings("UseSpecificCatch")
public void keycloakInit() {
// Possible scenarios:
@@ -119,11 +126,6 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
nodesRegistrationManagement = new NodesRegistrationManagement();
}
- protected void beforeStop() {
- if (nodesRegistrationManagement != null) {
- nodesRegistrationManagement.stop();
- }
- }
private static InputStream getJSONFromServletContext(ServletContext servletContext) {
String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
pom.xml 7(+6 -1)
diff --git a/pom.xml b/pom.xml
index 1a22d53..65968cb 100755
--- a/pom.xml
+++ b/pom.xml
@@ -894,7 +894,12 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-server-filter-adapter</artifactId>
+ <artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-servlet-filter-adapter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
index bcba3d0..c0f8dc4 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
@@ -32,6 +32,11 @@ public class InitiateLogin implements AuthChallenge {
}
@Override
+ public int getResponseCode() {
+ return 0;
+ }
+
+ @Override
public boolean challenge(HttpFacade httpFacade) {
try {
String issuerURL = deployment.getEntityID();
diff --git a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
index a63b934..ec97fc9 100755
--- a/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
+++ b/saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
@@ -259,6 +259,7 @@ 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 {
@@ -268,7 +269,6 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
}
}
- challenge.challenge(facade);
}
return Authentication.SEND_CONTINUE;
}
diff --git a/saml/client-adapter/servlet-filter/pom.xml b/saml/client-adapter/servlet-filter/pom.xml
index 219110c..c2de715 100755
--- a/saml/client-adapter/servlet-filter/pom.xml
+++ b/saml/client-adapter/servlet-filter/pom.xml
@@ -9,7 +9,7 @@
</parent>
<modelVersion>4.0.0</modelVersion>
- <artifactId>keycloak-saml-server-filter-adapter</artifactId>
+ <artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
<name>Keycloak SAML Servlet Filter</name>
<description />
diff --git a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java
index dc4cd32..2d3a7fd 100755
--- a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java
+++ b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java
@@ -2,6 +2,7 @@ package org.keycloak.adapters.saml.servlet;
import org.jboss.logging.Logger;
import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
import org.keycloak.adapters.SessionIdMapper;
import org.keycloak.adapters.saml.SamlSession;
import org.keycloak.adapters.saml.SamlSessionStore;
@@ -34,8 +35,6 @@ public class FilterSamlSessionStore extends FilterSessionStore implements SamlSe
this.idMapper = idMapper;
}
- protected boolean needRequestRestore;
-
@Override
public void logoutAccount() {
HttpSession session = request.getSession(false);
@@ -107,91 +106,8 @@ public class FilterSamlSessionStore extends FilterSessionStore implements SamlSe
public HttpServletRequestWrapper getWrap() {
HttpSession session = request.getSession(true);
final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
- if (needRequestRestore) {
- final String method = (String)session.getAttribute(SAVED_METHOD);
- final byte[] body = (byte[])session.getAttribute(SAVED_BODY);
- final MultivaluedHashMap<String, String> headers = (MultivaluedHashMap<String, String>)session.getAttribute(SAVED_HEADERS);
- clearSavedRequest(session);
- HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
- @Override
- public boolean isUserInRole(String role) {
- return samlSession.getRoles().contains(role);
- }
-
- @Override
- public Principal getUserPrincipal() {
- return samlSession.getPrincipal();
- }
-
- @Override
- public String getMethod() {
- if (needRequestRestore) {
- return method;
- } else {
- return super.getMethod();
-
- }
- }
-
- @Override
- public String getHeader(String name) {
- if (needRequestRestore && headers != null) {
- return headers.getFirst(name);
- }
- return super.getHeader(name);
- }
-
- @Override
- public Enumeration<String> getHeaders(String name) {
- if (needRequestRestore && headers != null) {
- List<String> values = headers.getList(name);
- if (values == null) return Collections.emptyEnumeration();
- else return Collections.enumeration(values);
- }
- return super.getHeaders(name);
- }
-
- @Override
- public Enumeration<String> getHeaderNames() {
- if (needRequestRestore && headers != null) {
- return Collections.enumeration(headers.keySet());
- }
- return super.getHeaderNames();
- }
-
- @Override
- public ServletInputStream getInputStream() throws IOException {
-
- if (needRequestRestore && body != null) {
- final ByteArrayInputStream is = new ByteArrayInputStream(body);
- return new ServletInputStream() {
- @Override
- public int read() throws IOException {
- return is.read();
- }
- };
- }
- return super.getInputStream();
- }
- };
- return wrapper;
- } else {
- return new HttpServletRequestWrapper(request) {
- @Override
- public boolean isUserInRole(String role) {
- return samlSession.getRoles().contains(role);
- }
-
- @Override
- public Principal getUserPrincipal() {
- return samlSession.getPrincipal();
- }
-
- };
- }
-
-
-
+ final KeycloakAccount account = samlSession;
+ return buildWrapper(session, account);
}
@Override
diff --git a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
index c1b6ad5..8188066 100755
--- a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
+++ b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
@@ -24,6 +24,8 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
@@ -52,10 +54,20 @@ public class SamlFilter implements Filter {
//deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
}
} else {
- String path = "/WEB-INF/keycloak-saml.xml";
- String pathParam = filterConfig.getInitParameter("keycloak.config.file");
- if (pathParam != null) path = pathParam;
- InputStream is = filterConfig.getServletContext().getResourceAsStream(path);
+ String fp = filterConfig.getInitParameter("keycloak.config.file");
+ InputStream is = null;
+ if (fp != null) {
+ try {
+ is = new FileInputStream(fp);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ String path = "/WEB-INF/keycloak-saml.xml";
+ String pathParam = filterConfig.getInitParameter("keycloak.config.path");
+ if (pathParam != null) path = pathParam;
+ is = filterConfig.getServletContext().getResourceAsStream(path);
+ }
final SamlDeployment deployment;
if (is == null) {
log.info("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
@@ -124,18 +136,16 @@ public class SamlFilter implements Filter {
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
log.fine("challenge");
+ challenge.challenge(facade);
if (challenge.errorPage()) {
- response.sendError(403);
+ response.sendError(challenge.getResponseCode());
return;
}
log.fine("sending challenge");
- challenge.challenge(facade);
+ return;
}
- if (outcome == AuthOutcome.FAILED) {
+ if (!facade.isEnded()) {
response.sendError(403);
- } else if (!facade.isEnded()) {
- chain.doFilter(req, res);
- return;
}
}
diff --git a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
index febbb72..cfc21a0 100755
--- a/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
+++ b/saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
@@ -212,14 +212,12 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
if (loginConfig == null) {
loginConfig = request.getContext().getLoginConfig();
}
+ challenge.challenge(facade);
if (challenge.errorPage()) {
log.fine("error page");
if (forwardToErrorPageInternal(request, response, loginConfig))return false;
}
- log.fine("sending challenge");
- challenge.challenge(facade);
}
- log.fine("No challenge, but failed authentication");
return false;
}
testsuite/integration/pom.xml 6(+5 -1)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 7587247..dad4114 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -107,7 +107,11 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-server-filter-adapter</artifactId>
+ <artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-servlet-filter-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
index a63132f..d5f9072 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
@@ -604,6 +604,7 @@ public class AdapterTestStrategy extends ExternalResource {
// logout sessions in account management
accountSessionsPage.realm("demo");
accountSessionsPage.open();
+ Assert.assertTrue(accountSessionsPage.isCurrent());
accountSessionsPage.logoutAll();
// Assert I need to login again (logout was propagated to the app)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
new file mode 100755
index 0000000..39c0368
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
@@ -0,0 +1,214 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.adapter;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+
+import java.net.URL;
+import java.security.PublicKey;
+
+/**
+ * Tests Undertow Adapter
+ *
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+public class FilterAdapterTest {
+
+ public static PublicKey realmPublicKey;
+ @ClassRule
+ public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+ @Override
+ protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+ RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
+ realmPublicKey = realm.getPublicKey();
+
+ URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
+ createApplicationDeployment()
+ .name("customer-portal").contextPath("/customer-portal")
+ .servletClass(CustomerServlet.class).adapterConfigPath(url.getPath())
+ .role("user").deployApplicationWithFilter();
+
+ url = getClass().getResource("/adapter-test/secure-portal-keycloak.json");
+ createApplicationDeployment()
+ .name("secure-portal").contextPath("/secure-portal")
+ .servletClass(CallAuthenticatedServlet.class).adapterConfigPath(url.getPath())
+ .role("user")
+ .isConstrained(false).deployApplicationWithFilter();
+
+ url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
+ createApplicationDeployment()
+ .name("customer-db").contextPath("/customer-db")
+ .servletClass(CustomerDatabaseServlet.class).adapterConfigPath(url.getPath())
+ .role("user")
+ .errorPage(null).deployApplicationWithFilter();
+
+ createApplicationDeployment()
+ .name("customer-db-error-page").contextPath("/customer-db-error-page")
+ .servletClass(CustomerDatabaseServlet.class).adapterConfigPath(url.getPath())
+ .role("user").deployApplicationWithFilter();
+
+ url = getClass().getResource("/adapter-test/product-keycloak.json");
+ createApplicationDeployment()
+ .name("product-portal").contextPath("/product-portal")
+ .servletClass(ProductServlet.class).adapterConfigPath(url.getPath())
+ .role("user").deployApplicationWithFilter();
+
+ // Test that replacing system properties works for adapters
+ System.setProperty("app.server.base.url", "http://localhost:8081");
+ System.setProperty("my.host.name", "localhost");
+ url = getClass().getResource("/adapter-test/session-keycloak.json");
+ createApplicationDeployment()
+ .name("session-portal").contextPath("/session-portal")
+ .servletClass(SessionServlet.class).adapterConfigPath(url.getPath())
+ .role("user").deployApplicationWithFilter();
+
+ url = getClass().getResource("/adapter-test/input-keycloak.json");
+ createApplicationDeployment()
+ .name("input-portal").contextPath("/input-portal")
+ .servletClass(InputServlet.class).adapterConfigPath(url.getPath())
+ .role("user").constraintUrl("/secured/*").deployApplicationWithFilter();
+ }
+ };
+
+ @Rule
+ public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8081", keycloakRule);
+
+ @Test
+ public void testLoginSSOAndLogout() throws Exception {
+ testStrategy.testLoginSSOAndLogout();
+ }
+
+ @Test
+ public void testSavedPostRequest() throws Exception {
+ testStrategy.testSavedPostRequest();
+ }
+
+ @Test
+ public void testServletRequestLogout() throws Exception {
+ testStrategy.testServletRequestLogout();
+ }
+
+ @Test
+ public void testLoginSSOIdle() throws Exception {
+ testStrategy.testLoginSSOIdle();
+
+ }
+
+ @Test
+ public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception {
+ testStrategy.testLoginSSOIdleRemoveExpiredUserSessions();
+ }
+
+ @Test
+ public void testLoginSSOMax() throws Exception {
+ testStrategy.testLoginSSOMax();
+ }
+
+ /**
+ * KEYCLOAK-518
+ * @throws Exception
+ */
+ @Test
+ public void testNullBearerToken() throws Exception {
+ testStrategy.testNullBearerToken();
+ }
+
+ /**
+ * KEYCLOAK-1368
+ * @throws Exception
+ */
+ /*
+ can't test because of the way filter works
+ @Test
+ public void testNullBearerTokenCustomErrorPage() throws Exception {
+ testStrategy.testNullBearerTokenCustomErrorPage();
+ }
+ */
+
+ /**
+ * KEYCLOAK-518
+ * @throws Exception
+ */
+ @Test
+ public void testBadUser() throws Exception {
+ testStrategy.testBadUser();
+ }
+
+ @Test
+ public void testVersion() throws Exception {
+ testStrategy.testVersion();
+ }
+
+ /*
+ Don't need to test this because HttpServletRequest.authenticate doesn't make sense with filter implementation
+
+ @Test
+ public void testAuthenticated() throws Exception {
+ testStrategy.testAuthenticated();
+ }
+ */
+
+ /**
+ * KEYCLOAK-732
+ *
+ * @throws Throwable
+ */
+ @Test
+ public void testSingleSessionInvalidated() throws Throwable {
+ testStrategy.testSingleSessionInvalidated();
+ }
+
+ /**
+ * KEYCLOAK-741
+ */
+ @Test
+ public void testSessionInvalidatedAfterFailedRefresh() throws Throwable {
+ testStrategy.testSessionInvalidatedAfterFailedRefresh();
+
+ }
+
+ /**
+ * KEYCLOAK-942
+ */
+ @Test
+ public void testAdminApplicationLogout() throws Throwable {
+ testStrategy.testAdminApplicationLogout();
+ }
+
+ /**
+ * KEYCLOAK-1216
+ */
+ /*
+ Can't test this because backchannel logout for filter does not invalidate the session
+ @Test
+ public void testAccountManagementSessionsLogout() throws Throwable {
+ testStrategy.testAccountManagementSessionsLogout();
+ }
+ */
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
index d18b615..8c673a0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
@@ -1,6 +1,7 @@
package org.keycloak.testsuite.rule;
import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.FilterInfo;
import io.undertow.servlet.api.LoginConfig;
import io.undertow.servlet.api.SecurityConstraint;
import io.undertow.servlet.api.SecurityInfo;
@@ -11,6 +12,8 @@ import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import org.keycloak.Config;
import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.saml.servlet.SamlFilter;
+import org.keycloak.adapters.servlet.KeycloakOIDCFilter;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
@@ -24,6 +27,7 @@ import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.Time;
+import javax.servlet.DispatcherType;
import javax.servlet.Servlet;
import javax.ws.rs.core.Application;
import java.io.ByteArrayOutputStream;
@@ -350,6 +354,22 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
server.getServer().deploy(di);
}
+ public void deployApplicationWithFilter() {
+ DeploymentInfo di = createDeploymentInfo(name, contextPath, servletClass);
+ FilterInfo filter = new FilterInfo("keycloak-filter", KeycloakOIDCFilter.class);
+ if (null == keycloakConfigResolver) {
+ filter.addInitParam("keycloak.config.file", adapterConfigPath);
+ } else {
+ filter.addInitParam("keycloak.config.resolver", keycloakConfigResolver.getCanonicalName());
+ }
+ di.addFilter(filter);
+ di.addFilterUrlMapping("keycloak-filter", constraintUrl, DispatcherType.REQUEST);
+ server.getServer().deploy(di);
+
+
+
+ }
+
}
}