Add Saml2AuthenticationRequestResolver
Closes gh-10355
This commit is contained in:
+13
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
/**
|
||||
@@ -54,6 +55,17 @@ public class Saml2PostAuthenticationRequest extends AbstractSaml2AuthenticationR
|
||||
return new Builder().authenticationRequestUri(context.getDestination()).relayState(context.getRelayState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link Builder} from a {@link RelyingPartyRegistration} object.
|
||||
* @param registration a relying party registration
|
||||
* @return a modifiable builder object
|
||||
* @since 5.7
|
||||
*/
|
||||
public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) {
|
||||
String location = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
|
||||
return new Builder().authenticationRequestUri(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for a {@link Saml2PostAuthenticationRequest} object.
|
||||
*/
|
||||
|
||||
+14
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
/**
|
||||
@@ -77,6 +78,18 @@ public final class Saml2RedirectAuthenticationRequest extends AbstractSaml2Authe
|
||||
return new Builder().authenticationRequestUri(context.getDestination()).relayState(context.getRelayState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link Saml2PostAuthenticationRequest.Builder} from a
|
||||
* {@link RelyingPartyRegistration} object.
|
||||
* @param registration a relying party registration
|
||||
* @return a modifiable builder object
|
||||
* @since 5.7
|
||||
*/
|
||||
public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) {
|
||||
String location = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
|
||||
return new Builder().authenticationRequestUri(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for a {@link Saml2RedirectAuthenticationRequest} object.
|
||||
*/
|
||||
|
||||
+69
-30
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -42,6 +42,7 @@ import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2A
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
|
||||
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;
|
||||
@@ -78,11 +79,7 @@ import org.springframework.web.util.UriUtils;
|
||||
*/
|
||||
public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter {
|
||||
|
||||
private final Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver;
|
||||
|
||||
private Saml2AuthenticationRequestFactory authenticationRequestFactory;
|
||||
|
||||
private RequestMatcher redirectMatcher = new AntPathRequestMatcher("/saml2/authenticate/{registrationId}");
|
||||
private final Saml2AuthenticationRequestResolver authenticationRequestResolver;
|
||||
|
||||
private Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository = new HttpSessionSaml2AuthenticationRequestRepository();
|
||||
|
||||
@@ -129,11 +126,20 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
||||
public Saml2WebSsoAuthenticationRequestFilter(
|
||||
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver,
|
||||
Saml2AuthenticationRequestFactory authenticationRequestFactory) {
|
||||
this(new FactorySaml2AuthenticationRequestResolver(authenticationRequestContextResolver,
|
||||
authenticationRequestFactory));
|
||||
}
|
||||
|
||||
Assert.notNull(authenticationRequestContextResolver, "authenticationRequestContextResolver cannot be null");
|
||||
Assert.notNull(authenticationRequestFactory, "authenticationRequestFactory cannot be null");
|
||||
this.authenticationRequestContextResolver = authenticationRequestContextResolver;
|
||||
this.authenticationRequestFactory = authenticationRequestFactory;
|
||||
/**
|
||||
* Construct a {@link Saml2WebSsoAuthenticationRequestFilter} with the strategy for
|
||||
* resolving the {@code AuthnRequest}
|
||||
* @param authenticationRequestResolver the strategy for resolving the
|
||||
* {@code AuthnRequest}
|
||||
* @since 5.7
|
||||
*/
|
||||
public Saml2WebSsoAuthenticationRequestFilter(Saml2AuthenticationRequestResolver authenticationRequestResolver) {
|
||||
Assert.notNull(authenticationRequestResolver, "authenticationRequestResolver cannot be null");
|
||||
this.authenticationRequestResolver = authenticationRequestResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,16 +152,23 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
||||
@Deprecated
|
||||
public void setAuthenticationRequestFactory(Saml2AuthenticationRequestFactory authenticationRequestFactory) {
|
||||
Assert.notNull(authenticationRequestFactory, "authenticationRequestFactory cannot be null");
|
||||
this.authenticationRequestFactory = authenticationRequestFactory;
|
||||
Assert.isInstanceOf(FactorySaml2AuthenticationRequestResolver.class, this.authenticationRequestResolver,
|
||||
"You cannot supply both a Saml2AuthenticationRequestResolver and a Saml2AuthenticationRequestFactory");
|
||||
((FactorySaml2AuthenticationRequestResolver) this.authenticationRequestResolver).authenticationRequestFactory = authenticationRequestFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link RequestMatcher} that activates this filter for a given request
|
||||
* @param redirectMatcher the {@link RequestMatcher} to use
|
||||
* @deprecated Configure the request matcher in an implementation of
|
||||
* {@link Saml2AuthenticationRequestResolver} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRedirectMatcher(RequestMatcher redirectMatcher) {
|
||||
Assert.notNull(redirectMatcher, "redirectMatcher cannot be null");
|
||||
this.redirectMatcher = redirectMatcher;
|
||||
Assert.isInstanceOf(FactorySaml2AuthenticationRequestResolver.class, this.authenticationRequestResolver,
|
||||
"You cannot supply a Saml2AuthenticationRequestResolver and a redirect matcher");
|
||||
((FactorySaml2AuthenticationRequestResolver) this.authenticationRequestResolver).redirectMatcher = redirectMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,30 +187,21 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
MatchResult matcher = this.redirectMatcher.matcher(request);
|
||||
if (!matcher.isMatch()) {
|
||||
AbstractSaml2AuthenticationRequest authenticationRequest = this.authenticationRequestResolver.resolve(request);
|
||||
if (authenticationRequest == null) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
Saml2AuthenticationRequestContext context = this.authenticationRequestContextResolver.resolve(request);
|
||||
if (context == null) {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
RelyingPartyRegistration relyingParty = context.getRelyingPartyRegistration();
|
||||
if (relyingParty.getAssertingPartyDetails().getSingleSignOnServiceBinding() == Saml2MessageBinding.REDIRECT) {
|
||||
sendRedirect(request, response, context);
|
||||
if (authenticationRequest instanceof Saml2RedirectAuthenticationRequest) {
|
||||
sendRedirect(request, response, (Saml2RedirectAuthenticationRequest) authenticationRequest);
|
||||
}
|
||||
else {
|
||||
sendPost(request, response, context);
|
||||
sendPost(request, response, (Saml2PostAuthenticationRequest) authenticationRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendRedirect(HttpServletRequest request, HttpServletResponse response,
|
||||
Saml2AuthenticationRequestContext context) throws IOException {
|
||||
Saml2RedirectAuthenticationRequest authenticationRequest = this.authenticationRequestFactory
|
||||
.createRedirectAuthenticationRequest(context);
|
||||
Saml2RedirectAuthenticationRequest authenticationRequest) throws IOException {
|
||||
this.authenticationRequestRepository.saveAuthenticationRequest(authenticationRequest, request, response);
|
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
||||
.fromUriString(authenticationRequest.getAuthenticationRequestUri());
|
||||
@@ -218,9 +222,7 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
||||
}
|
||||
|
||||
private void sendPost(HttpServletRequest request, HttpServletResponse response,
|
||||
Saml2AuthenticationRequestContext context) throws IOException {
|
||||
Saml2PostAuthenticationRequest authenticationRequest = this.authenticationRequestFactory
|
||||
.createPostAuthenticationRequest(context);
|
||||
Saml2PostAuthenticationRequest authenticationRequest) throws IOException {
|
||||
this.authenticationRequestRepository.saveAuthenticationRequest(authenticationRequest, request, response);
|
||||
String html = createSamlPostRequestFormData(authenticationRequest);
|
||||
response.setContentType(MediaType.TEXT_HTML_VALUE);
|
||||
@@ -269,4 +271,41 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
private static class FactorySaml2AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver {
|
||||
|
||||
private final Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver;
|
||||
|
||||
private RequestMatcher redirectMatcher = new AntPathRequestMatcher("/saml2/authenticate/{registrationId}");
|
||||
|
||||
private Saml2AuthenticationRequestFactory authenticationRequestFactory;
|
||||
|
||||
FactorySaml2AuthenticationRequestResolver(
|
||||
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver,
|
||||
Saml2AuthenticationRequestFactory authenticationRequestFactory) {
|
||||
Assert.notNull(authenticationRequestContextResolver, "authenticationRequestContextResolver cannot be null");
|
||||
Assert.notNull(authenticationRequestFactory, "authenticationRequestFactory cannot be null");
|
||||
this.authenticationRequestContextResolver = authenticationRequestContextResolver;
|
||||
this.authenticationRequestFactory = authenticationRequestFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractSaml2AuthenticationRequest resolve(HttpServletRequest request) {
|
||||
MatchResult matcher = this.redirectMatcher.matcher(request);
|
||||
if (!matcher.isMatch()) {
|
||||
return null;
|
||||
}
|
||||
Saml2AuthenticationRequestContext context = this.authenticationRequestContextResolver.resolve(request);
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
Saml2MessageBinding binding = context.getRelyingPartyRegistration().getAssertingPartyDetails()
|
||||
.getSingleSignOnServiceBinding();
|
||||
if (binding == Saml2MessageBinding.REDIRECT) {
|
||||
return this.authenticationRequestFactory.createRedirectAuthenticationRequest(context);
|
||||
}
|
||||
return this.authenticationRequestFactory.createPostAuthenticationRequest(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+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);
|
||||
|
||||
}
|
||||
+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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+31
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* 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.
|
||||
@@ -20,6 +20,9 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -43,6 +46,7 @@ import org.springframework.security.saml2.provider.service.web.DefaultSaml2Authe
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
@@ -69,6 +73,9 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
|
||||
|
||||
private Saml2AuthenticationRequestContextResolver resolver = mock(Saml2AuthenticationRequestContextResolver.class);
|
||||
|
||||
private Saml2AuthenticationRequestResolver authenticationRequestResolver = mock(
|
||||
Saml2AuthenticationRequestResolver.class);
|
||||
|
||||
private Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository = mock(
|
||||
Saml2AuthenticationRequestRepository.class);
|
||||
|
||||
@@ -86,7 +93,12 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.request.setPathInfo("/saml2/authenticate/registration-id");
|
||||
this.filterChain = new MockFilterChain();
|
||||
this.filterChain = new MockFilterChain() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response) {
|
||||
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
};
|
||||
this.rpBuilder = RelyingPartyRegistration.withRegistrationId("registration-id")
|
||||
.providerDetails((c) -> c.entityId("idp-entity-id")).providerDetails((c) -> c.webSsoUrl(IDP_SSO_URL))
|
||||
.assertionConsumerServiceUrlTemplate("template")
|
||||
@@ -114,6 +126,12 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
|
||||
.authenticationRequestUri(IDP_SSO_URL);
|
||||
}
|
||||
|
||||
private static Saml2RedirectAuthenticationRequest.Builder redirectAuthenticationRequest(
|
||||
RelyingPartyRegistration registration) {
|
||||
return Saml2RedirectAuthenticationRequest.withRelyingPartyRegistration(registration).samlRequest("request")
|
||||
.authenticationRequestUri(IDP_SSO_URL);
|
||||
}
|
||||
|
||||
private static Saml2PostAuthenticationRequest.Builder postAuthenticationRequest(
|
||||
Saml2AuthenticationRequestContext context) {
|
||||
return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context).samlRequest("request")
|
||||
@@ -287,4 +305,15 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
|
||||
verify(this.repository).findByRegistrationId("registration-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomAuthenticationRequestResolverThenUses() throws Exception {
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
|
||||
Saml2RedirectAuthenticationRequest authenticationRequest = redirectAuthenticationRequest(registration).build();
|
||||
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(
|
||||
this.authenticationRequestResolver);
|
||||
given(this.authenticationRequestResolver.resolve(any())).willReturn(authenticationRequest);
|
||||
filter.doFilterInternal(this.request, this.response, this.filterChain);
|
||||
verify(this.authenticationRequestResolver).resolve(any());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user