Correct signature handling for SAML2 AuthNRequest
Implements the following bindings for AuthNRequest - REDIRECT - POST (future PR) Has been tested with - Keycloak - SSOCircle - Okta - SimpleSAMLPhp Fixes gh-7711
This commit is contained in:
parent
43098d41cc
commit
a51a202925
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Data holder for {@code AuthNRequest} parameters to be sent using either the
|
||||
* {@link Saml2MessageBinding#POST} or {@link Saml2MessageBinding#REDIRECT} binding.
|
||||
* Data will be encoded and possibly deflated, but will not be escaped for transport,
|
||||
* ie URL encoded, {@link org.springframework.web.util.UriUtils#encode(String, Charset)}
|
||||
* or HTML encoded, {@link org.springframework.web.util.HtmlUtils#htmlEscape(String)}.
|
||||
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031)
|
||||
*
|
||||
* @see Saml2AuthenticationRequestFactory#createPostAuthenticationRequest(Saml2AuthenticationRequestContext)
|
||||
* @see Saml2AuthenticationRequestFactory#createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)
|
||||
* @since 5.3
|
||||
*/
|
||||
abstract class AbstractSaml2AuthenticationRequest {
|
||||
|
||||
private final String samlRequest;
|
||||
private final String relayState;
|
||||
private final String authenticationRequestUri;
|
||||
|
||||
/**
|
||||
* Mandatory constructor for the {@link AbstractSaml2AuthenticationRequest}
|
||||
* @param samlRequest - the SAMLRequest XML data, SAML encoded, cannot be empty or null
|
||||
* @param relayState - RelayState value that accompanies the request, may be null
|
||||
* @param authenticationRequestUri - The authenticationRequestUri, a URL, where to send the XML message, cannot be empty or null
|
||||
*/
|
||||
AbstractSaml2AuthenticationRequest(
|
||||
String samlRequest,
|
||||
String relayState,
|
||||
String authenticationRequestUri) {
|
||||
Assert.hasText(samlRequest, "samlRequest cannot be null or empty");
|
||||
Assert.hasText(authenticationRequestUri, "authenticationRequestUri cannot be null or empty");
|
||||
this.authenticationRequestUri = authenticationRequestUri;
|
||||
this.samlRequest = samlRequest;
|
||||
this.relayState = relayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AuthNRequest XML value to be sent. This value is already encoded for transport.
|
||||
* If {@link #getBinding()} is {@link Saml2MessageBinding#REDIRECT} the value is deflated and SAML encoded.
|
||||
* If {@link #getBinding()} is {@link Saml2MessageBinding#POST} the value is SAML encoded.
|
||||
* @return the SAMLRequest parameter value
|
||||
*/
|
||||
public String getSamlRequest() {
|
||||
return this.samlRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RelayState value, if present in the parameters
|
||||
* @return the RelayState value, or null if not available
|
||||
*/
|
||||
public String getRelayState() {
|
||||
return this.relayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI endpoint that this AuthNRequest should be sent to.
|
||||
* @return the URI endpoint for this message
|
||||
*/
|
||||
public String getAuthenticationRequestUri() {
|
||||
return this.authenticationRequestUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binding this AuthNRequest will be sent and
|
||||
* encoded with. If {@link Saml2MessageBinding#REDIRECT} is used, the DEFLATE encoding will be automatically applied.
|
||||
* @return the binding this message will be sent with.
|
||||
*/
|
||||
public abstract Saml2MessageBinding getBinding();
|
||||
|
||||
/**
|
||||
* A builder for {@link AbstractSaml2AuthenticationRequest} and its subclasses.
|
||||
*/
|
||||
static class Builder<T extends Builder<T>> {
|
||||
String authenticationRequestUri;
|
||||
String samlRequest;
|
||||
String relayState;
|
||||
|
||||
protected Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Casting the return as the generic subtype, when returning itself
|
||||
* @return this object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final T _this() {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the {@code RelayState} parameter that will accompany this AuthNRequest
|
||||
*
|
||||
* @param relayState the relay state value, unencoded. if null or empty, the parameter will be removed from the
|
||||
* map.
|
||||
* @return this object
|
||||
*/
|
||||
public T relayState(String relayState) {
|
||||
this.relayState = relayState;
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code SAMLRequest} parameter that will accompany this AuthNRequest
|
||||
*
|
||||
* @param samlRequest the SAMLRequest parameter.
|
||||
* @return this object
|
||||
*/
|
||||
public T samlRequest(String samlRequest) {
|
||||
this.samlRequest = samlRequest;
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code authenticationRequestUri}, a URL that will receive the AuthNRequest message
|
||||
*
|
||||
* @param authenticationRequestUri the relay state value, unencoded.
|
||||
* @return this object
|
||||
*/
|
||||
public T authenticationRequestUri(String authenticationRequestUri) {
|
||||
this.authenticationRequestUri = authenticationRequestUri;
|
||||
return _this();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,17 +16,25 @@
|
|||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml.saml2.core.Issuer;
|
||||
import org.springframework.security.saml2.credentials.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.Builder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDeflate;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlEncode;
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
|
@ -35,11 +43,50 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
|
|||
private final OpenSamlImplementation saml = OpenSamlImplementation.getInstance();
|
||||
private String protocolBinding = SAMLConstants.SAML2_POST_BINDING_URI;
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
|
||||
return createAuthenticationRequest(request, request.getCredentials());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
|
||||
public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
|
||||
String xml = createAuthenticationRequest(context, context.getRelyingPartyRegistration().getSigningCredentials());
|
||||
return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
|
||||
.samlRequest(samlEncode(xml.getBytes(UTF_8)))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext context) {
|
||||
String xml = createAuthenticationRequest(context, emptyList());
|
||||
List<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration().getSigningCredentials();
|
||||
Builder result = Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context);
|
||||
|
||||
String deflatedAndEncoded = samlEncode(samlDeflate(xml));
|
||||
Map<String, String> signedParams = this.saml.signQueryParameters(
|
||||
signingCredentials,
|
||||
deflatedAndEncoded,
|
||||
context.getRelayState()
|
||||
);
|
||||
result.samlRequest(signedParams.get("SAMLRequest"))
|
||||
.relayState(signedParams.get("RelayState"))
|
||||
.sigAlg(signedParams.get("SigAlg"))
|
||||
.signature(signedParams.get("Signature"));
|
||||
return result.build();
|
||||
}
|
||||
|
||||
private String createAuthenticationRequest(Saml2AuthenticationRequestContext request, List<Saml2X509Credential> credentials) {
|
||||
return createAuthenticationRequest(Saml2AuthenticationRequest.withAuthenticationRequestContext(request).build(), credentials);
|
||||
}
|
||||
|
||||
private String createAuthenticationRequest(Saml2AuthenticationRequest context, List<Saml2X509Credential> credentials) {
|
||||
AuthnRequest auth = this.saml.buildSAMLObject(AuthnRequest.class);
|
||||
auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||
auth.setIssueInstant(new DateTime(this.clock.millis()));
|
||||
|
@ -47,14 +94,14 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
|
|||
auth.setIsPassive(Boolean.FALSE);
|
||||
auth.setProtocolBinding(protocolBinding);
|
||||
Issuer issuer = this.saml.buildSAMLObject(Issuer.class);
|
||||
issuer.setValue(request.getIssuer());
|
||||
issuer.setValue(context.getIssuer());
|
||||
auth.setIssuer(issuer);
|
||||
auth.setDestination(request.getDestination());
|
||||
auth.setAssertionConsumerServiceURL(request.getAssertionConsumerServiceUrl());
|
||||
auth.setDestination(context.getDestination());
|
||||
auth.setAssertionConsumerServiceURL(context.getAssertionConsumerServiceUrl());
|
||||
return this.saml.toXml(
|
||||
auth,
|
||||
request.getCredentials(),
|
||||
request.getIssuer()
|
||||
credentials,
|
||||
context.getIssuer()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -71,11 +118,14 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code protocolBinding} to use when generating authentication requests
|
||||
* Sets the {@code protocolBinding} to use when generating authentication requests.
|
||||
* Acceptable values are {@link SAMLConstants#SAML2_POST_BINDING_URI} and
|
||||
* {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI}
|
||||
* The IDP will be reading this value in the {@code AuthNRequest} to determine how to
|
||||
* send the Response/Assertion to the ACS URL, assertion consumer service URL.
|
||||
*
|
||||
* @param protocolBinding
|
||||
* @param protocolBinding either {@link SAMLConstants#SAML2_POST_BINDING_URI} or
|
||||
* {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI}
|
||||
* @throws IllegalArgumentException if the protocolBinding is not valid
|
||||
*/
|
||||
public void setProtocolBinding(String protocolBinding) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,10 +13,8 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.credentials.Saml2X509Credential;
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
|
||||
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
|
||||
|
@ -41,6 +39,7 @@ import org.opensaml.security.credential.CredentialSupport;
|
|||
import org.opensaml.security.credential.UsageType;
|
||||
import org.opensaml.security.x509.BasicX509Credential;
|
||||
import org.opensaml.xmlsec.SignatureSigningParameters;
|
||||
import org.opensaml.xmlsec.crypto.XMLSigningUtil;
|
||||
import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
|
||||
import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver;
|
||||
import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
|
||||
|
@ -48,21 +47,29 @@ import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyR
|
|||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureException;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureSupport;
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.credentials.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Utils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.QName;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getBuilderFactory;
|
||||
import static org.springframework.util.StringUtils.hasText;
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
|
@ -191,11 +198,68 @@ final class OpenSamlImplementation {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns query parameter after creating a Query String signature
|
||||
* All return values are unencoded and will need to be encoded prior to sending
|
||||
* The methods {@link UriUtils#encode(String, Charset)} and {@link UriUtils#decode(String, Charset)}
|
||||
* with the {@link StandardCharsets#ISO_8859_1} character set are used for all URL encoding/decoding.
|
||||
* @param signingCredentials - credentials to be used for signature
|
||||
* @return a map of unencoded query parameters with the following keys:
|
||||
* {@code {SAMLRequest, RelayState (may be null)}, SigAlg, Signature}
|
||||
*
|
||||
*/
|
||||
Map<String, String> signQueryParameters(
|
||||
List<Saml2X509Credential> signingCredentials,
|
||||
String samlRequest,
|
||||
String relayState) {
|
||||
Assert.notNull(samlRequest, "samlRequest cannot be null");
|
||||
String algorithmUri = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256;
|
||||
StringBuilder queryString = new StringBuilder();
|
||||
queryString
|
||||
.append("SAMLRequest")
|
||||
.append("=")
|
||||
.append(UriUtils.encode(samlRequest, StandardCharsets.ISO_8859_1))
|
||||
.append("&");
|
||||
if (hasText(relayState)) {
|
||||
queryString
|
||||
.append("RelayState")
|
||||
.append("=")
|
||||
.append(UriUtils.encode(relayState, StandardCharsets.ISO_8859_1))
|
||||
.append("&");
|
||||
}
|
||||
queryString
|
||||
.append("SigAlg")
|
||||
.append("=")
|
||||
.append(UriUtils.encode(algorithmUri, StandardCharsets.ISO_8859_1));
|
||||
|
||||
try {
|
||||
byte[] rawSignature = XMLSigningUtil.signWithURI(
|
||||
getSigningCredential(signingCredentials, ""),
|
||||
algorithmUri,
|
||||
queryString.toString().getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
String b64Signature = Saml2Utils.samlEncode(rawSignature);
|
||||
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
result.put("SAMLRequest", samlRequest);
|
||||
if (hasText(relayState)) {
|
||||
result.put("RelayState", relayState);
|
||||
}
|
||||
result.put("SigAlg", algorithmUri);
|
||||
result.put("Signature", b64Signature);
|
||||
return result;
|
||||
}
|
||||
catch (SecurityException e) {
|
||||
throw new Saml2Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ==============================================================
|
||||
* PRIVATE METHODS
|
||||
* ==============================================================
|
||||
*/
|
||||
|
||||
private XMLObject resolve(byte[] xml) {
|
||||
XMLObject parsed = parse(xml);
|
||||
if (parsed != null) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -29,9 +29,10 @@ import java.util.function.Consumer;
|
|||
* from the service provider to the identity provider
|
||||
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031)
|
||||
*
|
||||
* @see {@link Saml2AuthenticationRequestFactory}
|
||||
* @since 5.2
|
||||
* @deprecated use {@link Saml2AuthenticationRequestContext}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class Saml2AuthenticationRequest {
|
||||
private final String issuer;
|
||||
private final List<Saml2X509Credential> credentials;
|
||||
|
@ -55,7 +56,6 @@ public final class Saml2AuthenticationRequest {
|
|||
this.credentials.add(c);
|
||||
}
|
||||
}
|
||||
Assert.notEmpty(this.credentials, "at least one SIGNING credential must be present");
|
||||
}
|
||||
|
||||
|
||||
|
@ -104,6 +104,20 @@ public final class Saml2AuthenticationRequest {
|
|||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link Saml2AuthenticationRequest}.
|
||||
* @param context a context object to copy values from.
|
||||
* returns a builder object
|
||||
*/
|
||||
public static Builder withAuthenticationRequestContext(Saml2AuthenticationRequestContext context) {
|
||||
return new Builder()
|
||||
.assertionConsumerServiceUrl(context.getAssertionConsumerServiceUrl())
|
||||
.issuer(context.getIssuer())
|
||||
.destination(context.getDestination())
|
||||
.credentials(c -> c.addAll(context.getRelyingPartyRegistration().getCredentials()))
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link Saml2AuthenticationRequest}.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Data holder for information required to create an {@code AuthNRequest}
|
||||
* to be sent from the service provider to the identity provider
|
||||
* <a href="https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf">
|
||||
* Assertions and Protocols for SAML 2 (line 2031)</a>
|
||||
*
|
||||
* @see Saml2AuthenticationRequestFactory#createPostAuthenticationRequest(Saml2AuthenticationRequestContext)
|
||||
* @see Saml2AuthenticationRequestFactory#createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)
|
||||
* @since 5.3
|
||||
*/
|
||||
public final class Saml2AuthenticationRequestContext {
|
||||
private final RelyingPartyRegistration relyingPartyRegistration;
|
||||
private final String issuer;
|
||||
private final String assertionConsumerServiceUrl;
|
||||
private final String relayState;
|
||||
|
||||
private Saml2AuthenticationRequestContext(
|
||||
RelyingPartyRegistration relyingPartyRegistration,
|
||||
String issuer,
|
||||
String assertionConsumerServiceUrl,
|
||||
String relayState) {
|
||||
Assert.hasText(issuer, "issuer cannot be null or empty");
|
||||
Assert.notNull(relyingPartyRegistration, "relyingPartyRegistration cannot be null");
|
||||
Assert.hasText(assertionConsumerServiceUrl, "spAssertionConsumerServiceUrl cannot be null or empty");
|
||||
this.issuer = issuer;
|
||||
this.relyingPartyRegistration = relyingPartyRegistration;
|
||||
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
|
||||
this.relayState = relayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link RelyingPartyRegistration} configuration for which the AuthNRequest is intended for.
|
||||
* @return the {@link RelyingPartyRegistration} configuration
|
||||
*/
|
||||
public RelyingPartyRegistration getRelyingPartyRegistration() {
|
||||
return this.relyingPartyRegistration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Issuer} value to be used in the {@code AuthNRequest} object.
|
||||
* This property should be used to populate the {@code AuthNRequest.Issuer} XML element.
|
||||
* This value typically is a URI, but can be an arbitrary string.
|
||||
* @return the Issuer value
|
||||
*/
|
||||
public String getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the desired {@code AssertionConsumerServiceUrl} that this SP wishes to receive the
|
||||
* assertion on. The IDP may or may not honor this request.
|
||||
* This property populates the {@code AuthNRequest.AssertionConsumerServiceURL} XML attribute.
|
||||
* @return the AssertionConsumerServiceURL value
|
||||
*/
|
||||
public String getAssertionConsumerServiceUrl() {
|
||||
return assertionConsumerServiceUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RelayState value, if present in the parameters
|
||||
* @return the RelayState value, or null if not available
|
||||
*/
|
||||
public String getRelayState() {
|
||||
return this.relayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Destination}, the WEB Single Sign On URI, for this authentication request.
|
||||
* This property can also populate the {@code AuthNRequest.Destination} XML attribute.
|
||||
* @return the Destination value
|
||||
*/
|
||||
public String getDestination() {
|
||||
return this.getRelyingPartyRegistration().getIdpWebSsoUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link Saml2AuthenticationRequestContext}.
|
||||
* @return a builder object
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link Saml2AuthenticationRequestContext}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private String issuer;
|
||||
private String assertionConsumerServiceUrl;
|
||||
private String relayState;
|
||||
private RelyingPartyRegistration relyingPartyRegistration;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the issuer for the authentication request.
|
||||
* @param issuer - a required value
|
||||
* @return this {@code Builder}
|
||||
*/
|
||||
public Builder issuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RelyingPartyRegistration} used to build the authentication request.
|
||||
* @param relyingPartyRegistration - a required value
|
||||
* @return this {@code Builder}
|
||||
*/
|
||||
public Builder relyingPartyRegistration(RelyingPartyRegistration relyingPartyRegistration) {
|
||||
this.relyingPartyRegistration = relyingPartyRegistration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code assertionConsumerServiceURL} for the authentication request.
|
||||
* Typically the {@code Service Provider EntityID}
|
||||
* @param assertionConsumerServiceUrl - a required value
|
||||
* @return this {@code Builder}
|
||||
*/
|
||||
public Builder assertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
|
||||
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code RelayState} parameter that will accompany this AuthNRequest
|
||||
* @param relayState the relay state value, unencoded. if null or empty, the parameter will be removed from the map.
|
||||
* @return this object
|
||||
*/
|
||||
public Builder relayState(String relayState) {
|
||||
this.relayState = relayState;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Saml2AuthenticationRequestContext} object.
|
||||
* @return the Saml2AuthenticationRequest object
|
||||
* @throws {@link IllegalArgumentException} if a required property is not set
|
||||
*/
|
||||
public Saml2AuthenticationRequestContext build() {
|
||||
return new Saml2AuthenticationRequestContext(
|
||||
this.relyingPartyRegistration,
|
||||
this.issuer,
|
||||
this.assertionConsumerServiceUrl,
|
||||
this.relayState
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,25 +17,102 @@
|
|||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequest.withAuthenticationRequestContext;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDeflate;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlEncode;
|
||||
|
||||
/**
|
||||
* Component that generates an AuthenticationRequest, <code>samlp:AuthnRequestType</code> as defined by
|
||||
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
|
||||
* Component that generates AuthenticationRequest, <code>samlp:AuthnRequestType</code> XML, and accompanying
|
||||
* signature data.
|
||||
* as defined by https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf
|
||||
* Page 50, Line 2147
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
public interface Saml2AuthenticationRequestFactory {
|
||||
|
||||
/**
|
||||
* Creates an authentication request from the Service Provider, sp,
|
||||
* to the Identity Provider, idp.
|
||||
* Creates an authentication request from the Service Provider, sp, to the Identity Provider, idp.
|
||||
* The authentication result is an XML string that may be signed, encrypted, both or neither.
|
||||
* This method only returns the {@code SAMLRequest} string for the request, and for a complete
|
||||
* set of data parameters please use {@link #createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)}
|
||||
* or {@link #createPostAuthenticationRequest(Saml2AuthenticationRequestContext)}
|
||||
*
|
||||
* @param request - information about the identity provider, the recipient of this authentication request and
|
||||
* accompanying data
|
||||
* @return XML data in the format of a String. This data may be signed, encrypted, both signed and encrypted or
|
||||
* neither signed and encrypted
|
||||
* @param request information about the identity provider,
|
||||
* the recipient of this authentication request and accompanying data
|
||||
* @return XML data in the format of a String. This data may be signed, encrypted, both signed and encrypted with the
|
||||
* signature embedded in the XML or neither signed and encrypted
|
||||
* @throws Saml2Exception when a SAML library exception occurs
|
||||
* @since 5.2
|
||||
* @deprecated please use {@link #createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)}
|
||||
* or {@link #createPostAuthenticationRequest(Saml2AuthenticationRequestContext)}
|
||||
* This method will be removed in future versions of Spring Security
|
||||
*/
|
||||
@Deprecated
|
||||
String createAuthenticationRequest(Saml2AuthenticationRequest request);
|
||||
|
||||
/**
|
||||
* Creates all the necessary AuthNRequest parameters for a REDIRECT binding.
|
||||
* If the {@link Saml2AuthenticationRequestContext} doesn't contain any {@link Saml2X509CredentialType#SIGNING} credentials
|
||||
* the result will not contain any signatures.
|
||||
* The data set will be signed and encoded for REDIRECT binding including the DEFLATE encoding.
|
||||
* It will contain the following parameters to be sent as part of the query string:
|
||||
* {@code SAMLRequest, RelayState, SigAlg, Signature}.
|
||||
* <i>The default implementation, for sake of backwards compatibility, of this method returns the
|
||||
* SAMLRequest message with an XML signature embedded, that should only be used for the{@link Saml2MessageBinding#POST}
|
||||
* binding, but works over {@link Saml2MessageBinding#POST} with most providers.</i>
|
||||
* @param context - information about the identity provider, the recipient of this authentication request and
|
||||
* accompanying data
|
||||
* @return a {@link Saml2RedirectAuthenticationRequest} object with applicable http parameters
|
||||
* necessary to make the AuthNRequest over a POST or REDIRECT binding.
|
||||
* All parameters will be SAML encoded/deflated, but escaped, ie URI encoded or encoded for Form Data.
|
||||
* @throws Saml2Exception when a SAML library exception occurs
|
||||
* @since 5.3
|
||||
*/
|
||||
default Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(
|
||||
Saml2AuthenticationRequestContext context
|
||||
) {
|
||||
//backwards compatible with 5.2.x settings
|
||||
Saml2AuthenticationRequest.Builder resultBuilder = withAuthenticationRequestContext(context);
|
||||
String samlRequest = createAuthenticationRequest(resultBuilder.build());
|
||||
samlRequest = samlEncode(samlDeflate(samlRequest));
|
||||
return Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context)
|
||||
.samlRequest(samlRequest)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates all the necessary AuthNRequest parameters for a POST binding.
|
||||
* If the {@link Saml2AuthenticationRequestContext} doesn't contain any {@link Saml2X509CredentialType#SIGNING} credentials
|
||||
* the result will not contain any signatures.
|
||||
* The data set will be signed and encoded for POST binding and if applicable signed with XML signatures.
|
||||
* will contain the following parameters to be sent as part of the form data: {@code SAMLRequest, RelayState}.
|
||||
* <i>The default implementation of this method returns the SAMLRequest message with an XML signature embedded,
|
||||
* that should only be used for the {@link Saml2MessageBinding#POST} binding.</i>
|
||||
* @param context - information about the identity provider, the recipient of this authentication request and
|
||||
* accompanying data
|
||||
* @return a {@link Saml2PostAuthenticationRequest} object with applicable http parameters
|
||||
* necessary to make the AuthNRequest over a POST binding.
|
||||
* All parameters will be SAML encoded but not escaped for Form Data.
|
||||
* @throws Saml2Exception when a SAML library exception occurs
|
||||
* @since 5.3
|
||||
*/
|
||||
default Saml2PostAuthenticationRequest createPostAuthenticationRequest(
|
||||
Saml2AuthenticationRequestContext context
|
||||
) {
|
||||
//backwards compatible with 5.2.x settings
|
||||
Saml2AuthenticationRequest.Builder resultBuilder = withAuthenticationRequestContext(context);
|
||||
String samlRequest = createAuthenticationRequest(resultBuilder.build());
|
||||
samlRequest = samlEncode(samlRequest.getBytes(StandardCharsets.UTF_8));
|
||||
return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
|
||||
.samlRequest(samlRequest)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST;
|
||||
|
||||
/**
|
||||
* Data holder for information required to send an {@code AuthNRequest} over a POST binding
|
||||
* from the service provider to the identity provider
|
||||
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031)
|
||||
*
|
||||
* @see Saml2AuthenticationRequestFactory
|
||||
* @since 5.3
|
||||
*/
|
||||
public class Saml2PostAuthenticationRequest extends AbstractSaml2AuthenticationRequest {
|
||||
|
||||
private Saml2PostAuthenticationRequest(
|
||||
String samlRequest,
|
||||
String relayState,
|
||||
String authenticationRequestUri) {
|
||||
super(samlRequest, relayState, authenticationRequestUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Saml2MessageBinding#POST}
|
||||
*/
|
||||
@Override
|
||||
public Saml2MessageBinding getBinding() {
|
||||
return POST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link Builder} from a {@link Saml2AuthenticationRequestContext} object.
|
||||
* By default the {@link Saml2PostAuthenticationRequest#getAuthenticationRequestUri()} will be set to the
|
||||
* {@link Saml2AuthenticationRequestContext#getDestination()} value.
|
||||
* @param context input providing {@code Destination}, {@code RelayState}, and {@code Issuer} objects.
|
||||
* @return a modifiable builder object
|
||||
*/
|
||||
public static Builder withAuthenticationRequestContext(Saml2AuthenticationRequestContext context) {
|
||||
return new Builder()
|
||||
.authenticationRequestUri(context.getDestination())
|
||||
.relayState(context.getRelayState())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for a {@link Saml2PostAuthenticationRequest} object.
|
||||
*/
|
||||
public static class Builder extends AbstractSaml2AuthenticationRequest.Builder<Builder> {
|
||||
|
||||
private Builder() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an immutable {@link Saml2PostAuthenticationRequest} object.
|
||||
* @return an immutable {@link Saml2PostAuthenticationRequest} object.
|
||||
*/
|
||||
public Saml2PostAuthenticationRequest build() {
|
||||
return new Saml2PostAuthenticationRequest(
|
||||
this.samlRequest,
|
||||
this.relayState,
|
||||
this.authenticationRequestUri
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.REDIRECT;
|
||||
|
||||
/**
|
||||
* Data holder for information required to send an {@code AuthNRequest} over a REDIRECT binding
|
||||
* from the service provider to the identity provider
|
||||
* https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031)
|
||||
*
|
||||
* @see Saml2AuthenticationRequestFactory
|
||||
* @since 5.3
|
||||
*/
|
||||
public class Saml2RedirectAuthenticationRequest extends AbstractSaml2AuthenticationRequest {
|
||||
|
||||
private final String sigAlg;
|
||||
private final String signature;
|
||||
|
||||
private Saml2RedirectAuthenticationRequest(
|
||||
String samlRequest,
|
||||
String sigAlg,
|
||||
String signature,
|
||||
String relayState,
|
||||
String authenticationRequestUri) {
|
||||
super(samlRequest, relayState, authenticationRequestUri);
|
||||
this.sigAlg = sigAlg;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SigAlg value for {@link Saml2MessageBinding#REDIRECT} requests
|
||||
* @return the SigAlg value
|
||||
*/
|
||||
public String getSigAlg() {
|
||||
return this.sigAlg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Signature value for {@link Saml2MessageBinding#REDIRECT} requests
|
||||
* @return the Signature value
|
||||
*/
|
||||
public String getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Saml2MessageBinding#REDIRECT}
|
||||
*/
|
||||
@Override
|
||||
public Saml2MessageBinding getBinding() {
|
||||
return REDIRECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link Saml2RedirectAuthenticationRequest.Builder} from a {@link Saml2AuthenticationRequestContext} object.
|
||||
* By default the {@link Saml2RedirectAuthenticationRequest#getAuthenticationRequestUri()} will be set to the
|
||||
* {@link Saml2AuthenticationRequestContext#getDestination()} value.
|
||||
* @param context input providing {@code Destination}, {@code RelayState}, and {@code Issuer} objects.
|
||||
* @return a modifiable builder object
|
||||
*/
|
||||
public static Builder withAuthenticationRequestContext(Saml2AuthenticationRequestContext context) {
|
||||
return new Builder()
|
||||
.authenticationRequestUri(context.getDestination())
|
||||
.relayState(context.getRelayState())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for a {@link Saml2RedirectAuthenticationRequest} object.
|
||||
*/
|
||||
public static class Builder extends AbstractSaml2AuthenticationRequest.Builder<Builder> {
|
||||
private String sigAlg;
|
||||
private String signature;
|
||||
|
||||
private Builder() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code SigAlg} parameter that will accompany this AuthNRequest
|
||||
* @param sigAlg the SigAlg parameter value.
|
||||
* @return this object
|
||||
*/
|
||||
public Builder sigAlg(String sigAlg) {
|
||||
this.sigAlg = sigAlg;
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Signature} parameter that will accompany this AuthNRequest
|
||||
* @param signature the Signature parameter value.
|
||||
* @return this object
|
||||
*/
|
||||
public Builder signature(String signature) {
|
||||
this.signature = signature;
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an immutable {@link Saml2RedirectAuthenticationRequest} object.
|
||||
* @return an immutable {@link Saml2RedirectAuthenticationRequest} object.
|
||||
*/
|
||||
public Saml2RedirectAuthenticationRequest build() {
|
||||
return new Saml2RedirectAuthenticationRequest(
|
||||
this.samlRequest,
|
||||
this.sigAlg,
|
||||
this.signature,
|
||||
this.relayState,
|
||||
this.authenticationRequestUri
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterOutputStream;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.zip.Deflater.DEFLATED;
|
||||
|
||||
/**
|
||||
* @since 5.3
|
||||
*/
|
||||
final class Saml2Utils {
|
||||
|
||||
|
||||
private static Base64 BASE64 = new Base64(0, new byte[]{'\n'});
|
||||
|
||||
static String samlEncode(byte[] b) {
|
||||
return BASE64.encodeAsString(b);
|
||||
}
|
||||
|
||||
static byte[] samlDecode(String s) {
|
||||
return BASE64.decode(s);
|
||||
}
|
||||
|
||||
static byte[] samlDeflate(String s) {
|
||||
try {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(DEFLATED, true));
|
||||
deflater.write(s.getBytes(UTF_8));
|
||||
deflater.finish();
|
||||
return b.toByteArray();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new Saml2Exception("Unable to deflate string", e);
|
||||
}
|
||||
}
|
||||
|
||||
static String samlInflate(byte[] b) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
|
||||
iout.write(b);
|
||||
iout.finish();
|
||||
return new String(out.toByteArray(), UTF_8);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new Saml2Exception("Unable to inflate string", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.registration;
|
||||
|
||||
/**
|
||||
* The type of bindings that messages are exchanged using
|
||||
* Supported bindings are {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST}
|
||||
* and {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect}.
|
||||
* In addition there is support for {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect}
|
||||
* with an XML signature in the message rather than query parameters.
|
||||
* @since 5.3
|
||||
*/
|
||||
public enum Saml2MessageBinding {
|
||||
|
||||
POST("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"),
|
||||
REDIRECT("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
|
||||
|
||||
private final String urn;
|
||||
|
||||
Saml2MessageBinding(String s) {
|
||||
this.urn = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URN value from the SAML 2 specification for this binding.
|
||||
* @return URN value representing this binding
|
||||
*/
|
||||
public String getUrn() {
|
||||
return urn;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.servlet.filter;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.springframework.security.web.util.UrlUtils.buildFullRequestUrl;
|
||||
import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl;
|
||||
|
||||
/**
|
||||
* @since 5.3
|
||||
*/
|
||||
final class Saml2ServletUtils {
|
||||
|
||||
private static final char PATH_DELIMITER = '/';
|
||||
|
||||
static String getServiceProviderEntityId(RelyingPartyRegistration rp, HttpServletRequest request) {
|
||||
return resolveUrlTemplate(
|
||||
rp.getLocalEntityIdTemplate(),
|
||||
getApplicationUri(request),
|
||||
rp.getRemoteIdpEntityId(),
|
||||
rp.getRegistrationId()
|
||||
);
|
||||
}
|
||||
|
||||
static String resolveUrlTemplate(String template, String baseUrl, String entityId, String registrationId) {
|
||||
if (!StringUtils.hasText(template)) {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
Map<String, String> uriVariables = new HashMap<>();
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(baseUrl)
|
||||
.replaceQuery(null)
|
||||
.fragment(null)
|
||||
.build();
|
||||
String scheme = uriComponents.getScheme();
|
||||
uriVariables.put("baseScheme", scheme == null ? "" : scheme);
|
||||
String host = uriComponents.getHost();
|
||||
uriVariables.put("baseHost", host == null ? "" : host);
|
||||
// following logic is based on HierarchicalUriComponents#toUriString()
|
||||
int port = uriComponents.getPort();
|
||||
uriVariables.put("basePort", port == -1 ? "" : ":" + port);
|
||||
String path = uriComponents.getPath();
|
||||
if (StringUtils.hasLength(path)) {
|
||||
if (path.charAt(0) != PATH_DELIMITER) {
|
||||
path = PATH_DELIMITER + path;
|
||||
}
|
||||
}
|
||||
uriVariables.put("basePath", path == null ? "" : path);
|
||||
uriVariables.put("baseUrl", uriComponents.toUriString());
|
||||
uriVariables.put("entityId", StringUtils.hasText(entityId) ? entityId : "");
|
||||
uriVariables.put("registrationId", StringUtils.hasText(registrationId) ? registrationId : "");
|
||||
|
||||
return UriComponentsBuilder.fromUriString(template)
|
||||
.buildAndExpand(uriVariables)
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
static String getApplicationUri(HttpServletRequest request) {
|
||||
UriComponents uriComponents = fromHttpUrl(buildFullRequestUrl(request))
|
||||
.replacePath(request.getContextPath())
|
||||
.replaceQuery(null)
|
||||
.fragment(null)
|
||||
.build();
|
||||
return uriComponents.toUriString();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -18,43 +18,34 @@ package org.springframework.security.saml2.provider.service.servlet.filter;
|
|||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.zip.Deflater.DEFLATED;
|
||||
import static org.springframework.security.web.util.UrlUtils.buildFullRequestUrl;
|
||||
import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl;
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
* @since 5.3
|
||||
*/
|
||||
final class Saml2Utils {
|
||||
|
||||
private static final char PATH_DELIMITER = '/';
|
||||
private static org.apache.commons.codec.binary.Base64 BASE64 = new Base64(0, new byte[]{'\n'});
|
||||
|
||||
static String encode(byte[] b) {
|
||||
private static Base64 BASE64 = new Base64(0, new byte[]{'\n'});
|
||||
|
||||
static String samlEncode(byte[] b) {
|
||||
return BASE64.encodeAsString(b);
|
||||
}
|
||||
|
||||
static byte[] decode(String s) {
|
||||
static byte[] samlDecode(String s) {
|
||||
return BASE64.decode(s);
|
||||
}
|
||||
|
||||
static byte[] deflate(String s) {
|
||||
static byte[] samlDeflate(String s) {
|
||||
try {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(DEFLATED, true));
|
||||
|
@ -67,7 +58,7 @@ final class Saml2Utils {
|
|||
}
|
||||
}
|
||||
|
||||
static String inflate(byte[] b) {
|
||||
static String samlInflate(byte[] b) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
|
||||
|
@ -79,55 +70,4 @@ final class Saml2Utils {
|
|||
throw new Saml2Exception("Unable to inflate string", e);
|
||||
}
|
||||
}
|
||||
|
||||
static String getServiceProviderEntityId(RelyingPartyRegistration rp, HttpServletRequest request) {
|
||||
return resolveUrlTemplate(
|
||||
rp.getLocalEntityIdTemplate(),
|
||||
getApplicationUri(request),
|
||||
rp.getRemoteIdpEntityId(),
|
||||
rp.getRegistrationId()
|
||||
);
|
||||
}
|
||||
|
||||
static String resolveUrlTemplate(String template, String baseUrl, String entityId, String registrationId) {
|
||||
if (!StringUtils.hasText(template)) {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
Map<String, String> uriVariables = new HashMap<>();
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(baseUrl)
|
||||
.replaceQuery(null)
|
||||
.fragment(null)
|
||||
.build();
|
||||
String scheme = uriComponents.getScheme();
|
||||
uriVariables.put("baseScheme", scheme == null ? "" : scheme);
|
||||
String host = uriComponents.getHost();
|
||||
uriVariables.put("baseHost", host == null ? "" : host);
|
||||
// following logic is based on HierarchicalUriComponents#toUriString()
|
||||
int port = uriComponents.getPort();
|
||||
uriVariables.put("basePort", port == -1 ? "" : ":" + port);
|
||||
String path = uriComponents.getPath();
|
||||
if (StringUtils.hasLength(path)) {
|
||||
if (path.charAt(0) != PATH_DELIMITER) {
|
||||
path = PATH_DELIMITER + path;
|
||||
}
|
||||
}
|
||||
uriVariables.put("basePath", path == null ? "" : path);
|
||||
uriVariables.put("baseUrl", uriComponents.toUriString());
|
||||
uriVariables.put("entityId", StringUtils.hasText(entityId) ? entityId : "");
|
||||
uriVariables.put("registrationId", StringUtils.hasText(registrationId) ? registrationId : "");
|
||||
|
||||
return UriComponentsBuilder.fromUriString(template)
|
||||
.buildAndExpand(uriVariables)
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
static String getApplicationUri(HttpServletRequest request) {
|
||||
UriComponents uriComponents = fromHttpUrl(buildFullRequestUrl(request))
|
||||
.replacePath(request.getContextPath())
|
||||
.replaceQuery(null)
|
||||
.fragment(null)
|
||||
.build();
|
||||
return uriComponents.toUriString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,8 +61,8 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
|
|||
* @param filterProcessesUrl the processing URL, must contain a {registrationId} variable. Required.
|
||||
*/
|
||||
public Saml2WebSsoAuthenticationFilter(
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
|
||||
String filterProcessesUrl) {
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
|
||||
String filterProcessesUrl) {
|
||||
super(filterProcessesUrl);
|
||||
Assert.notNull(relyingPartyRegistrationRepository, "relyingPartyRegistrationRepository cannot be null");
|
||||
Assert.hasText(filterProcessesUrl, "filterProcessesUrl must contain a URL pattern");
|
||||
|
@ -86,7 +86,7 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
|
|||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||
throws AuthenticationException {
|
||||
String saml2Response = request.getParameter("SAMLResponse");
|
||||
byte[] b = Saml2Utils.decode(saml2Response);
|
||||
byte[] b = Saml2Utils.samlDecode(saml2Response);
|
||||
|
||||
String responseXml = inflateIfRequired(request, b);
|
||||
String registrationId = this.matcher.matcher(request).getVariables().get("registrationId");
|
||||
|
@ -97,7 +97,7 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
|
|||
"Relying Party Registration not found with ID: " + registrationId);
|
||||
throw new Saml2AuthenticationException(saml2Error);
|
||||
}
|
||||
String localSpEntityId = Saml2Utils.getServiceProviderEntityId(rp, request);
|
||||
String localSpEntityId = Saml2ServletUtils.getServiceProviderEntityId(rp, request);
|
||||
final Saml2AuthenticationToken authentication = new Saml2AuthenticationToken(
|
||||
responseXml,
|
||||
request.getRequestURL().toString(),
|
||||
|
@ -110,7 +110,7 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
|
|||
|
||||
private String inflateIfRequired(HttpServletRequest request, byte[] b) {
|
||||
if (HttpMethod.GET.matches(request.getMethod())) {
|
||||
return Saml2Utils.inflate(b);
|
||||
return Saml2Utils.samlInflate(b);
|
||||
}
|
||||
else {
|
||||
return new String(b, UTF_8);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,29 +17,28 @@
|
|||
package org.springframework.security.saml2.provider.service.servlet.filter;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestFactory;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher.MatchResult;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.springframework.security.saml2.provider.service.servlet.filter.Saml2Utils.deflate;
|
||||
import static org.springframework.security.saml2.provider.service.servlet.filter.Saml2Utils.encode;
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
import static org.springframework.util.StringUtils.hasText;
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
|
@ -47,9 +46,7 @@ import static org.springframework.security.saml2.provider.service.servlet.filter
|
|||
public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter {
|
||||
|
||||
private final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||
|
||||
private RequestMatcher redirectMatcher = new AntPathRequestMatcher("/saml2/authenticate/{registrationId}");
|
||||
|
||||
private Saml2AuthenticationRequestFactory authenticationRequestFactory = new OpenSamlAuthenticationRequestFactory();
|
||||
|
||||
public Saml2WebSsoAuthenticationRequestFilter(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||
|
@ -91,39 +88,47 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
|||
}
|
||||
|
||||
private String createSamlRequestRedirectUrl(HttpServletRequest request, RelyingPartyRegistration relyingParty) {
|
||||
Saml2AuthenticationRequest authNRequest = createAuthenticationRequest(relyingParty, request);
|
||||
String xml = this.authenticationRequestFactory.createAuthenticationRequest(authNRequest);
|
||||
String encoded = encode(deflate(xml));
|
||||
String relayState = request.getParameter("RelayState");
|
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
||||
.fromUriString(relyingParty.getIdpWebSsoUrl())
|
||||
.queryParam("SAMLRequest", UriUtils.encode(encoded, StandardCharsets.ISO_8859_1));
|
||||
|
||||
if (StringUtils.hasText(relayState)) {
|
||||
uriBuilder.queryParam("RelayState", UriUtils.encode(relayState, StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
|
||||
Saml2AuthenticationRequestContext authnRequest = createRedirectAuthenticationRequestContext(relyingParty, request);
|
||||
Saml2RedirectAuthenticationRequest authNData =
|
||||
this.authenticationRequestFactory.createRedirectAuthenticationRequest(authnRequest);
|
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(authNData.getAuthenticationRequestUri());
|
||||
addParameter("SAMLRequest", authNData.getSamlRequest(), uriBuilder);
|
||||
addParameter("RelayState", authNData.getRelayState(), uriBuilder);
|
||||
addParameter("SigAlg", authNData.getSigAlg(), uriBuilder);
|
||||
addParameter("Signature", authNData.getSignature(), uriBuilder);
|
||||
return uriBuilder
|
||||
.build(true)
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
private Saml2AuthenticationRequest createAuthenticationRequest(RelyingPartyRegistration relyingParty, HttpServletRequest request) {
|
||||
String localSpEntityId = Saml2Utils.getServiceProviderEntityId(relyingParty, request);
|
||||
return Saml2AuthenticationRequest
|
||||
private void addParameter(String name, String value, UriComponentsBuilder builder) {
|
||||
Assert.hasText(name, "name cannot be empty or null");
|
||||
if (hasText(value)) {
|
||||
builder.queryParam(
|
||||
UriUtils.encode(name, ISO_8859_1),
|
||||
UriUtils.encode(value, ISO_8859_1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Saml2AuthenticationRequestContext createRedirectAuthenticationRequestContext(
|
||||
RelyingPartyRegistration relyingParty,
|
||||
HttpServletRequest request) {
|
||||
String localSpEntityId = Saml2ServletUtils.getServiceProviderEntityId(relyingParty, request);
|
||||
return Saml2AuthenticationRequestContext
|
||||
.builder()
|
||||
.issuer(localSpEntityId)
|
||||
.destination(relyingParty.getIdpWebSsoUrl())
|
||||
.credentials(c -> c.addAll(relyingParty.getCredentials()))
|
||||
.relyingPartyRegistration(relyingParty)
|
||||
.assertionConsumerServiceUrl(
|
||||
Saml2Utils.resolveUrlTemplate(
|
||||
Saml2ServletUtils.resolveUrlTemplate(
|
||||
relyingParty.getAssertionConsumerServiceUrlTemplate(),
|
||||
Saml2Utils.getApplicationUri(request),
|
||||
Saml2ServletUtils.getApplicationUri(request),
|
||||
relyingParty.getRemoteIdpEntityId(),
|
||||
relyingParty.getRegistrationId()
|
||||
)
|
||||
)
|
||||
.build();
|
||||
.relayState(request.getParameter("RelayState"))
|
||||
.build()
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -23,39 +23,77 @@ import org.junit.Test;
|
|||
import org.junit.rules.ExpectedException;
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDecode;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.TestSaml2X509Credentials.relyingPartyCredentials;
|
||||
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST;
|
||||
import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.REDIRECT;
|
||||
|
||||
/**
|
||||
* Tests for {@link OpenSamlAuthenticationRequestFactory}
|
||||
*/
|
||||
public class OpenSamlAuthenticationRequestFactoryTests {
|
||||
|
||||
private OpenSamlAuthenticationRequestFactory factory;
|
||||
private Saml2AuthenticationRequest request;
|
||||
private Saml2AuthenticationRequestContext.Builder contextBuilder;
|
||||
private Saml2AuthenticationRequestContext context;
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
request = Saml2AuthenticationRequest.builder()
|
||||
.issuer("https://issuer")
|
||||
.destination("https://destination/sso")
|
||||
.assertionConsumerServiceUrl("https://issuer/sso")
|
||||
RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("id")
|
||||
.assertionConsumerServiceUrlTemplate("template")
|
||||
.idpWebSsoUrl("https://destination/sso")
|
||||
.remoteIdpEntityId("remote-entity-id")
|
||||
.localEntityIdTemplate("local-entity-id")
|
||||
.credentials(c -> c.addAll(relyingPartyCredentials()))
|
||||
.build();
|
||||
contextBuilder = Saml2AuthenticationRequestContext.builder()
|
||||
.issuer("https://issuer")
|
||||
.relyingPartyRegistration(registration)
|
||||
.assertionConsumerServiceUrl("https://issuer/sso");
|
||||
context = contextBuilder.build();
|
||||
factory = new OpenSamlAuthenticationRequestFactory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAuthenticationRequestWhenInvokingDeprecatedMethodThenReturnsXML() {
|
||||
Saml2AuthenticationRequest request = Saml2AuthenticationRequest.withAuthenticationRequestContext(context).build();
|
||||
String result = factory.createAuthenticationRequest(request);
|
||||
assertThat(result.replace("\n", "")).startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?><saml2p:AuthnRequest");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRedirectAuthenticationRequestWhenUsingContextThenAllValuesAreSet() {
|
||||
context = contextBuilder
|
||||
.relayState("Relay State Value")
|
||||
.build();
|
||||
Saml2RedirectAuthenticationRequest result = factory.createRedirectAuthenticationRequest(context);
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isEqualTo("Relay State Value");
|
||||
assertThat(result.getSigAlg()).isNotEmpty();
|
||||
assertThat(result.getSignature()).isNotEmpty();
|
||||
assertThat(result.getBinding()).isEqualTo(REDIRECT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAuthenticationRequestWhenDefaultThenReturnsPostBinding() {
|
||||
AuthnRequest authn = getAuthNRequest();
|
||||
AuthnRequest authn = getAuthNRequest(POST);
|
||||
Assert.assertEquals(SAMLConstants.SAML2_POST_BINDING_URI, authn.getProtocolBinding());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAuthenticationRequestWhenSetUriThenReturnsCorrectBinding() {
|
||||
factory.setProtocolBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
|
||||
AuthnRequest authn = getAuthNRequest();
|
||||
AuthnRequest authn = getAuthNRequest(POST);
|
||||
Assert.assertEquals(SAMLConstants.SAML2_REDIRECT_BINDING_URI, authn.getProtocolBinding());
|
||||
}
|
||||
|
||||
|
@ -66,8 +104,18 @@ public class OpenSamlAuthenticationRequestFactoryTests {
|
|||
factory.setProtocolBinding("my-invalid-binding");
|
||||
}
|
||||
|
||||
private AuthnRequest getAuthNRequest() {
|
||||
String xml = factory.createAuthenticationRequest(request);
|
||||
return (AuthnRequest) OpenSamlImplementation.getInstance().resolve(xml);
|
||||
private AuthnRequest getAuthNRequest(Saml2MessageBinding binding) {
|
||||
AbstractSaml2AuthenticationRequest result = (binding == REDIRECT) ?
|
||||
factory.createRedirectAuthenticationRequest(context) :
|
||||
factory.createPostAuthenticationRequest(context);
|
||||
String samlRequest = result.getSamlRequest();
|
||||
assertThat(samlRequest).isNotEmpty();
|
||||
if (result.getBinding() == REDIRECT) {
|
||||
samlRequest = Saml2Utils.samlInflate(samlDecode(samlRequest));
|
||||
}
|
||||
else {
|
||||
samlRequest = new String(samlDecode(samlRequest), StandardCharsets.UTF_8);
|
||||
}
|
||||
return (AuthnRequest) OpenSamlImplementation.getInstance().resolve(samlRequest);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,23 @@
|
|||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.opensaml.security.credential.BasicCredential;
|
||||
import org.opensaml.security.credential.Credential;
|
||||
import org.opensaml.security.credential.CredentialSupport;
|
||||
import org.opensaml.security.credential.UsageType;
|
||||
import org.opensaml.xmlsec.crypto.XMLSigningUtil;
|
||||
import org.springframework.security.saml2.credentials.Saml2X509Credential;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.opensaml.xmlsec.signature.support.SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.TestSaml2X509Credentials.assertingPartyCredentials;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.TestSaml2X509Credentials.relyingPartyCredentials;
|
||||
|
||||
public class OpenSamlImplementationTests {
|
||||
|
||||
|
@ -24,4 +41,42 @@ public class OpenSamlImplementationTests {
|
|||
public void getInstance() {
|
||||
OpenSamlImplementation.getInstance();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signQueryParametersWhenDataSuppliedReturnsValidSignature() throws Exception {
|
||||
OpenSamlImplementation impl = OpenSamlImplementation.getInstance();
|
||||
List<Saml2X509Credential> signCredentials = relyingPartyCredentials();
|
||||
List<Saml2X509Credential> verifyCredentials = assertingPartyCredentials();
|
||||
String samlRequest = "saml-request-example";
|
||||
String encoded = Saml2Utils.samlEncode(samlRequest.getBytes(UTF_8));
|
||||
String relayState = "test relay state";
|
||||
Map<String, String> parameters = impl.signQueryParameters(signCredentials, encoded, relayState);
|
||||
|
||||
String queryString = "SAMLRequest=" +
|
||||
UriUtils.encode(encoded, ISO_8859_1) +
|
||||
"&RelayState=" +
|
||||
UriUtils.encode(relayState, ISO_8859_1) +
|
||||
"&SigAlg=" +
|
||||
UriUtils.encode(ALGO_ID_SIGNATURE_RSA_SHA256, ISO_8859_1);
|
||||
|
||||
|
||||
byte[] signature = Saml2Utils.samlDecode(parameters.get("Signature"));
|
||||
boolean result = XMLSigningUtil.verifyWithURI(
|
||||
getOpenSamlCredential(verifyCredentials.get(1), "local-sp-entity-id", UsageType.SIGNING),
|
||||
ALGO_ID_SIGNATURE_RSA_SHA256,
|
||||
signature,
|
||||
queryString.getBytes(UTF_8)
|
||||
);
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
private Credential getOpenSamlCredential(Saml2X509Credential credential, String localSpEntityId, UsageType usageType) {
|
||||
BasicCredential cred = CredentialSupport.getSimpleCredential(
|
||||
credential.getCertificate(),
|
||||
credential.getPrivateKey()
|
||||
);
|
||||
cred.setEntityId(localSpEntityId);
|
||||
cred.setUsageType(usageType);
|
||||
return cred;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDecode;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlInflate;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.TestSaml2X509Credentials.relyingPartyCredentials;
|
||||
|
||||
/**
|
||||
* Tests for {@link Saml2AuthenticationRequestFactory} default interface methods
|
||||
*/
|
||||
public class Saml2AuthenticationRequestFactoryTests {
|
||||
|
||||
private RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("id")
|
||||
.assertionConsumerServiceUrlTemplate("template")
|
||||
.idpWebSsoUrl("https://example.com/destination")
|
||||
.remoteIdpEntityId("remote-entity-id")
|
||||
.localEntityIdTemplate("local-entity-id")
|
||||
.credentials(c -> c.addAll(relyingPartyCredentials()))
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void createAuthenticationRequestParametersWhenRedirectDefaultIsUsedMessageIsDeflatedAndEncoded() {
|
||||
final String value = "Test String: "+ UUID.randomUUID().toString();
|
||||
Saml2AuthenticationRequestFactory factory = request -> value;
|
||||
Saml2AuthenticationRequestContext request = Saml2AuthenticationRequestContext.builder()
|
||||
.relyingPartyRegistration(registration)
|
||||
.issuer("https://example.com/issuer")
|
||||
.assertionConsumerServiceUrl("https://example.com/acs-url")
|
||||
.build();
|
||||
Saml2RedirectAuthenticationRequest response = factory.createRedirectAuthenticationRequest(request);
|
||||
String resultValue = response.getSamlRequest();
|
||||
byte[] decoded = samlDecode(resultValue);
|
||||
String inflated = samlInflate(decoded);
|
||||
assertThat(inflated).isEqualTo(value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAuthenticationRequestParametersWhenPostDefaultIsUsedMessageIsEncoded() {
|
||||
final String value = "Test String: "+ UUID.randomUUID().toString();
|
||||
Saml2AuthenticationRequestFactory factory = request -> value;
|
||||
Saml2AuthenticationRequestContext request = Saml2AuthenticationRequestContext.builder()
|
||||
.relyingPartyRegistration(registration)
|
||||
.issuer("https://example.com/issuer")
|
||||
.assertionConsumerServiceUrl("https://example.com/acs-url")
|
||||
.build();
|
||||
Saml2PostAuthenticationRequest response = factory.createPostAuthenticationRequest(request);
|
||||
String resultValue = response.getSamlRequest();
|
||||
byte[] decoded = samlDecode(resultValue);
|
||||
assertThat(new String(decoded)).isEqualTo(value);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -50,7 +50,7 @@ public class Saml2UtilsTests {
|
|||
@Test
|
||||
public void decodeWhenUsingSamlUtilsBase64ThenXmlIsValid() throws Exception {
|
||||
String responseUrlDecoded = getSsoCircleEncodedXml();
|
||||
String xml = new String(Saml2Utils.decode(responseUrlDecoded), UTF_8);
|
||||
String xml = new String(Saml2Utils.samlDecode(responseUrlDecoded), UTF_8);
|
||||
validateSsoCircleXml(xml);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,10 +17,9 @@
|
|||
package org.springframework.security.saml2.provider.service.servlet.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
|
@ -28,18 +27,22 @@ import org.springframework.mock.web.MockHttpServletRequest;
|
|||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.saml2.provider.service.servlet.filter.TestSaml2SigningCredentials.signingCredential;
|
||||
|
||||
public class Saml2WebSsoAuthenticationRequestFilterTests {
|
||||
|
||||
private static final String IDP_SSO_URL = "https://sso-url.example.com/IDP/SSO";
|
||||
private Saml2WebSsoAuthenticationRequestFilter filter;
|
||||
private RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class);
|
||||
private MockHttpServletRequest request;
|
||||
private HttpServletResponse response;
|
||||
private MockHttpServletResponse response;
|
||||
private MockFilterChain filterChain;
|
||||
private RelyingPartyRegistration.Builder rpBuilder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
@ -49,43 +52,61 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
|
|||
request.setPathInfo("/saml2/authenticate/registration-id");
|
||||
|
||||
filterChain = new MockFilterChain();
|
||||
|
||||
rpBuilder = RelyingPartyRegistration
|
||||
.withRegistrationId("registration-id")
|
||||
.remoteIdpEntityId("idp-entity-id")
|
||||
.idpWebSsoUrl(IDP_SSO_URL)
|
||||
.assertionConsumerServiceUrlTemplate("template")
|
||||
.credentials(c -> c.add(signingCredential()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSamlRequestRedirectUrlAndReturnUrlWithoutRelayState() throws ServletException, IOException {
|
||||
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration
|
||||
.withRegistrationId("registration-id")
|
||||
.remoteIdpEntityId("idp-entity-id")
|
||||
.idpWebSsoUrl("sso-url")
|
||||
.assertionConsumerServiceUrlTemplate("template")
|
||||
.credentials(c -> c.add(signingCredential()))
|
||||
.build();
|
||||
|
||||
when(repository.findByRegistrationId("registration-id"))
|
||||
.thenReturn(relyingPartyRegistration);
|
||||
|
||||
public void doFilterWhenNoRelayStateThenRedirectDoesNotContainParameter() throws ServletException, IOException {
|
||||
when(repository.findByRegistrationId("registration-id")).thenReturn(rpBuilder.build());
|
||||
filter.doFilterInternal(request, response, filterChain);
|
||||
|
||||
Assert.assertFalse(response.getHeader("Location").contains("RelayState="));
|
||||
assertThat(response.getHeader("Location"))
|
||||
.doesNotContain("RelayState=")
|
||||
.startsWith(IDP_SSO_URL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSamlRequestRedirectUrlAndReturnUrlWithRelayState() throws ServletException, IOException {
|
||||
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration
|
||||
.withRegistrationId("registration-id")
|
||||
.remoteIdpEntityId("idp-entity-id")
|
||||
.idpWebSsoUrl("sso-url")
|
||||
.assertionConsumerServiceUrlTemplate("template")
|
||||
.credentials(c -> c.add(signingCredential()))
|
||||
.build();
|
||||
|
||||
when(repository.findByRegistrationId("registration-id"))
|
||||
.thenReturn(relyingPartyRegistration);
|
||||
|
||||
public void doFilterWhenRelayStateThenRedirectDoesContainParameter() throws ServletException, IOException {
|
||||
when(repository.findByRegistrationId("registration-id")).thenReturn(rpBuilder.build());
|
||||
request.setParameter("RelayState", "my-relay-state");
|
||||
|
||||
filter.doFilterInternal(request, response, filterChain);
|
||||
|
||||
Assert.assertTrue(response.getHeader("Location").contains("RelayState=my-relay-state"));
|
||||
assertThat(response.getHeader("Location"))
|
||||
.contains("RelayState=my-relay-state")
|
||||
.startsWith(IDP_SSO_URL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenRelayStateThatRequiresEncodingThenRedirectDoesContainsEncodedParameter() throws Exception {
|
||||
when(repository.findByRegistrationId("registration-id")).thenReturn(rpBuilder.build());
|
||||
final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param";
|
||||
final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1);
|
||||
request.setParameter("RelayState", relayStateValue);
|
||||
filter.doFilterInternal(request, response, filterChain);
|
||||
assertThat(response.getHeader("Location"))
|
||||
.contains("RelayState="+relayStateEncoded)
|
||||
.startsWith(IDP_SSO_URL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenSimpleSignatureSpecifiedThenSignatureParametersAreInTheRedirectURL() throws Exception {
|
||||
when(repository.findByRegistrationId("registration-id")).thenReturn(
|
||||
rpBuilder
|
||||
.build()
|
||||
);
|
||||
final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param";
|
||||
final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1);
|
||||
request.setParameter("RelayState", relayStateValue);
|
||||
filter.doFilterInternal(request, response, filterChain);
|
||||
assertThat(response.getHeader("Location"))
|
||||
.contains("RelayState="+relayStateEncoded)
|
||||
.contains("SigAlg=")
|
||||
.contains("Signature=")
|
||||
.startsWith(IDP_SSO_URL);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,12 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.samples;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.xml.security.algorithms.JCEMapper;
|
||||
import org.apache.xml.security.encryption.XMLCipherParameters;
|
||||
import org.joda.time.DateTime;
|
||||
|
@ -57,23 +54,16 @@ import org.opensaml.security.credential.CredentialSupport;
|
|||
import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters;
|
||||
import org.opensaml.xmlsec.encryption.support.EncryptionException;
|
||||
import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters;
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterOutputStream;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.zip.Deflater.DEFLATED;
|
||||
import static org.opensaml.security.crypto.KeySupport.generateKey;
|
||||
|
||||
/**
|
||||
|
@ -83,8 +73,6 @@ import static org.opensaml.security.crypto.KeySupport.generateKey;
|
|||
*/
|
||||
public class OpenSamlActionTestingSupport {
|
||||
|
||||
static Base64 UNCHUNKED_ENCODER = new Base64(0, new byte[] { '\n' });
|
||||
|
||||
/** ID used for all generated {@link Response} objects. */
|
||||
final static String REQUEST_ID = "request";
|
||||
|
||||
|
@ -94,40 +82,6 @@ public class OpenSamlActionTestingSupport {
|
|||
/** ID used for all generated {@link Assertion} objects. */
|
||||
final static String ASSERTION_ID = "assertion";
|
||||
|
||||
static String encode(byte[] b) {
|
||||
return UNCHUNKED_ENCODER.encodeToString(b);
|
||||
}
|
||||
|
||||
static byte[] decode(String s) {
|
||||
return UNCHUNKED_ENCODER.decode(s);
|
||||
}
|
||||
|
||||
static byte[] deflate(String s) {
|
||||
try {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(DEFLATED, true));
|
||||
deflater.write(s.getBytes(UTF_8));
|
||||
deflater.finish();
|
||||
return b.toByteArray();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new Saml2Exception("Unable to deflate string", e);
|
||||
}
|
||||
}
|
||||
|
||||
static String inflate(byte[] b) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
|
||||
iout.write(b);
|
||||
iout.finish();
|
||||
return new String(out.toByteArray(), UTF_8);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new Saml2Exception("Unable to inflate string", e);
|
||||
}
|
||||
}
|
||||
|
||||
static EncryptedAssertion encryptAssertion(Assertion assertion, X509Certificate certificate) {
|
||||
Encrypter encrypter = getEncrypter(certificate);
|
||||
try {
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,7 +13,8 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.samples;
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
|
||||
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
|
||||
|
@ -52,7 +53,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
|||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.util.AssertionErrors;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
@ -63,6 +63,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -73,19 +74,18 @@ import java.security.cert.CertificateException;
|
|||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.UUID;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.matchesRegex;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildConditions;
|
||||
import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildIssuer;
|
||||
import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildSubject;
|
||||
import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildSubjectConfirmation;
|
||||
import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildSubjectConfirmationData;
|
||||
import static org.springframework.security.samples.OpenSamlActionTestingSupport.encryptNameId;
|
||||
import static org.springframework.security.samples.OpenSamlActionTestingSupport.inflate;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildConditions;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildIssuer;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildSubject;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildSubjectConfirmation;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildSubjectConfirmationData;
|
||||
import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.encryptNameId;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
|
||||
import static org.springframework.security.web.WebAttributes.AUTHENTICATION_EXCEPTION;
|
||||
|
@ -133,10 +133,15 @@ public class Saml2LoginIntegrationTests {
|
|||
mockMvc.perform(
|
||||
get("http://localhost:8080/saml2/authenticate/simplesamlphp")
|
||||
.param("RelayState", "relay state value with spaces")
|
||||
.param("OtherParam", "OtherParamValue")
|
||||
.param("OtherParam2", "OtherParamValue2")
|
||||
)
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php?SAMLRequest=")))
|
||||
.andExpect(header().string("Location", containsString("RelayState=relay%20state%20value%20with%20spaces")));
|
||||
.andExpect(header().string("Location", containsString("RelayState=relay%20state%20value%20with%20spaces")))
|
||||
//check order of parameters
|
||||
.andExpect(header().string("Location", matchesRegex(".*\\?SAMLRequest\\=.*\\&RelayState\\=.*\\&SigAlg\\=.*\\&Signature\\=.*")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -151,7 +156,7 @@ public class Saml2LoginIntegrationTests {
|
|||
String request = parameters.getFirst("SAMLRequest");
|
||||
AssertionErrors.assertNotNull("SAMLRequest parameter is missing", request);
|
||||
request = URLDecoder.decode(request);
|
||||
request = inflate(OpenSamlActionTestingSupport.decode(request));
|
||||
request = Saml2Utils.samlInflate(Saml2Utils.samlDecode(request));
|
||||
AuthnRequest authnRequest = (AuthnRequest) fromXml(request);
|
||||
String destination = authnRequest.getDestination();
|
||||
assertEquals(
|
||||
|
@ -298,7 +303,7 @@ public class Saml2LoginIntegrationTests {
|
|||
String xml = toXml(response);
|
||||
return mockMvc.perform(post("http://localhost:8080/login/saml2/sso/simplesamlphp")
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.param("SAMLResponse", OpenSamlActionTestingSupport.encode(xml.getBytes(UTF_8))))
|
||||
.param("SAMLResponse", Saml2Utils.samlEncode(xml.getBytes(UTF_8))))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(redirectedUrl(redirectUrl));
|
||||
}
|
Loading…
Reference in New Issue