SAML SP Filter
This commit is contained in:
parent
0b83325470
commit
7c600e2f4b
18 changed files with 1089 additions and 2 deletions
|
@ -1,6 +1,7 @@
|
||||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
||||||
"http://www.docbook.org/xml/4.4/docbookx.dtd"
|
"http://www.docbook.org/xml/4.4/docbookx.dtd"
|
||||||
[
|
[
|
||||||
|
<!ENTITY Overview SYSTEM "modules/overview.xml">
|
||||||
<!ENTITY AdapterConfig SYSTEM "modules/adapter-config.xml">
|
<!ENTITY AdapterConfig SYSTEM "modules/adapter-config.xml">
|
||||||
<!ENTITY JBossAdapter SYSTEM "modules/jboss-adapter.xml">
|
<!ENTITY JBossAdapter SYSTEM "modules/jboss-adapter.xml">
|
||||||
<!ENTITY TomcatAdapter SYSTEM "modules/tomcat-adapter.xml">
|
<!ENTITY TomcatAdapter SYSTEM "modules/tomcat-adapter.xml">
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
|
|
||||||
<bookinfo>
|
<bookinfo>
|
||||||
<title>Keycloak SAML Client Adapter Reference Guide</title>
|
<title>Keycloak SAML Client Adapter Reference Guide</title>
|
||||||
<subtitle>SAML 2.0 Client Adapters for Java Applications</subtitle>
|
<subtitle>SAML 2.0 Client Adapters</subtitle>
|
||||||
<releaseinfo>&project.version;</releaseinfo>
|
<releaseinfo>&project.version;</releaseinfo>
|
||||||
</bookinfo>
|
</bookinfo>
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ This one is short
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</preface>
|
</preface>
|
||||||
|
&Overview;
|
||||||
&AdapterConfig;
|
&AdapterConfig;
|
||||||
&JBossAdapter;
|
&JBossAdapter;
|
||||||
&TomcatAdapter;
|
&TomcatAdapter;
|
||||||
|
|
9
docbook/saml-adapter-docs/reference/en/en-US/modules/overview.xml
Executable file
9
docbook/saml-adapter-docs/reference/en/en-US/modules/overview.xml
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
<chapter>
|
||||||
|
<title>Overview</title>
|
||||||
|
<para>
|
||||||
|
This document describes the Keycloak SAML client adapter and how it can be configured for a variety of platforms.
|
||||||
|
The Keycloak SAML client adapter is a standalone component that provides generic SAML 2.0 support for your web applications.
|
||||||
|
There are no Keycloak server extensions built into it. As long as the IDP you are talking to supports standard SAML, the
|
||||||
|
Keycloak SAML client adapter should be able to integrate with it.
|
||||||
|
</para>
|
||||||
|
</chapter>
|
|
@ -17,6 +17,11 @@ public class InMemorySessionIdMapper implements SessionIdMapper {
|
||||||
ConcurrentHashMap<String, Set<String>> principalToSession = new ConcurrentHashMap<>();
|
ConcurrentHashMap<String, Set<String>> principalToSession = new ConcurrentHashMap<>();
|
||||||
ConcurrentHashMap<String, String> sessionToPrincipal = new ConcurrentHashMap<>();
|
ConcurrentHashMap<String, String> sessionToPrincipal = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSession(String id) {
|
||||||
|
return sessionToSso.containsKey(id) || sessionToPrincipal.containsKey(id);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getUserSessions(String principal) {
|
public Set<String> getUserSessions(String principal) {
|
||||||
Set<String> lookup = principalToSession.get(principal);
|
Set<String> lookup = principalToSession.get(principal);
|
||||||
|
|
|
@ -7,6 +7,8 @@ import java.util.Set;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface SessionIdMapper {
|
public interface SessionIdMapper {
|
||||||
|
boolean hasSession(String id);
|
||||||
|
|
||||||
Set<String> getUserSessions(String principal);
|
Set<String> getUserSessions(String principal);
|
||||||
|
|
||||||
String getSessionFromSSO(String sso);
|
String getSessionFromSSO(String sso);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>adapter-spi</module>
|
<module>adapter-spi</module>
|
||||||
|
<module>servlet-adapter-spi</module>
|
||||||
<module>adapter-core</module>
|
<module>adapter-core</module>
|
||||||
<module>jaxrs-oauth-client</module>
|
<module>jaxrs-oauth-client</module>
|
||||||
<module>servlet-oauth-client</module>
|
<module>servlet-oauth-client</module>
|
||||||
|
|
53
integration/servlet-adapter-spi/pom.xml
Executable file
53
integration/servlet-adapter-spi/pom.xml
Executable file
|
@ -0,0 +1,53 @@
|
||||||
|
<?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-adapter-spi</artifactId>
|
||||||
|
<name>Keycloak Servlet Integration</name>
|
||||||
|
<description/>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-common</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>
|
|
@ -0,0 +1,98 @@
|
||||||
|
package org.keycloak.adapters.servlet;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.AdapterSessionStore;
|
||||||
|
import org.keycloak.adapters.HttpFacade;
|
||||||
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class FilterSessionStore implements AdapterSessionStore {
|
||||||
|
public static final String REDIRECT_URI = "__REDIRECT_URI";
|
||||||
|
public static final String SAVED_METHOD = "__SAVED_METHOD";
|
||||||
|
public static final String SAVED_HEADERS = "__SAVED_HEADERS";
|
||||||
|
public static final String SAVED_BODY = "__SAVED_BODY";
|
||||||
|
protected final HttpServletRequest request;
|
||||||
|
protected final HttpFacade facade;
|
||||||
|
protected final int maxBuffer;
|
||||||
|
protected byte[] restoredBuffer = null;
|
||||||
|
|
||||||
|
public FilterSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer) {
|
||||||
|
this.request = request;
|
||||||
|
this.facade = facade;
|
||||||
|
this.maxBuffer = maxBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSavedRequest(HttpSession session) {
|
||||||
|
session.removeAttribute(REDIRECT_URI);
|
||||||
|
session.removeAttribute(SAVED_METHOD);
|
||||||
|
session.removeAttribute(SAVED_HEADERS);
|
||||||
|
session.removeAttribute(SAVED_BODY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRedirectUri() {
|
||||||
|
HttpSession session = request.getSession(true);
|
||||||
|
return (String)session.getAttribute(REDIRECT_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean restoreRequest() {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) return false;
|
||||||
|
return session.getAttribute(REDIRECT_URI) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveRequest() {
|
||||||
|
HttpSession session = request.getSession(true);
|
||||||
|
session.setAttribute(REDIRECT_URI, facade.getRequest().getURI());
|
||||||
|
session.setAttribute(SAVED_METHOD, request.getMethod());
|
||||||
|
MultivaluedHashMap<String, String> headers = new MultivaluedHashMap<>();
|
||||||
|
Enumeration<String> names = request.getHeaderNames();
|
||||||
|
while (names.hasMoreElements()) {
|
||||||
|
String name = names.nextElement();
|
||||||
|
Enumeration<String> values = request.getHeaders(name);
|
||||||
|
while (values.hasMoreElements()) {
|
||||||
|
headers.add(name, values.nextElement());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.setAttribute(SAVED_HEADERS, headers);
|
||||||
|
if (request.getMethod().equalsIgnoreCase("GET")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
int totalRead = 0;
|
||||||
|
try {
|
||||||
|
InputStream is = request.getInputStream();
|
||||||
|
|
||||||
|
while ( (bytesRead = is.read(buffer) ) >= 0) {
|
||||||
|
os.write(buffer);
|
||||||
|
totalRead += bytesRead;
|
||||||
|
if (totalRead > maxBuffer) {
|
||||||
|
throw new RuntimeException("max buffer reached on a saved request");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
byte[] body = os.toByteArray();
|
||||||
|
// Only save the request body if there is something to save
|
||||||
|
if (body.length > 0) {
|
||||||
|
session.setAttribute(SAVED_BODY, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
package org.keycloak.adapters.servlet;
|
||||||
|
|
||||||
|
import org.bouncycastle.ocsp.Req;
|
||||||
|
import org.keycloak.adapters.HttpFacade;
|
||||||
|
import org.keycloak.util.KeycloakUriBuilder;
|
||||||
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
|
import org.keycloak.util.ServerCookie;
|
||||||
|
import org.keycloak.util.UriUtils;
|
||||||
|
|
||||||
|
import javax.security.cert.X509Certificate;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class ServletHttpFacade implements HttpFacade {
|
||||||
|
protected final RequestFacade requestFacade = new RequestFacade();
|
||||||
|
protected final ResponseFacade responseFacade = new ResponseFacade();
|
||||||
|
protected HttpServletRequest request;
|
||||||
|
protected HttpServletResponse response;
|
||||||
|
protected MultivaluedHashMap<String, String> queryParameters;
|
||||||
|
|
||||||
|
public ServletHttpFacade(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
this.request = request;
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class RequestFacade implements Request {
|
||||||
|
@Override
|
||||||
|
public String getMethod() {
|
||||||
|
return request.getMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getURI() {
|
||||||
|
StringBuffer buf = request.getRequestURL();
|
||||||
|
if (request.getQueryString() != null) {
|
||||||
|
buf.append('?').append(request.getQueryString());
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSecure() {
|
||||||
|
return request.isSecure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFirstParam(String param) {
|
||||||
|
return request.getParameter(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQueryParamValue(String param) {
|
||||||
|
if (queryParameters == null) {
|
||||||
|
queryParameters = UriUtils.decodeQueryString(request.getQueryString());
|
||||||
|
}
|
||||||
|
return queryParameters.getFirst(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cookie getCookie(String cookieName) {
|
||||||
|
if (request.getCookies() == null) return null;
|
||||||
|
javax.servlet.http.Cookie cookie = null;
|
||||||
|
for (javax.servlet.http.Cookie c : request.getCookies()) {
|
||||||
|
if (c.getName().equals(cookieName)) {
|
||||||
|
cookie = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cookie == null) return null;
|
||||||
|
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader(String name) {
|
||||||
|
return request.getHeader(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaders(String name) {
|
||||||
|
Enumeration<String> values = request.getHeaders(name);
|
||||||
|
List<String> list = new LinkedList<>();
|
||||||
|
while (values.hasMoreElements()) list.add(values.nextElement());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() {
|
||||||
|
try {
|
||||||
|
return request.getInputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRemoteAddr() {
|
||||||
|
return request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public boolean isEnded() {
|
||||||
|
return responseFacade.isEnded();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ResponseFacade implements Response {
|
||||||
|
protected boolean ended;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatus(int status) {
|
||||||
|
response.setStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeader(String name, String value) {
|
||||||
|
response.addHeader(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeader(String name, String value) {
|
||||||
|
response.setHeader(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetCookie(String name, String path) {
|
||||||
|
setCookie(name, "", path, null, 0, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
|
||||||
|
StringBuffer cookieBuf = new StringBuffer();
|
||||||
|
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, null, maxAge, secure, httpOnly);
|
||||||
|
String cookie = cookieBuf.toString();
|
||||||
|
response.addHeader("Set-Cookie", cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() {
|
||||||
|
try {
|
||||||
|
return response.getOutputStream();
|
||||||
|
} 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 void end() {
|
||||||
|
ended = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnded() {
|
||||||
|
return ended;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Request getRequest() {
|
||||||
|
return requestFacade;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getResponse() {
|
||||||
|
return responseFacade;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getCertificateChain() {
|
||||||
|
throw new IllegalStateException("Not supported yet");
|
||||||
|
}
|
||||||
|
}
|
10
pom.xml
10
pom.xml
|
@ -762,6 +762,11 @@
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-servlet-adapter-spi</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
|
@ -887,6 +892,11 @@
|
||||||
<artifactId>keycloak-tomcat6-adapter</artifactId>
|
<artifactId>keycloak-tomcat6-adapter</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-saml-server-filter-adapter</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-tomcat6-adapter</artifactId>
|
<artifactId>keycloak-saml-tomcat6-adapter</artifactId>
|
||||||
|
|
|
@ -88,6 +88,9 @@ public abstract class SamlAuthenticator {
|
||||||
|
|
||||||
protected AuthOutcome globalLogout() {
|
protected AuthOutcome globalLogout() {
|
||||||
SamlSession account = sessionStore.getAccount();
|
SamlSession account = sessionStore.getAccount();
|
||||||
|
if (account == null) {
|
||||||
|
return AuthOutcome.NOT_ATTEMPTED;
|
||||||
|
}
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
||||||
.assertionExpiration(30)
|
.assertionExpiration(30)
|
||||||
.issuer(deployment.getEntityID())
|
.issuer(deployment.getEntityID())
|
||||||
|
|
|
@ -20,5 +20,6 @@
|
||||||
<module>jetty</module>
|
<module>jetty</module>
|
||||||
<module>wildfly</module>
|
<module>wildfly</module>
|
||||||
<module>as7-eap6</module>
|
<module>as7-eap6</module>
|
||||||
|
<module>servlet-filter</module>
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
|
|
69
saml/client-adapter/servlet-filter/pom.xml
Executable file
69
saml/client-adapter/servlet-filter/pom.xml
Executable file
|
@ -0,0 +1,69 @@
|
||||||
|
<?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-saml-server-filter-adapter</artifactId>
|
||||||
|
<name>Keycloak SAML Servlet Filter</name>
|
||||||
|
<description />
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-servlet-adapter-spi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-saml-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-saml-adapter-core</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>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,218 @@
|
||||||
|
package org.keycloak.adapters.saml.servlet;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.adapters.HttpFacade;
|
||||||
|
import org.keycloak.adapters.SessionIdMapper;
|
||||||
|
import org.keycloak.adapters.saml.SamlSession;
|
||||||
|
import org.keycloak.adapters.saml.SamlSessionStore;
|
||||||
|
import org.keycloak.adapters.servlet.FilterSessionStore;
|
||||||
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
|
|
||||||
|
import javax.servlet.ServletInputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class FilterSamlSessionStore extends FilterSessionStore implements SamlSessionStore {
|
||||||
|
protected static Logger log = Logger.getLogger(SamlSessionStore.class);
|
||||||
|
protected final SessionIdMapper idMapper;
|
||||||
|
|
||||||
|
public FilterSamlSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer, SessionIdMapper idMapper) {
|
||||||
|
super(request, facade, maxBuffer);
|
||||||
|
this.idMapper = idMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean needRequestRestore;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logoutAccount() {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) return;
|
||||||
|
if (session != null) {
|
||||||
|
if (idMapper != null) idMapper.removeSession(session.getId());
|
||||||
|
SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
|
||||||
|
if (samlSession != null) {
|
||||||
|
session.removeAttribute(SamlSession.class.getName());
|
||||||
|
}
|
||||||
|
clearSavedRequest(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logoutByPrincipal(String principal) {
|
||||||
|
SamlSession account = getAccount();
|
||||||
|
if (account != null && account.getPrincipal().getSamlSubject().equals(principal)) {
|
||||||
|
logoutAccount();
|
||||||
|
}
|
||||||
|
if (idMapper != null) {
|
||||||
|
Set<String> sessions = idMapper.getUserSessions(principal);
|
||||||
|
if (sessions != null) {
|
||||||
|
List<String> ids = new LinkedList<String>();
|
||||||
|
ids.addAll(sessions);
|
||||||
|
for (String id : ids) {
|
||||||
|
idMapper.removeSession(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logoutBySsoId(List<String> ssoIds) {
|
||||||
|
SamlSession account = getAccount();
|
||||||
|
for (String ssoId : ssoIds) {
|
||||||
|
if (account != null && account.getSessionIndex().equals(ssoId)) {
|
||||||
|
logoutAccount();
|
||||||
|
} else if (idMapper != null) {
|
||||||
|
String sessionId = idMapper.getSessionFromSSO(ssoId);
|
||||||
|
idMapper.removeSession(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLoggedIn() {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) return false;
|
||||||
|
if (session == null) {
|
||||||
|
log.debug("session was null, returning null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
|
||||||
|
if (samlSession == null) {
|
||||||
|
log.debug("SamlSession was not in session, returning null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (idMapper != null && !idMapper.hasSession(session.getId())) {
|
||||||
|
logoutAccount();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
needRequestRestore = restoreRequest();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveAccount(SamlSession account) {
|
||||||
|
HttpSession session = request.getSession(true);
|
||||||
|
session.setAttribute(SamlSession.class.getName(), account);
|
||||||
|
if (idMapper != null) idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), session.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SamlSession getAccount() {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) return null;
|
||||||
|
return (SamlSession)session.getAttribute(SamlSession.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRedirectUri() {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) return null;
|
||||||
|
return (String)session.getAttribute(REDIRECT_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
package org.keycloak.adapters.saml.servlet;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.AuthChallenge;
|
||||||
|
import org.keycloak.adapters.AuthOutcome;
|
||||||
|
import org.keycloak.adapters.InMemorySessionIdMapper;
|
||||||
|
import org.keycloak.adapters.SessionIdMapper;
|
||||||
|
import org.keycloak.adapters.saml.DefaultSamlDeployment;
|
||||||
|
import org.keycloak.adapters.saml.SamlAuthenticator;
|
||||||
|
import org.keycloak.adapters.saml.SamlDeployment;
|
||||||
|
import org.keycloak.adapters.saml.SamlDeploymentContext;
|
||||||
|
import org.keycloak.adapters.saml.SamlSession;
|
||||||
|
import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
|
||||||
|
import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
|
||||||
|
import org.keycloak.adapters.servlet.ServletHttpFacade;
|
||||||
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.RequestDispatcher;
|
||||||
|
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.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
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 SamlFilter implements Filter {
|
||||||
|
protected SamlDeploymentContext deploymentContext;
|
||||||
|
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
|
||||||
|
private final static Logger log = Logger.getLogger(""+SamlFilter.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(final FilterConfig filterConfig) throws ServletException {
|
||||||
|
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
|
||||||
|
if (configResolverClass != null) {
|
||||||
|
try {
|
||||||
|
throw new RuntimeException("Not implemented yet");
|
||||||
|
//KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
|
||||||
|
//deploymentContext = new SamlDeploymentContext(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 path = "/WEB-INF/keycloak-saml.xml";
|
||||||
|
String pathParam = filterConfig.getInitParameter("keycloak.config.file");
|
||||||
|
if (pathParam != null) path = pathParam;
|
||||||
|
InputStream is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||||
|
final SamlDeployment deployment;
|
||||||
|
if (is == null) {
|
||||||
|
log.info("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
||||||
|
deployment = new DefaultSamlDeployment();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
ResourceLoader loader = new ResourceLoader() {
|
||||||
|
@Override
|
||||||
|
public InputStream getResourceAsStream(String resource) {
|
||||||
|
return filterConfig.getServletContext().getResourceAsStream(resource);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
deployment = new DeploymentBuilder().build(is, loader);
|
||||||
|
} catch (ParsingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deploymentContext = new SamlDeploymentContext(deployment);
|
||||||
|
log.fine("Keycloak is using a per-deployment configuration.");
|
||||||
|
}
|
||||||
|
filterConfig.getServletContext().setAttribute(SamlDeploymentContext.class.getName(), deploymentContext);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||||
|
HttpServletRequest request = (HttpServletRequest) req;
|
||||||
|
HttpServletResponse response = (HttpServletResponse) res;
|
||||||
|
ServletHttpFacade facade = new ServletHttpFacade(request, response);
|
||||||
|
SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||||
|
if (deployment == null || !deployment.isConfigured()) {
|
||||||
|
response.sendError(403);
|
||||||
|
log.fine("deployment not configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FilterSamlSessionStore tokenStore = new FilterSamlSessionStore(request, facade, 100000, idMapper);
|
||||||
|
|
||||||
|
|
||||||
|
SamlAuthenticator authenticator = new SamlAuthenticator(facade, deployment, tokenStore) {
|
||||||
|
@Override
|
||||||
|
protected void completeAuthentication(SamlSession account) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AuthOutcome outcome = authenticator.authenticate();
|
||||||
|
if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||||
|
log.fine("AUTHENTICATED");
|
||||||
|
if (facade.isEnded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HttpServletRequestWrapper wrapper = tokenStore.getWrap();
|
||||||
|
chain.doFilter(wrapper, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (outcome == AuthOutcome.LOGGED_OUT) {
|
||||||
|
tokenStore.logoutAccount();
|
||||||
|
if (deployment.getLogoutPage() != null) {
|
||||||
|
RequestDispatcher disp = req.getRequestDispatcher(deployment.getLogoutPage());
|
||||||
|
disp.forward(req, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chain.doFilter(req, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthChallenge challenge = authenticator.getChallenge();
|
||||||
|
if (challenge != null) {
|
||||||
|
log.fine("challenge");
|
||||||
|
if (challenge.errorPage()) {
|
||||||
|
response.sendError(403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.fine("sending challenge");
|
||||||
|
challenge.challenge(facade);
|
||||||
|
}
|
||||||
|
if (outcome == AuthOutcome.FAILED) {
|
||||||
|
response.sendError(403);
|
||||||
|
} else if (!facade.isEnded()) {
|
||||||
|
chain.doFilter(req, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -105,6 +105,10 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-adapter-core</artifactId>
|
<artifactId>keycloak-saml-adapter-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-saml-server-filter-adapter</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-undertow-adapter</artifactId>
|
<artifactId>keycloak-saml-undertow-adapter</artifactId>
|
||||||
|
|
|
@ -26,7 +26,11 @@ public class SendUsernameServlet extends HttpServlet {
|
||||||
if (checkRoles != null) {
|
if (checkRoles != null) {
|
||||||
for (String role : checkRoles) {
|
for (String role : checkRoles) {
|
||||||
System.out.println("check role: " + role);
|
System.out.println("check role: " + role);
|
||||||
Assert.assertTrue(req.isUserInRole(role));
|
//Assert.assertTrue(req.isUserInRole(role));
|
||||||
|
if (!req.isUserInRole(role)) {
|
||||||
|
resp.sendError(403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
package org.keycloak.testsuite.samlfilter;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.testsuite.keycloaksaml.SamlAdapterTestStrategy;
|
||||||
|
import org.keycloak.testsuite.keycloaksaml.SamlSPFacade;
|
||||||
|
import org.keycloak.testsuite.keycloaksaml.SendUsernameServlet;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class SamlAdapterTest {
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static SamlKeycloakRule keycloakRule = new SamlKeycloakRule() {
|
||||||
|
@Override
|
||||||
|
public void initWars() {
|
||||||
|
ClassLoader classLoader = SamlAdapterTest.class.getClassLoader();
|
||||||
|
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/simple-post", "/sales-post", "post.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/signed-post", "/sales-post-sig", "post-sig.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/signed-post-email", "/sales-post-sig-email", "post-sig-email.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/signed-post-transient", "/sales-post-sig-transient", "post-sig-transient.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/signed-post-persistent", "/sales-post-sig-persistent", "post-sig-persistent.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/signed-metadata", "/sales-metadata", "post-metadata.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/signed-get", "/employee-sig", "employee-sig.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/mappers", "/employee2", "employee2.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/signed-front-get", "/employee-sig-front", "employee-sig-front.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/bad-client-signed-post", "/bad-client-sales-post-sig", "bad-client-post-sig.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/bad-realm-signed-post", "/bad-realm-sales-post-sig", "bad-realm-post-sig.war", classLoader);
|
||||||
|
initializeSamlSecuredWar("/keycloak-saml/encrypted-post", "/sales-post-enc", "post-enc.war", classLoader);
|
||||||
|
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRealmJson() {
|
||||||
|
return "/keycloak-saml/testsaml.json";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public SamlAdapterTestStrategy testStrategy = new SamlAdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8081", keycloakRule);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostBadRealmSignature() {
|
||||||
|
testStrategy.testPostBadRealmSignature( new SamlAdapterTestStrategy.CheckAuthError() {
|
||||||
|
@Override
|
||||||
|
public void check(WebDriver driver) {
|
||||||
|
Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostSimpleUnauthorized() {
|
||||||
|
List<String> requiredRoles = new LinkedList<>();
|
||||||
|
requiredRoles.add("manager");
|
||||||
|
requiredRoles.add("employee");
|
||||||
|
requiredRoles.add("user");
|
||||||
|
SendUsernameServlet.checkRoles = requiredRoles;
|
||||||
|
try {
|
||||||
|
testStrategy.testPostSimpleUnauthorized(new SamlAdapterTestStrategy.CheckAuthError() {
|
||||||
|
@Override
|
||||||
|
public void check(WebDriver driver) {
|
||||||
|
Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
SendUsernameServlet.checkRoles = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMetadataPostSignedLoginLogout() throws Exception {
|
||||||
|
testStrategy.testMetadataPostSignedLoginLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRedirectSignedLoginLogout() {
|
||||||
|
testStrategy.testRedirectSignedLoginLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostSignedLoginLogoutEmailNameID() {
|
||||||
|
testStrategy.testPostSignedLoginLogoutEmailNameID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostEncryptedLoginLogout() {
|
||||||
|
testStrategy.testPostEncryptedLoginLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRedirectSignedLoginLogoutFrontNoSSO() {
|
||||||
|
testStrategy.testRedirectSignedLoginLogoutFrontNoSSO();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostSimpleLoginLogout() {
|
||||||
|
testStrategy.testPostSimpleLoginLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostSignedLoginLogoutTransientNameID() {
|
||||||
|
testStrategy.testPostSignedLoginLogoutTransientNameID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostSimpleLoginLogoutIdpInitiated() {
|
||||||
|
testStrategy.testPostSimpleLoginLogoutIdpInitiated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttributes() throws Exception {
|
||||||
|
testStrategy.testAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostSignedLoginLogoutPersistentNameID() {
|
||||||
|
testStrategy.testPostSignedLoginLogoutPersistentNameID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostBadClientSignature() {
|
||||||
|
testStrategy.testPostBadClientSignature();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRedirectSignedLoginLogoutFront() {
|
||||||
|
testStrategy.testRedirectSignedLoginLogoutFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostSignedLoginLogout() {
|
||||||
|
testStrategy.testPostSignedLoginLogout();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package org.keycloak.testsuite.samlfilter;
|
||||||
|
|
||||||
|
import io.undertow.security.idm.Account;
|
||||||
|
import io.undertow.security.idm.Credential;
|
||||||
|
import io.undertow.security.idm.IdentityManager;
|
||||||
|
import io.undertow.server.handlers.resource.Resource;
|
||||||
|
import io.undertow.server.handlers.resource.ResourceChangeListener;
|
||||||
|
import io.undertow.server.handlers.resource.ResourceManager;
|
||||||
|
import io.undertow.server.handlers.resource.URLResource;
|
||||||
|
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.ServletInfo;
|
||||||
|
import io.undertow.servlet.api.WebResourceCollection;
|
||||||
|
import org.keycloak.adapters.saml.servlet.SamlFilter;
|
||||||
|
import org.keycloak.adapters.saml.undertow.SamlServletExtension;
|
||||||
|
import org.keycloak.testsuite.keycloaksaml.SendUsernameServlet;
|
||||||
|
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
|
||||||
|
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public abstract class SamlKeycloakRule extends AbstractKeycloakRule {
|
||||||
|
|
||||||
|
public static class TestResourceManager implements ResourceManager {
|
||||||
|
|
||||||
|
private final String basePath;
|
||||||
|
|
||||||
|
public TestResourceManager(String basePath){
|
||||||
|
this.basePath = basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resource getResource(String path) throws IOException {
|
||||||
|
String temp = path;
|
||||||
|
String fullPath = basePath + temp;
|
||||||
|
URL url = getClass().getResource(fullPath);
|
||||||
|
if (url == null) {
|
||||||
|
System.out.println("url is null: " + fullPath);
|
||||||
|
}
|
||||||
|
return new URLResource(url, url.openConnection(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isResourceChangeListenerSupported() {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerResourceChangeListener(ResourceChangeListener listener) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeResourceChangeListener(ResourceChangeListener listener) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestIdentityManager implements IdentityManager {
|
||||||
|
@Override
|
||||||
|
public Account verify(Account account) {
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Account verify(String userName, Credential credential) {
|
||||||
|
throw new RuntimeException("WTF");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Account verify(Credential credential) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupKeycloak() {
|
||||||
|
String realmJson = getRealmJson();
|
||||||
|
server.importRealm(getClass().getResourceAsStream(realmJson));
|
||||||
|
initWars();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void initWars();
|
||||||
|
|
||||||
|
public void initializeSamlSecuredWar(String warResourcePath, String contextPath, String warDeploymentName, ClassLoader classLoader) {
|
||||||
|
|
||||||
|
ServletInfo regularServletInfo = new ServletInfo("servlet", SendUsernameServlet.class)
|
||||||
|
.addMapping("/*");
|
||||||
|
|
||||||
|
FilterInfo samlFilter = new FilterInfo("saml-filter", SamlFilter.class);
|
||||||
|
|
||||||
|
|
||||||
|
ResourceManager resourceManager = new TestResourceManager(warResourcePath);
|
||||||
|
|
||||||
|
DeploymentInfo deploymentInfo = new DeploymentInfo()
|
||||||
|
.setClassLoader(classLoader)
|
||||||
|
.setIdentityManager(new TestIdentityManager())
|
||||||
|
.setContextPath(contextPath)
|
||||||
|
.setDeploymentName(warDeploymentName)
|
||||||
|
.setResourceManager(resourceManager)
|
||||||
|
.addServlets(regularServletInfo)
|
||||||
|
.addFilter(samlFilter)
|
||||||
|
.addFilterUrlMapping("saml-filter", "/*", DispatcherType.REQUEST)
|
||||||
|
.addServletExtension(new SamlServletExtension());
|
||||||
|
server.getServer().deploy(deploymentInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealmJson() {
|
||||||
|
return "/keycloak-saml/testsaml.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue