Support the IdP initiated SAML SSO flow
Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/cf461e4c Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/cf461e4c Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/cf461e4c Branch: refs/heads/master-jaxrs-2.1 Commit: cf461e4c0805b187b43b5514fd08131fdd3fe601 Parents: 6583010 Author: Colm O hEigeartaigh <[email protected]> Authored: Wed Aug 10 11:55:20 2016 +0100 Committer: Colm O hEigeartaigh <[email protected]> Committed: Wed Aug 10 11:55:20 2016 +0100 ---------------------------------------------------------------------- ...AbstractRequestAssertionConsumerHandler.java | 54 ++++++++++++++++++++ .../security/saml/sso/AbstractSSOSpHandler.java | 36 +++++++++++++ .../saml/sso/AbstractServiceProviderFilter.java | 46 +++++------------ .../saml/sso/SAMLSSOResponseValidator.java | 3 ++ 4 files changed, 106 insertions(+), 33 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/cf461e4c/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java index 3c110a1..0e801f3 100644 --- a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java +++ b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java @@ -45,6 +45,7 @@ import org.apache.cxf.common.util.Base64Utility; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.jaxrs.ext.MessageContext; import org.apache.cxf.jaxrs.utils.ExceptionUtils; +import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder; import org.apache.cxf.rs.security.saml.sso.state.RequestState; import org.apache.cxf.rs.security.saml.sso.state.ResponseState; @@ -69,6 +70,8 @@ public abstract class AbstractRequestAssertionConsumerHandler extends AbstractSS private TokenReplayCache<String> replayCache; private MessageContext messageContext; + private String applicationURL; + private boolean parseApplicationURLFromRelayState; @Context public void setMessageContext(MessageContext mc) { @@ -201,6 +204,31 @@ public abstract class AbstractRequestAssertionConsumerHandler extends AbstractSS } protected RequestState processRelayState(String relayState) { + if (isSupportUnsolicited()) { + String urlToForwardTo = applicationURL; + if (relayState != null && relayState.getBytes().length > 0 && relayState.getBytes().length < 80) { + // First see if we have a valid RequestState + RequestState requestState = getStateProvider().removeRequestState(relayState); + if (requestState != null && !isStateExpired(requestState.getCreatedAt(), 0)) { + return requestState; + } + + // Otherwise get the application URL from the RelayState if supported + if (parseApplicationURLFromRelayState) { + urlToForwardTo = relayState; + } + } + + // Otherwise create a new one for the IdP initiated case + return new RequestState(urlToForwardTo, + getIdpServiceAddress(), + null, + getIssuerId(JAXRSUtils.getCurrentMessage()), + "/", + null, + new Date().getTime()); + } + if (relayState == null) { reportError("MISSING_RELAY_STATE"); throw ExceptionUtils.toBadRequestException(null, null); @@ -218,6 +246,7 @@ public abstract class AbstractRequestAssertionConsumerHandler extends AbstractSS reportError("EXPIRED_REQUEST_STATE"); throw ExceptionUtils.toBadRequestException(null, null); } + return requestState; } @@ -348,4 +377,29 @@ public abstract class AbstractRequestAssertionConsumerHandler extends AbstractSS public void setEnforceResponseSigned(boolean enforceResponseSigned) { this.enforceResponseSigned = enforceResponseSigned; } + + public String getApplicationURL() { + return applicationURL; + } + + /** + * Set the Application URL to forward to, for the unsolicited IdP case. + * @param applicationURL + */ + public void setApplicationURL(String applicationURL) { + this.applicationURL = applicationURL; + } + + public boolean isParseApplicationURLFromRelayState() { + return parseApplicationURLFromRelayState; + } + + /** + * Whether to parse the application URL to forward to from the RelayState, for the unsolicted IdP case. + * @param parseApplicationURLFromRelayState + */ + public void setParseApplicationURLFromRelayState(boolean parseApplicationURLFromRelayState) { + this.parseApplicationURLFromRelayState = parseApplicationURLFromRelayState; + } + } http://git-wip-us.apache.org/repos/asf/cxf/blob/cf461e4c/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractSSOSpHandler.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractSSOSpHandler.java b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractSSOSpHandler.java index dd0a516..11a5b0c 100644 --- a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractSSOSpHandler.java +++ b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractSSOSpHandler.java @@ -28,7 +28,9 @@ import javax.annotation.PreDestroy; import javax.security.auth.callback.CallbackHandler; import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.jaxrs.impl.UriInfoImpl; import org.apache.cxf.jaxrs.utils.HttpUtils; +import org.apache.cxf.message.Message; import org.apache.cxf.rs.security.saml.sso.state.SPStateManager; import org.apache.cxf.rt.security.utils.SecurityUtils; import org.apache.wss4j.common.crypto.Crypto; @@ -47,6 +49,9 @@ public class AbstractSSOSpHandler { private CallbackHandler callbackHandler; private String callbackHandlerClass; private String signatureUsername; + private String idpServiceAddress; + private String issuerId; + private boolean supportUnsolicited; static { OpenSAMLUtil.initSamlEngine(); @@ -203,4 +208,35 @@ public class AbstractSSOSpHandler { return signatureUsername; } + public void setIdpServiceAddress(String idpServiceAddress) { + this.idpServiceAddress = idpServiceAddress; + } + + public String getIdpServiceAddress() { + return idpServiceAddress; + } + + public void setIssuerId(String issuerId) { + this.issuerId = issuerId; + } + + protected String getIssuerId(Message m) { + if (issuerId == null) { + return new UriInfoImpl(m).getBaseUri().toString(); + } else { + return issuerId; + } + } + + public boolean isSupportUnsolicited() { + return supportUnsolicited; + } + + /** + * Whether to support unsolicited IdP initiated login or not. The default + * is false. + */ + public void setSupportUnsolicited(boolean supportUnsolicited) { + this.supportUnsolicited = supportUnsolicited; + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/cf461e4c/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractServiceProviderFilter.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractServiceProviderFilter.java b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractServiceProviderFilter.java index 3d88dc8..edb2a9a 100644 --- a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractServiceProviderFilter.java +++ b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractServiceProviderFilter.java @@ -75,8 +75,6 @@ public abstract class AbstractServiceProviderFilter extends AbstractSSOSpHandler protected static final ResourceBundle BUNDLE = BundleUtils.getBundle(AbstractServiceProviderFilter.class); - private String idpServiceAddress; - private String issuerId; private String assertionConsumerServiceAddress; private AuthnRequestBuilder authnRequestBuilder = new DefaultAuthnRequestBuilder(); private boolean signRequest; @@ -106,31 +104,11 @@ public abstract class AbstractServiceProviderFilter extends AbstractSSOSpHandler this.assertionConsumerServiceAddress = assertionConsumerServiceAddress; } - public void setIssuerId(String issuerId) { - this.issuerId = issuerId; - } - - public void setIdpServiceAddress(String idpServiceAddress) { - this.idpServiceAddress = idpServiceAddress; - } - - public String getIdpServiceAddress() { - return idpServiceAddress; - } - @PreDestroy public void close() { super.close(); } - private String getIssuerId(Message m) { - if (issuerId == null) { - return new UriInfoImpl(m).getBaseUri().toString(); - } else { - return issuerId; - } - } - protected boolean checkSecurityContext(Message m) { HttpHeaders headers = new HttpHeadersImpl(m); Map<String, Cookie> cookies = headers.getCookies(); @@ -142,16 +120,18 @@ public abstract class AbstractServiceProviderFilter extends AbstractSSOSpHandler return false; } - Cookie relayStateCookie = cookies.get(SSOConstants.RELAY_STATE); - if (relayStateCookie == null) { - reportError("MISSING_RELAY_COOKIE"); - return false; - } - String originalRelayState = responseState.getRelayState(); - if (!originalRelayState.equals(relayStateCookie.getValue())) { - // perhaps the response state should also be removed - reportError("INVALID_RELAY_STATE"); - return false; + if (!isSupportUnsolicited()) { + Cookie relayStateCookie = cookies.get(SSOConstants.RELAY_STATE); + if (relayStateCookie == null) { + reportError("MISSING_RELAY_COOKIE"); + return false; + } + String originalRelayState = responseState.getRelayState(); + if (!originalRelayState.equals(relayStateCookie.getValue())) { + // perhaps the response state should also be removed + reportError("INVALID_RELAY_STATE"); + return false; + } } try { String assertion = responseState.getAssertion(); @@ -241,7 +221,7 @@ public abstract class AbstractServiceProviderFilter extends AbstractSSOSpHandler m, getIssuerId(m), getAbsoluteAssertionServiceAddress(m) ); if (isSignRequest()) { - authnRequest.setDestination(idpServiceAddress); + authnRequest.setDestination(getIdpServiceAddress()); signAuthnRequest(authnRequest); } Element authnRequestElement = OpenSAMLUtil.toDom(authnRequest, doc); http://git-wip-us.apache.org/repos/asf/cxf/blob/cf461e4c/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java index c95e773..25083c1 100644 --- a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java +++ b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java @@ -262,6 +262,9 @@ public class SAMLSSOResponseValidator { if (requestId != null && !requestId.equals(subjectConfData.getInResponseTo())) { LOG.fine("The InResponseTo String does match the original request id " + requestId); throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); + } else if (requestId == null && subjectConfData.getInResponseTo() != null) { + LOG.fine("No InResponseTo String is allowed for the unsolicted case"); + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); } }
