diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java index 26bfd1a8489..5ecdffacd84 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java @@ -35,11 +35,16 @@ import org.opensaml.core.xml.config.XMLObjectProviderRegistry; import org.opensaml.core.xml.io.MarshallingException; import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration; import org.opensaml.saml.saml2.core.AuthnRequest; import org.opensaml.saml.saml2.core.Issuer; +import org.opensaml.saml.saml2.core.RequestedAuthnContext; +import org.opensaml.saml.saml2.core.impl.AuthnContextClassRefBuilder; 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.RequestedAuthnContextBuilder; import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; import org.opensaml.security.SecurityException; import org.opensaml.security.credential.BasicCredential; @@ -84,6 +89,8 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication private IssuerBuilder issuerBuilder; + private RequestedAuthnContextBuilder reqAuthnContextBuilder; + private Converter protocolBindingResolver = (context) -> { if (context == null) { return SAMLConstants.SAML2_POST_BINDING_URI; @@ -103,13 +110,16 @@ public OpenSamlAuthenticationRequestFactory() { this.authnRequestBuilder = (AuthnRequestBuilder) registry.getBuilderFactory() .getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME); this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME); + this.reqAuthnContextBuilder = (RequestedAuthnContextBuilder) registry.getBuilderFactory() + .getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME); } @Override @Deprecated public String createAuthenticationRequest(Saml2AuthenticationRequest request) { AuthnRequest authnRequest = createAuthnRequest(request.getIssuer(), request.getDestination(), - request.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(null)); + request.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(null), null, false, + false); for (org.springframework.security.saml2.credentials.Saml2X509Credential credential : request.getCredentials()) { if (credential.isSigningCredential()) { X509Certificate certificate = credential.getCertificate(); @@ -160,17 +170,29 @@ public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest( private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext context) { return createAuthnRequest(context.getIssuer(), context.getDestination(), - context.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(context)); + context.getAssertionConsumerServiceUrl(), this.protocolBindingResolver.convert(context), + context.getRequestAuthnContextRefs(), context.getIsPassive(), context.getForceAuthn()); } private AuthnRequest createAuthnRequest(String issuer, String destination, String assertionConsumerServiceUrl, - String protocolBinding) { + String protocolBinding, List requestAuthnContextRefs, Boolean isPassive, Boolean forceAuth) { AuthnRequest auth = this.authnRequestBuilder.buildObject(); auth.setID("ARQ" + UUID.randomUUID().toString().substring(1)); auth.setIssueInstant(new DateTime(this.clock.millis())); - auth.setForceAuthn(Boolean.FALSE); - auth.setIsPassive(Boolean.FALSE); auth.setProtocolBinding(protocolBinding); + auth.setIsPassive(isPassive); + auth.setForceAuthn(forceAuth); + if (requestAuthnContextRefs != null) { + RequestedAuthnContext requestAuthnContext = this.reqAuthnContextBuilder.buildObject(); + requestAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT); + requestAuthnContextRefs.forEach((r) -> { + AuthnContextClassRef authnContextClassRef = new AuthnContextClassRefBuilder().buildObject(); + authnContextClassRef.setAuthnContextClassRef(r); + requestAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef); + }); + auth.setRequestedAuthnContext(requestAuthnContext); + } + Issuer iss = this.issuerBuilder.buildObject(); iss.setValue(issuer); auth.setIssuer(iss); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java index a8bf6aeb51f..b90ddfac564 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java @@ -16,6 +16,10 @@ package org.springframework.security.saml2.provider.service.authentication; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.util.Assert; @@ -39,6 +43,12 @@ public class Saml2AuthenticationRequestContext { private final String relayState; + private final List requestAuthnContextRefs; + + private final Boolean isPassive; + + private final Boolean forceAuthn; + protected Saml2AuthenticationRequestContext(RelyingPartyRegistration relyingPartyRegistration, String issuer, String assertionConsumerServiceUrl, String relayState) { Assert.hasText(issuer, "issuer cannot be null or empty"); @@ -48,6 +58,23 @@ protected Saml2AuthenticationRequestContext(RelyingPartyRegistration relyingPart this.relyingPartyRegistration = relyingPartyRegistration; this.assertionConsumerServiceUrl = assertionConsumerServiceUrl; this.relayState = relayState; + this.isPassive = false; + this.forceAuthn = false; + this.requestAuthnContextRefs = new ArrayList<>(); + } + + private Saml2AuthenticationRequestContext(Builder builder) { + + Assert.hasText(builder.issuer, "issuer cannot be null or empty"); + Assert.notNull(builder.relyingPartyRegistration, "relyingPartyRegistration cannot be null"); + Assert.hasText(builder.assertionConsumerServiceUrl, "spAssertionConsumerServiceUrl cannot be null or empty"); + this.issuer = builder.issuer; + this.relyingPartyRegistration = builder.relyingPartyRegistration; + this.assertionConsumerServiceUrl = builder.assertionConsumerServiceUrl; + this.relayState = builder.relayState; + this.isPassive = builder.isPassive; + this.forceAuthn = builder.forceAuthn; + this.requestAuthnContextRefs = builder.requestAuthnContextRefs; } /** @@ -70,9 +97,10 @@ public String getIssuer() { } /** - * 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. + * Returns the desired {@code AssertionConsumerServiceUrl} that this relying party + * 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() { @@ -87,6 +115,41 @@ public String getRelayState() { return this.relayState; } + /** + * Returns the desired ({@code AuthnContextClassRef} that this relying party wishes to + * set in the request.This property populates the + * {@code AuthNRequest.RequestedAuthnContext.AuthnContextClassRef} XML attribute. + * @return the AuthnContextClassRef value + * @since 5.5 + */ + public List getRequestAuthnContextRefs() { + return this.requestAuthnContextRefs; + } + + /** + * Returns the isPassive value, if present in the parameters + * @return the isPassive value, default is set to false + * @since 5.5 + * @see OpenSAML 3 + * isPassive + */ + public Boolean getIsPassive() { + return this.isPassive; + } + + /** + * Returns the ForceAuthn value, if present in the parameters + * @return this forceAuthn value, default is set to false + * @since 5.5 + * @see OpenSAML 3 + * ForceAuthn + */ + public Boolean getForceAuthn() { + return this.forceAuthn; + } + /** * Returns the {@code Destination}, the WEB Single Sign On URI, for this * authentication request. This property can also populate the @@ -116,6 +179,12 @@ public static final class Builder { private String relayState; + private List requestAuthnContextRefs = new ArrayList<>(); + + private Boolean isPassive; + + private Boolean forceAuthn; + private RelyingPartyRegistration relyingPartyRegistration; private Builder() { @@ -164,14 +233,48 @@ public Builder relayState(String relayState) { return this; } + /** + * Sets this {@link Consumer} AuthnContextClassRef parameter that will accompany + * this AuthNRequest. + * @param requestAuthnContextRefsConsumer a {@link Consumer} of the list of + * AuthnContextClassRef + * @return this object + * @since 5.5 + */ + public Builder requestAuthnContextRefs(Consumer> requestAuthnContextRefsConsumer) { + requestAuthnContextRefsConsumer.accept(this.requestAuthnContextRefs); + return this; + } + + /** + * Sets the {@code IsPassive} parameter that will accompany this AuthNRequest + * @param isPassive the isPassive value. if null or empty, defaults to false. + * @return this object + * @since 5.5 + */ + public Builder isPassive(Boolean isPassive) { + this.isPassive = isPassive; + return this; + } + + /** + * Sets the {@code ForceAuth} parameter that will accompany this AuthNRequest + * @param forceAuth the foceAuth value. if null or empty, defaults to false. + * @return this object + * @since 5.5 + */ + public Builder forceAuthn(Boolean forceAuthn) { + this.forceAuthn = forceAuthn; + return this; + } + /** * Creates a {@link Saml2AuthenticationRequestContext} object. * @return the Saml2AuthenticationRequest object * @throws IllegalArgumentException if a required property is not set */ public Saml2AuthenticationRequestContext build() { - return new Saml2AuthenticationRequestContext(this.relyingPartyRegistration, this.issuer, - this.assertionConsumerServiceUrl, this.relayState); + return new Saml2AuthenticationRequestContext(this); } }