diff --git a/README.md b/README.md
index f80e35f..7000b21 100644
--- a/README.md
+++ b/README.md
@@ -665,6 +665,7 @@ The `SingleSignOutFilter` can affect character encoding. This becomes most obvio
| `relayStateParameterName` | Defaults to `RelayState` | No
| `eagerlyCreateSessions` | Defaults to `true` | No
| `artifactParameterOverPost` | Defaults to `false` | No
+| `logoutCallbackPath` | The path which is expected to receive logout callback requests from the CAS server. This is necessary if your app needs access to the raw input stream when handling form posts. If not configured, the default behavior will check every form post for a logout parameter. | No
| `casServerUrlPrefix` | URL to root of CAS Web application context. | Yes
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java
index 109aadd..d9b6ea6 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java
@@ -78,4 +78,5 @@ public interface ConfigurationKeys {
ConfigurationKey> TICKET_VALIDATOR_CLASS = new ConfigurationKey>("ticketValidatorClass", null);
ConfigurationKey PROXY_CALLBACK_URL = new ConfigurationKey("proxyCallbackUrl", null);
ConfigurationKey RELAY_STATE_PARAMETER_NAME = new ConfigurationKey("relayStateParameterName", "RelayState");
+ ConfigurationKey LOGOUT_CALLBACK_PATH = new ConfigurationKey("logoutCallbackPath", null);
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java
index 25d645c..ae511ba 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java
@@ -48,6 +48,7 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter {
setLogoutParameterName(getString(ConfigurationKeys.LOGOUT_PARAMETER_NAME));
setRelayStateParameterName(getString(ConfigurationKeys.RELAY_STATE_PARAMETER_NAME));
setCasServerUrlPrefix(getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX));
+ setLogoutCallbackPath(getString(ConfigurationKeys.LOGOUT_CALLBACK_PATH));
HANDLER.setArtifactParameterOverPost(getBoolean(ConfigurationKeys.ARTIFACT_PARAMETER_OVER_POST));
HANDLER.setEagerlyCreateSessions(getBoolean(ConfigurationKeys.EAGERLY_CREATE_SESSIONS));
}
@@ -71,6 +72,10 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter {
HANDLER.setCasServerUrlPrefix(casServerUrlPrefix);
}
+ public void setLogoutCallbackPath(String logoutCallbackPath) {
+ HANDLER.setLogoutCallbackPath(logoutCallbackPath);
+ }
+
public void setSessionMappingStorage(final SessionMappingStorage storage) {
HANDLER.setSessionMappingStorage(storage);
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java
index 26932db..e1b15bc 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java
@@ -66,6 +66,9 @@ public final class SingleSignOutHandler {
/** The prefix url of the CAS server */
private String casServerUrlPrefix = "";
+ /** The logout callback path configured at the CAS server, if there is one */
+ private String logoutCallbackPath;
+
private boolean artifactParameterOverPost = false;
private boolean eagerlyCreateSessions = true;
@@ -106,7 +109,14 @@ public final class SingleSignOutHandler {
public void setCasServerUrlPrefix(final String casServerUrlPrefix) {
this.casServerUrlPrefix = casServerUrlPrefix;
}
-
+
+ /**
+ * @param logoutCallbackPath The logout callback path configured at the CAS server.
+ */
+ public void setLogoutCallbackPath(String logoutCallbackPath) {
+ this.logoutCallbackPath = logoutCallbackPath;
+ }
+
/**
* @param name Name of parameter containing the state of the CAS server webflow.
*/
@@ -163,6 +173,7 @@ public final class SingleSignOutHandler {
private boolean isLogoutRequest(final HttpServletRequest request) {
if ("POST".equalsIgnoreCase(request.getMethod())) {
return !isMultipartRequest(request)
+ && pathEligibleForLogout(request)
&& CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName,
this.safeParameters));
}
@@ -172,7 +183,15 @@ public final class SingleSignOutHandler {
}
return false;
}
-
+
+ private boolean pathEligibleForLogout(HttpServletRequest request) {
+ return logoutCallbackPath == null || logoutCallbackPath.equals(getPath(request));
+ }
+
+ private String getPath(HttpServletRequest request) {
+ return request.getServletPath() + CommonUtils.nullToEmpty(request.getPathInfo());
+ }
+
/**
* Process a request regarding the SLO process: record the session or destroy it.
*
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java
index 886cf81..5cff27f 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java
@@ -719,6 +719,17 @@ public final class CommonUtils {
}
}
+ /**
+ * Returns the string as-is, unless it's null;
+ * in this case an empty string is returned.
+ *
+ * @param string a possibly null string
+ * @return a non-null string
+ */
+ public static String nullToEmpty(String string) {
+ return string == null ? "" : string;
+ }
+
/**
* Adds a trailing slash to the given uri, if it doesn't already have one.
*
diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java
index 164a362..e80e31d 100644
--- a/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java
+++ b/cas-client-core/src/test/java/org/jasig/cas/client/session/SingleSignOutHandlerTests.java
@@ -116,13 +116,36 @@ public final class SingleSignOutHandlerTests {
@Test
public void backChannelLogoutOK() {
+ final MockHttpSession session = doBackChannelLogout();
+ assertFalse(handler.process(request, response));
+ assertTrue(session.isInvalid());
+ }
+
+ @Test
+ public void backChannelLogoutDoesNotRunIfPathIsNotEligibleForLogout() {
+ handler.setLogoutCallbackPath("/logout");
+ request.setServletPath("/not-a-logout");
+ final MockHttpSession session = doBackChannelLogout();
+ assertTrue(handler.process(request, response));
+ assertFalse(session.isInvalid());
+ }
+
+ @Test
+ public void backChannelLogoutRunsIfPathEqualsLogoutPath() {
+ handler.setLogoutCallbackPath("/logout");
+ request.setServletPath("/logout");
+ final MockHttpSession session = doBackChannelLogout();
+ assertFalse(handler.process(request, response));
+ assertTrue(session.isInvalid());
+ }
+
+ private MockHttpSession doBackChannelLogout() {
final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage(TICKET);
request.setParameter(LOGOUT_PARAMETER_NAME, logoutMessage);
request.setMethod("POST");
final MockHttpSession session = new MockHttpSession();
handler.getSessionMappingStorage().addSessionById(TICKET, session);
- assertFalse(handler.process(request, response));
- assertTrue(session.isInvalid());
+ return session;
}
@Test
diff --git a/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java b/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java
index 00c63dc..ac6f26b 100644
--- a/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java
+++ b/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java
@@ -60,6 +60,10 @@ public class SingleSignOutValve extends AbstractLifecycleValve implements Sessio
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
}
+ public void setLogoutCallbackPath(String logoutCallbackPath) {
+ this.handler.setLogoutCallbackPath(logoutCallbackPath);
+ }
+
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.handler.setSessionMappingStorage(storage);
}
diff --git a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java
index e7cd85d..77711bc 100644
--- a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java
+++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java
@@ -64,6 +64,10 @@ public class SingleSignOutValve extends ValveBase implements SessionListener {
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
}
+ public void setLogoutCallbackPath(String logoutCallbackPath) {
+ this.handler.setLogoutCallbackPath(logoutCallbackPath);
+ }
+
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.handler.setSessionMappingStorage(storage);
}
diff --git a/cas-client-integration-tomcat-v8/src/main/java/org/jasig/cas/client/tomcat/v8/SingleSignOutValve.java b/cas-client-integration-tomcat-v8/src/main/java/org/jasig/cas/client/tomcat/v8/SingleSignOutValve.java
index de77527..e7196f5 100644
--- a/cas-client-integration-tomcat-v8/src/main/java/org/jasig/cas/client/tomcat/v8/SingleSignOutValve.java
+++ b/cas-client-integration-tomcat-v8/src/main/java/org/jasig/cas/client/tomcat/v8/SingleSignOutValve.java
@@ -64,6 +64,10 @@ public class SingleSignOutValve extends ValveBase implements SessionListener {
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
}
+ public void setLogoutCallbackPath(String logoutCallbackPath) {
+ this.handler.setLogoutCallbackPath(logoutCallbackPath);
+ }
+
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.handler.setSessionMappingStorage(storage);
}
diff --git a/cas-client-integration-tomcat-v85/src/main/java/org/jasig/cas/client/tomcat/v85/SingleSignOutValve.java b/cas-client-integration-tomcat-v85/src/main/java/org/jasig/cas/client/tomcat/v85/SingleSignOutValve.java
index 18aca7a..ccbdcc5 100644
--- a/cas-client-integration-tomcat-v85/src/main/java/org/jasig/cas/client/tomcat/v85/SingleSignOutValve.java
+++ b/cas-client-integration-tomcat-v85/src/main/java/org/jasig/cas/client/tomcat/v85/SingleSignOutValve.java
@@ -64,6 +64,10 @@ public class SingleSignOutValve extends ValveBase implements SessionListener {
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
}
+ public void setLogoutCallbackPath(String logoutCallbackPath) {
+ this.handler.setLogoutCallbackPath(logoutCallbackPath);
+ }
+
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.handler.setSessionMappingStorage(storage);
}