15 changed files with 1404 additions and 187 deletions
@ -0,0 +1,163 @@
@@ -0,0 +1,163 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.web.authentication; |
||||
|
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.Map; |
||||
import java.util.UUID; |
||||
import java.util.function.BiConsumer; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
|
||||
import net.shibboleth.utilities.java.support.xml.SerializeSupport; |
||||
import org.opensaml.core.config.ConfigurationService; |
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry; |
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; |
||||
import org.opensaml.core.xml.io.MarshallingException; |
||||
import org.opensaml.saml.saml2.core.AuthnRequest; |
||||
import org.opensaml.saml.saml2.core.Issuer; |
||||
import org.opensaml.saml.saml2.core.NameID; |
||||
import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder; |
||||
import org.opensaml.saml.saml2.core.impl.AuthnRequestMarshaller; |
||||
import org.opensaml.saml.saml2.core.impl.IssuerBuilder; |
||||
import org.opensaml.saml.saml2.core.impl.NameIDBuilder; |
||||
import org.w3c.dom.Element; |
||||
|
||||
import org.springframework.security.saml2.Saml2Exception; |
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService; |
||||
import org.springframework.security.saml2.core.Saml2ParameterNames; |
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; |
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; |
||||
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.Saml2MessageBinding; |
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
import org.springframework.security.web.util.matcher.RequestMatcher; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* For internal use only. Intended for consolidating common behavior related to minting a |
||||
* SAML 2.0 Authn Request. |
||||
*/ |
||||
class OpenSamlAuthenticationRequestResolver { |
||||
|
||||
static { |
||||
OpenSamlInitializationService.initialize(); |
||||
} |
||||
|
||||
private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/saml2/authenticate/{registrationId}"); |
||||
|
||||
private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver; |
||||
|
||||
private final AuthnRequestBuilder authnRequestBuilder; |
||||
|
||||
private final AuthnRequestMarshaller marshaller; |
||||
|
||||
private final IssuerBuilder issuerBuilder; |
||||
|
||||
private final NameIDBuilder nameIdBuilder; |
||||
|
||||
/** |
||||
* Construct a {@link OpenSamlAuthenticationRequestResolver} using the provided |
||||
* parameters |
||||
* @param relyingPartyRegistrationResolver a strategy for resolving the |
||||
* {@link RelyingPartyRegistration} from the {@link HttpServletRequest} |
||||
*/ |
||||
OpenSamlAuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { |
||||
Assert.notNull(relyingPartyRegistrationResolver, "relyingPartyRegistrationResolver cannot be null"); |
||||
this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver; |
||||
XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); |
||||
this.marshaller = (AuthnRequestMarshaller) registry.getMarshallerFactory() |
||||
.getMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME); |
||||
Assert.notNull(this.marshaller, "logoutRequestMarshaller must be configured in OpenSAML"); |
||||
this.authnRequestBuilder = (AuthnRequestBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory() |
||||
.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME); |
||||
Assert.notNull(this.authnRequestBuilder, "authnRequestBuilder must be configured in OpenSAML"); |
||||
this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME); |
||||
Assert.notNull(this.issuerBuilder, "issuerBuilder must be configured in OpenSAML"); |
||||
this.nameIdBuilder = (NameIDBuilder) registry.getBuilderFactory().getBuilder(NameID.DEFAULT_ELEMENT_NAME); |
||||
Assert.notNull(this.nameIdBuilder, "nameIdBuilder must be configured in OpenSAML"); |
||||
} |
||||
|
||||
<T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) { |
||||
return resolve(request, (registration, logoutRequest) -> { |
||||
}); |
||||
} |
||||
|
||||
<T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request, |
||||
BiConsumer<RelyingPartyRegistration, AuthnRequest> authnRequestConsumer) { |
||||
RequestMatcher.MatchResult result = this.requestMatcher.matcher(request); |
||||
if (!result.isMatch()) { |
||||
return null; |
||||
} |
||||
String registrationId = result.getVariables().get("registrationId"); |
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationResolver.resolve(request, registrationId); |
||||
if (registration == null) { |
||||
return null; |
||||
} |
||||
AuthnRequest authnRequest = this.authnRequestBuilder.buildObject(); |
||||
authnRequest.setForceAuthn(Boolean.FALSE); |
||||
authnRequest.setIsPassive(Boolean.FALSE); |
||||
authnRequest.setProtocolBinding(registration.getAssertionConsumerServiceBinding().getUrn()); |
||||
Issuer iss = this.issuerBuilder.buildObject(); |
||||
iss.setValue(registration.getEntityId()); |
||||
authnRequest.setIssuer(iss); |
||||
authnRequest.setDestination(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()); |
||||
authnRequest.setAssertionConsumerServiceURL(registration.getAssertionConsumerServiceLocation()); |
||||
authnRequestConsumer.accept(registration, authnRequest); |
||||
if (authnRequest.getID() == null) { |
||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1)); |
||||
} |
||||
String relayState = UUID.randomUUID().toString(); |
||||
Saml2MessageBinding binding = registration.getAssertingPartyDetails().getSingleSignOnServiceBinding(); |
||||
if (binding == Saml2MessageBinding.POST) { |
||||
if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) { |
||||
OpenSamlSigningUtils.sign(authnRequest, registration); |
||||
} |
||||
String xml = serialize(authnRequest); |
||||
String encoded = Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8)); |
||||
return (T) Saml2PostAuthenticationRequest.withRelyingPartyRegistration(registration).samlRequest(encoded) |
||||
.relayState(relayState).build(); |
||||
} |
||||
else { |
||||
String xml = serialize(authnRequest); |
||||
String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml)); |
||||
Saml2RedirectAuthenticationRequest.Builder builder = Saml2RedirectAuthenticationRequest |
||||
.withRelyingPartyRegistration(registration).samlRequest(deflatedAndEncoded).relayState(relayState); |
||||
if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) { |
||||
Map<String, String> parameters = OpenSamlSigningUtils.sign(registration) |
||||
.param(Saml2ParameterNames.SAML_REQUEST, deflatedAndEncoded) |
||||
.param(Saml2ParameterNames.RELAY_STATE, relayState).parameters(); |
||||
builder.sigAlg(parameters.get(Saml2ParameterNames.SIG_ALG)) |
||||
.signature(parameters.get(Saml2ParameterNames.SIGNATURE)); |
||||
} |
||||
return (T) builder.build(); |
||||
} |
||||
} |
||||
|
||||
private String serialize(AuthnRequest authnRequest) { |
||||
try { |
||||
Element element = this.marshaller.marshall(authnRequest); |
||||
return SerializeSupport.nodeToString(element); |
||||
} |
||||
catch (MarshallingException ex) { |
||||
throw new Saml2Exception(ex); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.web.authentication; |
||||
|
||||
import java.nio.charset.StandardCharsets; |
||||
import java.security.PrivateKey; |
||||
import java.security.cert.X509Certificate; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import net.shibboleth.utilities.java.support.resolver.CriteriaSet; |
||||
import net.shibboleth.utilities.java.support.xml.SerializeSupport; |
||||
import org.opensaml.core.xml.XMLObject; |
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; |
||||
import org.opensaml.core.xml.io.Marshaller; |
||||
import org.opensaml.core.xml.io.MarshallingException; |
||||
import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; |
||||
import org.opensaml.security.SecurityException; |
||||
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.SignatureSigningParameters; |
||||
import org.opensaml.xmlsec.SignatureSigningParametersResolver; |
||||
import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; |
||||
import org.opensaml.xmlsec.crypto.XMLSigningUtil; |
||||
import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; |
||||
import org.opensaml.xmlsec.signature.SignableXMLObject; |
||||
import org.opensaml.xmlsec.signature.support.SignatureConstants; |
||||
import org.opensaml.xmlsec.signature.support.SignatureSupport; |
||||
import org.w3c.dom.Element; |
||||
|
||||
import org.springframework.security.saml2.Saml2Exception; |
||||
import org.springframework.security.saml2.core.Saml2X509Credential; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
import org.springframework.web.util.UriUtils; |
||||
|
||||
/** |
||||
* Utility methods for signing SAML components with OpenSAML |
||||
* |
||||
* For internal use only. |
||||
* |
||||
* @author Josh Cummings |
||||
*/ |
||||
final class OpenSamlSigningUtils { |
||||
|
||||
static String serialize(XMLObject object) { |
||||
try { |
||||
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); |
||||
Element element = marshaller.marshall(object); |
||||
return SerializeSupport.nodeToString(element); |
||||
} |
||||
catch (MarshallingException ex) { |
||||
throw new Saml2Exception(ex); |
||||
} |
||||
} |
||||
|
||||
static <O extends SignableXMLObject> O sign(O object, RelyingPartyRegistration relyingPartyRegistration) { |
||||
SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration); |
||||
try { |
||||
SignatureSupport.signObject(object, parameters); |
||||
return object; |
||||
} |
||||
catch (Exception ex) { |
||||
throw new Saml2Exception(ex); |
||||
} |
||||
} |
||||
|
||||
static QueryParametersPartial sign(RelyingPartyRegistration registration) { |
||||
return new QueryParametersPartial(registration); |
||||
} |
||||
|
||||
private static SignatureSigningParameters resolveSigningParameters( |
||||
RelyingPartyRegistration relyingPartyRegistration) { |
||||
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration); |
||||
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms(); |
||||
List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); |
||||
String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; |
||||
SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); |
||||
CriteriaSet criteria = new CriteriaSet(); |
||||
BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); |
||||
signingConfiguration.setSigningCredentials(credentials); |
||||
signingConfiguration.setSignatureAlgorithms(algorithms); |
||||
signingConfiguration.setSignatureReferenceDigestMethods(digests); |
||||
signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); |
||||
criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration)); |
||||
try { |
||||
SignatureSigningParameters parameters = resolver.resolveSingle(criteria); |
||||
Assert.notNull(parameters, "Failed to resolve any signing credential"); |
||||
return parameters; |
||||
} |
||||
catch (Exception ex) { |
||||
throw new Saml2Exception(ex); |
||||
} |
||||
} |
||||
|
||||
private static List<Credential> resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) { |
||||
List<Credential> credentials = new ArrayList<>(); |
||||
for (Saml2X509Credential x509Credential : relyingPartyRegistration.getSigningX509Credentials()) { |
||||
X509Certificate certificate = x509Credential.getCertificate(); |
||||
PrivateKey privateKey = x509Credential.getPrivateKey(); |
||||
BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); |
||||
credential.setEntityId(relyingPartyRegistration.getEntityId()); |
||||
credential.setUsageType(UsageType.SIGNING); |
||||
credentials.add(credential); |
||||
} |
||||
return credentials; |
||||
} |
||||
|
||||
private OpenSamlSigningUtils() { |
||||
|
||||
} |
||||
|
||||
static class QueryParametersPartial { |
||||
|
||||
final RelyingPartyRegistration registration; |
||||
|
||||
final Map<String, String> components = new LinkedHashMap<>(); |
||||
|
||||
QueryParametersPartial(RelyingPartyRegistration registration) { |
||||
this.registration = registration; |
||||
} |
||||
|
||||
QueryParametersPartial param(String key, String value) { |
||||
this.components.put(key, value); |
||||
return this; |
||||
} |
||||
|
||||
Map<String, String> parameters() { |
||||
SignatureSigningParameters parameters = resolveSigningParameters(this.registration); |
||||
Credential credential = parameters.getSigningCredential(); |
||||
String algorithmUri = parameters.getSignatureAlgorithm(); |
||||
this.components.put("SigAlg", algorithmUri); |
||||
UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); |
||||
for (Map.Entry<String, String> component : this.components.entrySet()) { |
||||
builder.queryParam(component.getKey(), |
||||
UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); |
||||
} |
||||
String queryString = builder.build(true).toString().substring(1); |
||||
try { |
||||
byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, |
||||
queryString.getBytes(StandardCharsets.UTF_8)); |
||||
String b64Signature = Saml2Utils.samlEncode(rawSignature); |
||||
this.components.put("Signature", b64Signature); |
||||
} |
||||
catch (SecurityException ex) { |
||||
throw new Saml2Exception(ex); |
||||
} |
||||
return this.components; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,207 @@
@@ -0,0 +1,207 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.web.authentication; |
||||
|
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.HashSet; |
||||
import java.util.Set; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
|
||||
import net.shibboleth.utilities.java.support.resolver.CriteriaSet; |
||||
import org.opensaml.core.criterion.EntityIdCriterion; |
||||
import org.opensaml.saml.common.xml.SAMLConstants; |
||||
import org.opensaml.saml.criterion.ProtocolCriterion; |
||||
import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; |
||||
import org.opensaml.saml.saml2.core.Issuer; |
||||
import org.opensaml.saml.saml2.core.RequestAbstractType; |
||||
import org.opensaml.saml.saml2.core.StatusResponseType; |
||||
import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; |
||||
import org.opensaml.security.credential.Credential; |
||||
import org.opensaml.security.credential.CredentialResolver; |
||||
import org.opensaml.security.credential.UsageType; |
||||
import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; |
||||
import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; |
||||
import org.opensaml.security.credential.impl.CollectionCredentialResolver; |
||||
import org.opensaml.security.criteria.UsageCriterion; |
||||
import org.opensaml.security.x509.BasicX509Credential; |
||||
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; |
||||
import org.opensaml.xmlsec.signature.Signature; |
||||
import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; |
||||
import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; |
||||
|
||||
import org.springframework.security.saml2.core.Saml2Error; |
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes; |
||||
import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; |
||||
import org.springframework.security.saml2.core.Saml2X509Credential; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.web.util.UriUtils; |
||||
|
||||
/** |
||||
* Utility methods for verifying SAML component signatures with OpenSAML |
||||
* |
||||
* For internal use only. |
||||
* |
||||
* @author Josh Cummings |
||||
*/ |
||||
|
||||
final class OpenSamlVerificationUtils { |
||||
|
||||
static VerifierPartial verifySignature(StatusResponseType object, RelyingPartyRegistration registration) { |
||||
return new VerifierPartial(object, registration); |
||||
} |
||||
|
||||
static VerifierPartial verifySignature(RequestAbstractType object, RelyingPartyRegistration registration) { |
||||
return new VerifierPartial(object, registration); |
||||
} |
||||
|
||||
private OpenSamlVerificationUtils() { |
||||
|
||||
} |
||||
|
||||
static class VerifierPartial { |
||||
|
||||
private final String id; |
||||
|
||||
private final CriteriaSet criteria; |
||||
|
||||
private final SignatureTrustEngine trustEngine; |
||||
|
||||
VerifierPartial(StatusResponseType object, RelyingPartyRegistration registration) { |
||||
this.id = object.getID(); |
||||
this.criteria = verificationCriteria(object.getIssuer()); |
||||
this.trustEngine = trustEngine(registration); |
||||
} |
||||
|
||||
VerifierPartial(RequestAbstractType object, RelyingPartyRegistration registration) { |
||||
this.id = object.getID(); |
||||
this.criteria = verificationCriteria(object.getIssuer()); |
||||
this.trustEngine = trustEngine(registration); |
||||
} |
||||
|
||||
Saml2ResponseValidatorResult redirect(HttpServletRequest request, String objectParameterName) { |
||||
RedirectSignature signature = new RedirectSignature(request, objectParameterName); |
||||
if (signature.getAlgorithm() == null) { |
||||
return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, |
||||
"Missing signature algorithm for object [" + this.id + "]")); |
||||
} |
||||
if (!signature.hasSignature()) { |
||||
return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, |
||||
"Missing signature for object [" + this.id + "]")); |
||||
} |
||||
Collection<Saml2Error> errors = new ArrayList<>(); |
||||
String algorithmUri = signature.getAlgorithm(); |
||||
try { |
||||
if (!this.trustEngine.validate(signature.getSignature(), signature.getContent(), algorithmUri, |
||||
this.criteria, null)) { |
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, |
||||
"Invalid signature for object [" + this.id + "]")); |
||||
} |
||||
} |
||||
catch (Exception ex) { |
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, |
||||
"Invalid signature for object [" + this.id + "]: ")); |
||||
} |
||||
return Saml2ResponseValidatorResult.failure(errors); |
||||
} |
||||
|
||||
Saml2ResponseValidatorResult post(Signature signature) { |
||||
Collection<Saml2Error> errors = new ArrayList<>(); |
||||
SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); |
||||
try { |
||||
profileValidator.validate(signature); |
||||
} |
||||
catch (Exception ex) { |
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, |
||||
"Invalid signature for object [" + this.id + "]: ")); |
||||
} |
||||
|
||||
try { |
||||
if (!this.trustEngine.validate(signature, this.criteria)) { |
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, |
||||
"Invalid signature for object [" + this.id + "]")); |
||||
} |
||||
} |
||||
catch (Exception ex) { |
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, |
||||
"Invalid signature for object [" + this.id + "]: ")); |
||||
} |
||||
|
||||
return Saml2ResponseValidatorResult.failure(errors); |
||||
} |
||||
|
||||
private CriteriaSet verificationCriteria(Issuer issuer) { |
||||
CriteriaSet criteria = new CriteriaSet(); |
||||
criteria.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue()))); |
||||
criteria.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS))); |
||||
criteria.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); |
||||
return criteria; |
||||
} |
||||
|
||||
private SignatureTrustEngine trustEngine(RelyingPartyRegistration registration) { |
||||
Set<Credential> credentials = new HashSet<>(); |
||||
Collection<Saml2X509Credential> keys = registration.getAssertingPartyDetails() |
||||
.getVerificationX509Credentials(); |
||||
for (Saml2X509Credential key : keys) { |
||||
BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); |
||||
cred.setUsageType(UsageType.SIGNING); |
||||
cred.setEntityId(registration.getAssertingPartyDetails().getEntityId()); |
||||
credentials.add(cred); |
||||
} |
||||
CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); |
||||
return new ExplicitKeySignatureTrustEngine(credentialsResolver, |
||||
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); |
||||
} |
||||
|
||||
private static class RedirectSignature { |
||||
|
||||
private final HttpServletRequest request; |
||||
|
||||
private final String objectParameterName; |
||||
|
||||
RedirectSignature(HttpServletRequest request, String objectParameterName) { |
||||
this.request = request; |
||||
this.objectParameterName = objectParameterName; |
||||
} |
||||
|
||||
String getAlgorithm() { |
||||
return this.request.getParameter("SigAlg"); |
||||
} |
||||
|
||||
byte[] getContent() { |
||||
String query = String.format("%s=%s&SigAlg=%s", this.objectParameterName, |
||||
UriUtils.encode(this.request.getParameter(this.objectParameterName), |
||||
StandardCharsets.ISO_8859_1), |
||||
UriUtils.encode(getAlgorithm(), StandardCharsets.ISO_8859_1)); |
||||
return query.getBytes(StandardCharsets.UTF_8); |
||||
} |
||||
|
||||
byte[] getSignature() { |
||||
return Saml2Utils.samlDecode(this.request.getParameter("Signature")); |
||||
} |
||||
|
||||
boolean hasSignature() { |
||||
return this.request.getParameter("Signature") != null; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.web.authentication; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; |
||||
|
||||
/** |
||||
* A strategy for resolving a SAML 2.0 Authentication Request from the |
||||
* {@link HttpServletRequest}. |
||||
* |
||||
* @author Josh Cummings |
||||
* @since 5.7 |
||||
*/ |
||||
public interface Saml2AuthenticationRequestResolver { |
||||
|
||||
<T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request); |
||||
|
||||
} |
||||
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.web.authentication; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.zip.Deflater; |
||||
import java.util.zip.DeflaterOutputStream; |
||||
import java.util.zip.Inflater; |
||||
import java.util.zip.InflaterOutputStream; |
||||
|
||||
import org.apache.commons.codec.binary.Base64; |
||||
|
||||
import org.springframework.security.saml2.Saml2Exception; |
||||
|
||||
/** |
||||
* Utility methods for working with serialized SAML messages. |
||||
* |
||||
* For internal use only. |
||||
* |
||||
* @author Josh Cummings |
||||
*/ |
||||
final class Saml2Utils { |
||||
|
||||
private static Base64 BASE64 = new Base64(0, new byte[] { '\n' }); |
||||
|
||||
private Saml2Utils() { |
||||
} |
||||
|
||||
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(Deflater.DEFLATED, true)); |
||||
deflater.write(s.getBytes(StandardCharsets.UTF_8)); |
||||
deflater.finish(); |
||||
return b.toByteArray(); |
||||
} |
||||
catch (IOException ex) { |
||||
throw new Saml2Exception("Unable to deflate string", ex); |
||||
} |
||||
} |
||||
|
||||
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(), StandardCharsets.UTF_8); |
||||
} |
||||
catch (IOException ex) { |
||||
throw new Saml2Exception("Unable to inflate string", ex); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,113 @@
@@ -0,0 +1,113 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.web.authentication; |
||||
|
||||
import java.time.Clock; |
||||
import java.util.function.Consumer; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
|
||||
import org.joda.time.DateTime; |
||||
import org.opensaml.saml.saml2.core.AuthnRequest; |
||||
import org.opensaml.saml.saml2.core.LogoutRequest; |
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A strategy for resolving a SAML 2.0 Authentication Request from the |
||||
* {@link HttpServletRequest} using OpenSAML. |
||||
* |
||||
* @author Josh Cummings |
||||
* @since 5.7 |
||||
* @deprecated OpenSAML 3 has reached end-of-life so this version is no longer recommended |
||||
*/ |
||||
@Deprecated |
||||
public final class OpenSaml3AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver { |
||||
|
||||
private final OpenSamlAuthenticationRequestResolver authnRequestResolver; |
||||
|
||||
private Consumer<AuthnRequestContext> contextConsumer = (parameters) -> { |
||||
}; |
||||
|
||||
private Clock clock = Clock.systemUTC(); |
||||
|
||||
/** |
||||
* Construct a {@link OpenSaml3AuthenticationRequestResolver} |
||||
*/ |
||||
public OpenSaml3AuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { |
||||
this.authnRequestResolver = new OpenSamlAuthenticationRequestResolver(relyingPartyRegistrationResolver); |
||||
} |
||||
|
||||
@Override |
||||
public <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) { |
||||
return this.authnRequestResolver.resolve(request, (registration, authnRequest) -> { |
||||
authnRequest.setIssueInstant(new DateTime(this.clock.millis())); |
||||
this.contextConsumer.accept(new AuthnRequestContext(request, registration, authnRequest)); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest} |
||||
* @param contextConsumer a consumer that accepts an {@link AuthnRequestContext} |
||||
*/ |
||||
public void setAuthnRequestCustomizer(Consumer<AuthnRequestContext> contextConsumer) { |
||||
Assert.notNull(contextConsumer, "contextConsumer cannot be null"); |
||||
this.contextConsumer = contextConsumer; |
||||
} |
||||
|
||||
/** |
||||
* Use this {@link Clock} for generating the issued {@link DateTime} |
||||
* @param clock the {@link Clock} to use |
||||
*/ |
||||
public void setClock(Clock clock) { |
||||
Assert.notNull(clock, "clock must not be null"); |
||||
this.clock = clock; |
||||
} |
||||
|
||||
public static final class AuthnRequestContext { |
||||
|
||||
private final HttpServletRequest request; |
||||
|
||||
private final RelyingPartyRegistration registration; |
||||
|
||||
private final AuthnRequest authnRequest; |
||||
|
||||
public AuthnRequestContext(HttpServletRequest request, RelyingPartyRegistration registration, |
||||
AuthnRequest authnRequest) { |
||||
this.request = request; |
||||
this.registration = registration; |
||||
this.authnRequest = authnRequest; |
||||
} |
||||
|
||||
public HttpServletRequest getRequest() { |
||||
return this.request; |
||||
} |
||||
|
||||
public RelyingPartyRegistration getRelyingPartyRegistration() { |
||||
return this.registration; |
||||
} |
||||
|
||||
public AuthnRequest getAuthnRequest() { |
||||
return this.authnRequest; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,110 @@
@@ -0,0 +1,110 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.web.authentication; |
||||
|
||||
import java.time.Clock; |
||||
import java.time.Instant; |
||||
import java.util.function.Consumer; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest; |
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; |
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; |
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A strategy for resolving a SAML 2.0 Authentication Request from the |
||||
* {@link HttpServletRequest} using OpenSAML. |
||||
* |
||||
* @author Josh Cummings |
||||
* @since 5.7 |
||||
*/ |
||||
public final class OpenSaml4AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver { |
||||
|
||||
private final OpenSamlAuthenticationRequestResolver authnRequestResolver; |
||||
|
||||
private Consumer<AuthnRequestContext> contextConsumer = (parameters) -> { |
||||
}; |
||||
|
||||
private Clock clock = Clock.systemUTC(); |
||||
|
||||
/** |
||||
* Construct a {@link OpenSaml4AuthenticationRequestResolver} |
||||
*/ |
||||
public OpenSaml4AuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { |
||||
this.authnRequestResolver = new OpenSamlAuthenticationRequestResolver(relyingPartyRegistrationResolver); |
||||
} |
||||
|
||||
@Override |
||||
public <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) { |
||||
return this.authnRequestResolver.resolve(request, (registration, authnRequest) -> { |
||||
authnRequest.setIssueInstant(Instant.now(this.clock)); |
||||
this.contextConsumer.accept(new AuthnRequestContext(request, registration, authnRequest)); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link AuthnRequest} |
||||
* @param contextConsumer a consumer that accepts an {@link AuthnRequestContext} |
||||
*/ |
||||
public void setAuthnRequestCustomizer(Consumer<AuthnRequestContext> contextConsumer) { |
||||
Assert.notNull(contextConsumer, "contextConsumer cannot be null"); |
||||
this.contextConsumer = contextConsumer; |
||||
} |
||||
|
||||
/** |
||||
* Use this {@link Clock} for generating the issued {@link Instant} |
||||
* @param clock the {@link Clock} to use |
||||
*/ |
||||
public void setClock(Clock clock) { |
||||
Assert.notNull(clock, "clock must not be null"); |
||||
this.clock = clock; |
||||
} |
||||
|
||||
public static final class AuthnRequestContext { |
||||
|
||||
private final HttpServletRequest request; |
||||
|
||||
private final RelyingPartyRegistration registration; |
||||
|
||||
private final AuthnRequest authnRequest; |
||||
|
||||
public AuthnRequestContext(HttpServletRequest request, RelyingPartyRegistration registration, |
||||
AuthnRequest authnRequest) { |
||||
this.request = request; |
||||
this.registration = registration; |
||||
this.authnRequest = authnRequest; |
||||
} |
||||
|
||||
public HttpServletRequest getRequest() { |
||||
return this.request; |
||||
} |
||||
|
||||
public RelyingPartyRegistration getRelyingPartyRegistration() { |
||||
return this.registration; |
||||
} |
||||
|
||||
public AuthnRequest getAuthnRequest() { |
||||
return this.authnRequest; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,169 @@
@@ -0,0 +1,169 @@
|
||||
/* |
||||
* Copyright 2002-2022 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.web.authentication; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.opensaml.xmlsec.signature.support.SignatureConstants; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.security.saml2.Saml2Exception; |
||||
import org.springframework.security.saml2.core.Saml2X509Credential; |
||||
import org.springframework.security.saml2.core.TestSaml2X509Credentials; |
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; |
||||
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.Saml2MessageBinding; |
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link OpenSamlAuthenticationRequestResolver} |
||||
*/ |
||||
public class OpenSamlAuthenticationRequestResolverTests { |
||||
|
||||
private RelyingPartyRegistration.Builder relyingPartyRegistrationBuilder; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
this.relyingPartyRegistrationBuilder = TestRelyingPartyRegistrations.relyingPartyRegistration(); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveAuthenticationRequestWhenSignedRedirectThenSignsAndRedirects() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setPathInfo("/saml2/authenticate/registration-id"); |
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder.build(); |
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration); |
||||
Saml2RedirectAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> { |
||||
assertThat(authnRequest.getAssertionConsumerServiceURL()) |
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation()); |
||||
assertThat(authnRequest.getProtocolBinding()) |
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn()); |
||||
assertThat(authnRequest.getDestination()) |
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()); |
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId()); |
||||
}); |
||||
assertThat(result.getSamlRequest()).isNotEmpty(); |
||||
assertThat(result.getRelayState()).isNotNull(); |
||||
assertThat(result.getSigAlg()).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); |
||||
assertThat(result.getSignature()).isNotEmpty(); |
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveAuthenticationRequestWhenUnsignedRedirectThenRedirectsAndNoSignature() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setPathInfo("/saml2/authenticate/registration-id"); |
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder |
||||
.assertingPartyDetails((party) -> party.wantAuthnRequestsSigned(false)).build(); |
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration); |
||||
Saml2RedirectAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> { |
||||
assertThat(authnRequest.getAssertionConsumerServiceURL()) |
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation()); |
||||
assertThat(authnRequest.getProtocolBinding()) |
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn()); |
||||
assertThat(authnRequest.getDestination()) |
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()); |
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId()); |
||||
}); |
||||
assertThat(result.getSamlRequest()).isNotEmpty(); |
||||
assertThat(result.getRelayState()).isNotNull(); |
||||
assertThat(result.getSigAlg()).isNull(); |
||||
assertThat(result.getSignature()).isNull(); |
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveAuthenticationRequestWhenSignedThenCredentialIsRequired() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setPathInfo("/saml2/authenticate/registration-id"); |
||||
Saml2X509Credential credential = TestSaml2X509Credentials.relyingPartyVerifyingCredential(); |
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials() |
||||
.assertingPartyDetails((party) -> party.verificationX509Credentials((c) -> c.add(credential))).build(); |
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration); |
||||
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> resolver.resolve(request, null)); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveAuthenticationRequestWhenUnsignedPostThenOnlyPosts() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setPathInfo("/saml2/authenticate/registration-id"); |
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder.assertingPartyDetails( |
||||
(party) -> party.singleSignOnServiceBinding(Saml2MessageBinding.POST).wantAuthnRequestsSigned(false)) |
||||
.build(); |
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration); |
||||
Saml2PostAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> { |
||||
assertThat(authnRequest.getAssertionConsumerServiceURL()) |
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation()); |
||||
assertThat(authnRequest.getProtocolBinding()) |
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn()); |
||||
assertThat(authnRequest.getDestination()) |
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()); |
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId()); |
||||
}); |
||||
assertThat(result.getSamlRequest()).isNotEmpty(); |
||||
assertThat(result.getRelayState()).isNotNull(); |
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST); |
||||
assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()))).doesNotContain("Signature"); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveAuthenticationRequestWhenSignedPostThenSignsAndPosts() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setPathInfo("/saml2/authenticate/registration-id"); |
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder |
||||
.assertingPartyDetails((party) -> party.singleSignOnServiceBinding(Saml2MessageBinding.POST)).build(); |
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration); |
||||
Saml2PostAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> { |
||||
assertThat(authnRequest.getAssertionConsumerServiceURL()) |
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation()); |
||||
assertThat(authnRequest.getProtocolBinding()) |
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn()); |
||||
assertThat(authnRequest.getDestination()) |
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()); |
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId()); |
||||
}); |
||||
assertThat(result.getSamlRequest()).isNotEmpty(); |
||||
assertThat(result.getRelayState()).isNotNull(); |
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST); |
||||
assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()))).contains("Signature"); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveAuthenticationRequestWhenSHA1SignRequestThenSigns() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setPathInfo("/saml2/authenticate/registration-id"); |
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder.assertingPartyDetails( |
||||
(party) -> party.signingAlgorithms((algs) -> algs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1))) |
||||
.build(); |
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration); |
||||
Saml2RedirectAuthenticationRequest result = resolver.resolve(request, null); |
||||
assertThat(result.getSamlRequest()).isNotEmpty(); |
||||
assertThat(result.getRelayState()).isNotNull(); |
||||
assertThat(result.getSigAlg()).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); |
||||
assertThat(result.getSignature()).isNotNull(); |
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); |
||||
} |
||||
|
||||
private OpenSamlAuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistration registration) { |
||||
return new OpenSamlAuthenticationRequestResolver((request, id) -> registration); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue