CASC-220: Support front channel SLO logout
Updates after Misagh's code review
This commit is contained in:
parent
9714053f07
commit
6aa2379268
|
|
@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.jasig.cas.client.util.AbstractConfigurationFilter;
|
||||
import org.jasig.cas.client.util.CommonUtils;
|
||||
|
||||
/**
|
||||
* Implements the Single Sign Out protocol. It handles registering the session and destroying the session.
|
||||
|
|
@ -85,25 +84,9 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter {
|
|||
final HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
final HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
|
||||
if (handler.isTokenRequest(request)) {
|
||||
handler.recordSession(request);
|
||||
} else if (handler.isBackChannelLogoutRequest(request)) {
|
||||
handler.destroySession(request);
|
||||
// Do not continue up filter chain
|
||||
return;
|
||||
} else if (handler.isFrontChannelLogoutRequest(request)) {
|
||||
handler.destroySession(request);
|
||||
// redirection url to the CAS server
|
||||
final String redirectionUrl = handler.computeRedirectionToServer(request);
|
||||
if (redirectionUrl != null) {
|
||||
CommonUtils.sendRedirect(response, redirectionUrl);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
logger.trace("Ignoring URI {}", request.getRequestURI());
|
||||
if (handler.process(request, response)) {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import java.util.zip.Inflater;
|
|||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
|
@ -153,7 +154,7 @@ public final class SingleSignOutHandler {
|
|||
*
|
||||
* @return True if request contains authentication token, false otherwise.
|
||||
*/
|
||||
public boolean isTokenRequest(final HttpServletRequest request) {
|
||||
protected boolean isTokenRequest(final HttpServletRequest request) {
|
||||
return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName,
|
||||
this.safeParameters));
|
||||
}
|
||||
|
|
@ -165,7 +166,7 @@ public final class SingleSignOutHandler {
|
|||
*
|
||||
* @return True if request is logout request, false otherwise.
|
||||
*/
|
||||
public boolean isBackChannelLogoutRequest(final HttpServletRequest request) {
|
||||
protected boolean isBackChannelLogoutRequest(final HttpServletRequest request) {
|
||||
return "POST".equals(request.getMethod())
|
||||
&& !isMultipartRequest(request)
|
||||
&& CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName,
|
||||
|
|
@ -179,18 +180,46 @@ public final class SingleSignOutHandler {
|
|||
*
|
||||
* @return True if request is logout request, false otherwise.
|
||||
*/
|
||||
public boolean isFrontChannelLogoutRequest(final HttpServletRequest request) {
|
||||
protected boolean isFrontChannelLogoutRequest(final HttpServletRequest request) {
|
||||
return "GET".equals(request.getMethod())
|
||||
&& CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.frontLogoutParameterName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a request regarding the SLO process: record the session or destroy it.
|
||||
*
|
||||
* @param request the incoming HTTP request.
|
||||
* @param response the HTTP response.
|
||||
* @return if the request should continue to be processed.
|
||||
*/
|
||||
public boolean process(final HttpServletRequest request, final HttpServletResponse response) {
|
||||
if (isTokenRequest(request)) {
|
||||
recordSession(request);
|
||||
} else if (isBackChannelLogoutRequest(request)) {
|
||||
destroySession(request);
|
||||
// Do not continue up filter chain
|
||||
return false;
|
||||
} else if (isFrontChannelLogoutRequest(request)) {
|
||||
destroySession(request);
|
||||
// redirection url to the CAS server
|
||||
final String redirectionUrl = computeRedirectionToServer(request);
|
||||
if (redirectionUrl != null) {
|
||||
CommonUtils.sendRedirect(response, redirectionUrl);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
logger.trace("Ignoring URI {}", request.getRequestURI());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a token request with the current HTTP session by recording the mapping
|
||||
* in the the configured {@link SessionMappingStorage} container.
|
||||
*
|
||||
* @param request HTTP request containing an authentication token.
|
||||
*/
|
||||
public void recordSession(final HttpServletRequest request) {
|
||||
protected void recordSession(final HttpServletRequest request) {
|
||||
final HttpSession session = request.getSession(this.eagerlyCreateSessions);
|
||||
|
||||
if (session == null) {
|
||||
|
|
@ -215,17 +244,41 @@ public final class SingleSignOutHandler {
|
|||
* @param originalMessage the original logout message.
|
||||
* @return the uncompressed logout message.
|
||||
*/
|
||||
private String uncompressLogoutMessage(final String originalMessage) {
|
||||
protected String uncompressLogoutMessage(final String originalMessage) {
|
||||
// base64 decode
|
||||
final byte[] binaryMessage = Base64.decodeBase64(originalMessage);
|
||||
|
||||
Inflater decompresser = null;
|
||||
try {
|
||||
// decompress the bytes
|
||||
final Inflater decompresser = new Inflater();
|
||||
decompresser = new Inflater();
|
||||
decompresser.setInput(binaryMessage);
|
||||
|
||||
/* The received logout message is compressed, so this number (10) is the multiplier of the original size
|
||||
* of the logout message (binaryMessage.length) to compute the size of the buffer where the logout message
|
||||
* will be decompressed.
|
||||
* It's somehow the decompression factor.
|
||||
*
|
||||
* For the buffer, we could also have a fixed size for the buffer (like 10k), but I thought that ten times
|
||||
* would be a sufficient multiplier...
|
||||
*
|
||||
* A real test:
|
||||
* String sessionIndex = "ST-45-fs45646r84ffs1d31f554f5d4f64fg6r8eq5s4d6f4fddsf46-cas";
|
||||
* String bm = LogoutMessageGenerator.generateBackChannelLogoutMessage(sessionIndex);
|
||||
* System.out.println("bm.size = " + bm.length());
|
||||
* String fm = new String(Base64.decodeBase64(LogoutMessageGenerator.
|
||||
* generateFrontChannelLogoutMessage(sessionIndex)));
|
||||
* System.out.println("fm.size = " + fm.length());
|
||||
*
|
||||
* And the result:
|
||||
* bm.size = 354
|
||||
* fm.size = 224
|
||||
*
|
||||
* So ten times is enough, it's even too much...
|
||||
*/
|
||||
byte[] result = new byte[binaryMessage.length * 10];
|
||||
|
||||
int resultLength = decompresser.inflate(result);
|
||||
decompresser.end();
|
||||
|
||||
// decode the bytes into a String
|
||||
return new String(result, 0, resultLength, "UTF-8");
|
||||
|
|
@ -235,6 +288,10 @@ public final class SingleSignOutHandler {
|
|||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Unable to decompress logout message", e);
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (decompresser != null) {
|
||||
decompresser.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,7 +300,7 @@ public final class SingleSignOutHandler {
|
|||
*
|
||||
* @param request HTTP request containing a CAS logout message.
|
||||
*/
|
||||
public void destroySession(final HttpServletRequest request) {
|
||||
protected void destroySession(final HttpServletRequest request) {
|
||||
String logoutMessage;
|
||||
// front channel logout -> the message needs to be base64 decoded + decompressed
|
||||
if ("GET".equals(request.getMethod())) {
|
||||
|
|
@ -284,7 +341,7 @@ public final class SingleSignOutHandler {
|
|||
* @param request The HTTP request.
|
||||
* @return the redirection url to the CAS server.
|
||||
*/
|
||||
public String computeRedirectionToServer(final HttpServletRequest request) {
|
||||
protected String computeRedirectionToServer(final HttpServletRequest request) {
|
||||
// relay state value
|
||||
final String relayStateValue = CommonUtils.safeGetParameter(request, this.relayStateParameterName);
|
||||
// if we have a state value -> redirect to the CAS server to continue the logout process
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.jasig.cas.client.session;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
|
@ -16,13 +17,13 @@ public final class LogoutMessageGenerator {
|
|||
|
||||
private static final String LOGOUT_REQUEST_TEMPLATE =
|
||||
"<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"00000001\" Version=\"2.0\" "
|
||||
+ "IssueInstant=\"\"><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">@NOT_USED@"
|
||||
+ "IssueInstant=\"%s\"><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">@NOT_USED@"
|
||||
+ "</saml:NameID><samlp:SessionIndex>%s</samlp:SessionIndex></samlp:LogoutRequest>";
|
||||
|
||||
public static String generateBackChannelLogoutMessage(String sessionIndex) {
|
||||
return String.format(LOGOUT_REQUEST_TEMPLATE, sessionIndex);
|
||||
return String.format(LOGOUT_REQUEST_TEMPLATE, new Date(), sessionIndex);
|
||||
}
|
||||
|
||||
|
||||
public static String generateFrontChannelLogoutMessage(String sessionIndex) {
|
||||
final String logoutMessage = generateBackChannelLogoutMessage(sessionIndex);
|
||||
final Deflater deflater = new Deflater();
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import org.apache.catalina.connector.Request;
|
|||
import org.apache.catalina.connector.Response;
|
||||
import org.jasig.cas.client.session.SessionMappingStorage;
|
||||
import org.jasig.cas.client.session.SingleSignOutHandler;
|
||||
import org.jasig.cas.client.util.CommonUtils;
|
||||
|
||||
/**
|
||||
* Handles logout request messages sent from the CAS server by ending the current
|
||||
|
|
@ -46,57 +45,41 @@ public class SingleSignOutValve extends AbstractLifecycleValve implements Sessio
|
|||
private final SingleSignOutHandler handler = new SingleSignOutHandler();
|
||||
|
||||
public void setArtifactParameterName(final String name) {
|
||||
handler.setArtifactParameterName(name);
|
||||
this.handler.setArtifactParameterName(name);
|
||||
}
|
||||
|
||||
public void setLogoutParameterName(final String name) {
|
||||
handler.setLogoutParameterName(name);
|
||||
this.handler.setLogoutParameterName(name);
|
||||
}
|
||||
|
||||
public void setFrontLogoutParameterName(final String name) {
|
||||
handler.setFrontLogoutParameterName(name);
|
||||
this.handler.setFrontLogoutParameterName(name);
|
||||
}
|
||||
|
||||
public void setRelayStateParameterName(final String name) {
|
||||
handler.setRelayStateParameterName(name);
|
||||
this.handler.setRelayStateParameterName(name);
|
||||
}
|
||||
|
||||
public void setCasServerUrlPrefix(final String casServerUrlPrefix) {
|
||||
handler.setCasServerUrlPrefix(casServerUrlPrefix);
|
||||
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
|
||||
}
|
||||
|
||||
public void setSessionMappingStorage(final SessionMappingStorage storage) {
|
||||
handler.setSessionMappingStorage(storage);
|
||||
this.handler.setSessionMappingStorage(storage);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void start() throws LifecycleException {
|
||||
super.start();
|
||||
handler.init();
|
||||
this.handler.init();
|
||||
logger.info("Startup completed.");
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void invoke(final Request request, final Response response) throws IOException, ServletException {
|
||||
if (this.handler.isTokenRequest(request)) {
|
||||
this.handler.recordSession(request);
|
||||
request.getSessionInternal(true).addSessionListener(this);
|
||||
} else if (this.handler.isBackChannelLogoutRequest(request)) {
|
||||
this.handler.destroySession(request);
|
||||
// Do not proceed up valve chain
|
||||
return;
|
||||
} else if (this.handler.isFrontChannelLogoutRequest(request)) {
|
||||
this.handler.destroySession(request);
|
||||
// redirection url to the CAS server
|
||||
final String redirectionUrl = handler.computeRedirectionToServer(request);
|
||||
if (redirectionUrl != null) {
|
||||
CommonUtils.sendRedirect(response, redirectionUrl);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
logger.debug("Ignoring URI {}", request.getRequestURI());
|
||||
if (this.handler.process(request, response)) {
|
||||
getNext().invoke(request, response);
|
||||
}
|
||||
getNext().invoke(request, response);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
|
|
@ -111,5 +94,4 @@ public class SingleSignOutValve extends AbstractLifecycleValve implements Sessio
|
|||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import org.apache.catalina.connector.Response;
|
|||
import org.apache.catalina.valves.ValveBase;
|
||||
import org.jasig.cas.client.session.SessionMappingStorage;
|
||||
import org.jasig.cas.client.session.SingleSignOutHandler;
|
||||
import org.jasig.cas.client.util.CommonUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -50,50 +49,34 @@ public class SingleSignOutValve extends ValveBase implements SessionListener {
|
|||
private final SingleSignOutHandler handler = new SingleSignOutHandler();
|
||||
|
||||
public void setArtifactParameterName(final String name) {
|
||||
handler.setArtifactParameterName(name);
|
||||
this.handler.setArtifactParameterName(name);
|
||||
}
|
||||
|
||||
public void setLogoutParameterName(final String name) {
|
||||
handler.setLogoutParameterName(name);
|
||||
this.handler.setLogoutParameterName(name);
|
||||
}
|
||||
|
||||
public void setFrontLogoutParameterName(final String name) {
|
||||
handler.setFrontLogoutParameterName(name);
|
||||
this.handler.setFrontLogoutParameterName(name);
|
||||
}
|
||||
|
||||
public void setRelayStateParameterName(final String name) {
|
||||
handler.setRelayStateParameterName(name);
|
||||
this.handler.setRelayStateParameterName(name);
|
||||
}
|
||||
|
||||
public void setCasServerUrlPrefix(final String casServerUrlPrefix) {
|
||||
handler.setCasServerUrlPrefix(casServerUrlPrefix);
|
||||
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
|
||||
}
|
||||
|
||||
public void setSessionMappingStorage(final SessionMappingStorage storage) {
|
||||
handler.setSessionMappingStorage(storage);
|
||||
this.handler.setSessionMappingStorage(storage);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void invoke(final Request request, final Response response) throws IOException, ServletException {
|
||||
if (this.handler.isTokenRequest(request)) {
|
||||
this.handler.recordSession(request);
|
||||
request.getSessionInternal(true).addSessionListener(this);
|
||||
} else if (this.handler.isBackChannelLogoutRequest(request)) {
|
||||
this.handler.destroySession(request);
|
||||
// Do not proceed up valve chain
|
||||
return;
|
||||
} else if (this.handler.isFrontChannelLogoutRequest(request)) {
|
||||
this.handler.destroySession(request);
|
||||
// redirection url to the CAS server
|
||||
final String redirectionUrl = handler.computeRedirectionToServer(request);
|
||||
if (redirectionUrl != null) {
|
||||
CommonUtils.sendRedirect(response, redirectionUrl);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
logger.debug("Ignoring URI {}", request.getRequestURI());
|
||||
if (this.handler.process(request, response)) {
|
||||
getNext().invoke(request, response);
|
||||
}
|
||||
getNext().invoke(request, response);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
|
|
@ -108,7 +91,7 @@ public class SingleSignOutValve extends ValveBase implements SessionListener {
|
|||
protected void startInternal() throws LifecycleException {
|
||||
super.startInternal();
|
||||
logger.info("Starting...");
|
||||
handler.init();
|
||||
this.handler.init();
|
||||
logger.info("Startup completed.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue