Skip to content

Commit bf5b334

Browse files
committed
Use OpenSAML API for web.authentication
Issue gh-11658
1 parent 51fc056 commit bf5b334

File tree

10 files changed

+1016
-716
lines changed

10 files changed

+1016
-716
lines changed

saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ sourceSets.configureEach { set ->
3131
filter { line -> line.replaceAll(".saml2.internal", ".saml2.provider.service.web.authentication.logout") }
3232
with from
3333
}
34+
35+
copy {
36+
into "$projectDir/src/$set.name/java/org/springframework/security/saml2/provider/service/web/authentication"
37+
filter { line -> line.replaceAll(".saml2.internal", ".saml2.provider.service.web.authentication") }
38+
with from
39+
}
40+
3441
}
3542

3643
dependencies {
Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,19 +16,19 @@
1616

1717
package org.springframework.security.saml2.provider.service.web.authentication;
1818

19-
import java.nio.charset.StandardCharsets;
19+
import java.time.Clock;
20+
import java.time.Instant;
2021
import java.util.ArrayList;
22+
import java.util.HashMap;
2123
import java.util.List;
2224
import java.util.Map;
2325
import java.util.UUID;
24-
import java.util.function.BiConsumer;
26+
import java.util.function.Consumer;
2527

2628
import jakarta.servlet.http.HttpServletRequest;
27-
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
2829
import org.opensaml.core.config.ConfigurationService;
2930
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
3031
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
31-
import org.opensaml.core.xml.io.MarshallingException;
3232
import org.opensaml.saml.saml2.core.AuthnRequest;
3333
import org.opensaml.saml.saml2.core.Issuer;
3434
import org.opensaml.saml.saml2.core.NameID;
@@ -38,10 +38,8 @@
3838
import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
3939
import org.opensaml.saml.saml2.core.impl.NameIDBuilder;
4040
import org.opensaml.saml.saml2.core.impl.NameIDPolicyBuilder;
41-
import org.w3c.dom.Element;
4241

4342
import org.springframework.core.convert.converter.Converter;
44-
import org.springframework.security.saml2.Saml2Exception;
4543
import org.springframework.security.saml2.core.OpenSamlInitializationService;
4644
import org.springframework.security.saml2.core.Saml2ParameterNames;
4745
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
@@ -63,11 +61,14 @@
6361
* For internal use only. Intended for consolidating common behavior related to minting a
6462
* SAML 2.0 Authn Request.
6563
*/
66-
class OpenSamlAuthenticationRequestResolver {
64+
class BaseOpenSamlAuthenticationRequestResolver implements Saml2AuthenticationRequestResolver {
6765

6866
static {
6967
OpenSamlInitializationService.initialize();
7068
}
69+
70+
private final OpenSamlOperations saml;
71+
7172
private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver;
7273

7374
private final AuthnRequestBuilder authnRequestBuilder;
@@ -84,15 +85,22 @@ class OpenSamlAuthenticationRequestResolver {
8485
new AntPathRequestMatcher(Saml2AuthenticationRequestResolver.DEFAULT_AUTHENTICATION_REQUEST_URI),
8586
new AntPathQueryRequestMatcher("/saml2/authenticate", "registrationId={registrationId}"));
8687

88+
private Clock clock = Clock.systemUTC();
89+
8790
private Converter<HttpServletRequest, String> relayStateResolver = (request) -> UUID.randomUUID().toString();
8891

92+
private Consumer<AuthnRequestParameters> parametersConsumer = (parameters) -> {
93+
};
94+
8995
/**
90-
* Construct a {@link OpenSamlAuthenticationRequestResolver} using the provided
96+
* Construct a {@link BaseOpenSamlAuthenticationRequestResolver} using the provided
9197
* parameters
9298
* @param relyingPartyRegistrationResolver a strategy for resolving the
9399
* {@link RelyingPartyRegistration} from the {@link HttpServletRequest}
94100
*/
95-
OpenSamlAuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
101+
BaseOpenSamlAuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver,
102+
OpenSamlOperations saml) {
103+
this.saml = saml;
96104
Assert.notNull(relyingPartyRegistrationResolver, "relyingPartyRegistrationResolver cannot be null");
97105
this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver;
98106
XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
@@ -111,6 +119,10 @@ class OpenSamlAuthenticationRequestResolver {
111119
Assert.notNull(this.nameIdPolicyBuilder, "nameIdPolicyBuilder must be configured in OpenSAML");
112120
}
113121

122+
void setClock(Clock clock) {
123+
this.clock = clock;
124+
}
125+
114126
void setRelayStateResolver(Converter<HttpServletRequest, String> relayStateResolver) {
115127
this.relayStateResolver = relayStateResolver;
116128
}
@@ -119,13 +131,12 @@ void setRequestMatcher(RequestMatcher requestMatcher) {
119131
this.requestMatcher = requestMatcher;
120132
}
121133

122-
<T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) {
123-
return resolve(request, (registration, logoutRequest) -> {
124-
});
134+
void setParametersConsumer(Consumer<AuthnRequestParameters> parametersConsumer) {
135+
this.parametersConsumer = parametersConsumer;
125136
}
126137

127-
<T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request,
128-
BiConsumer<RelyingPartyRegistration, AuthnRequest> authnRequestConsumer) {
138+
@Override
139+
public <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) {
129140
RequestMatcher.MatchResult result = this.requestMatcher.matcher(request);
130141
if (!result.isMatch()) {
131142
return null;
@@ -153,7 +164,8 @@ <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest requ
153164
nameIdPolicy.setFormat(registration.getNameIdFormat());
154165
authnRequest.setNameIDPolicy(nameIdPolicy);
155166
}
156-
authnRequestConsumer.accept(registration, authnRequest);
167+
authnRequest.setIssueInstant(Instant.now(this.clock));
168+
this.parametersConsumer.accept(new AuthnRequestParameters(request, registration, authnRequest));
157169
if (authnRequest.getID() == null) {
158170
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
159171
}
@@ -162,10 +174,12 @@ <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest requ
162174
if (binding == Saml2MessageBinding.POST) {
163175
if (registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()
164176
|| registration.isAuthnRequestsSigned()) {
165-
OpenSamlSigningUtils.sign(authnRequest, registration);
177+
this.saml.withSigningKeys(registration.getSigningX509Credentials())
178+
.algorithms(registration.getAssertingPartyMetadata().getSigningAlgorithms())
179+
.sign(authnRequest);
166180
}
167181
String xml = serialize(authnRequest);
168-
String encoded = Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8));
182+
String encoded = Saml2Utils.withDecoded(xml).encode();
169183
return (T) Saml2PostAuthenticationRequest.withRelyingPartyRegistration(registration)
170184
.samlRequest(encoded)
171185
.relayState(relayState)
@@ -174,35 +188,31 @@ <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest requ
174188
}
175189
else {
176190
String xml = serialize(authnRequest);
177-
String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml));
191+
String deflatedAndEncoded = Saml2Utils.withDecoded(xml).deflate(true).encode();
178192
Saml2RedirectAuthenticationRequest.Builder builder = Saml2RedirectAuthenticationRequest
179193
.withRelyingPartyRegistration(registration)
180194
.samlRequest(deflatedAndEncoded)
181195
.relayState(relayState)
182196
.id(authnRequest.getID());
183197
if (registration.getAssertingPartyMetadata().getWantAuthnRequestsSigned()
184198
|| registration.isAuthnRequestsSigned()) {
185-
OpenSamlSigningUtils.QueryParametersPartial parametersPartial = OpenSamlSigningUtils.sign(registration)
186-
.param(Saml2ParameterNames.SAML_REQUEST, deflatedAndEncoded);
199+
Map<String, String> signingParameters = new HashMap<>();
200+
signingParameters.put(Saml2ParameterNames.SAML_REQUEST, deflatedAndEncoded);
187201
if (relayState != null) {
188-
parametersPartial = parametersPartial.param(Saml2ParameterNames.RELAY_STATE, relayState);
202+
signingParameters.put(Saml2ParameterNames.RELAY_STATE, relayState);
189203
}
190-
Map<String, String> parameters = parametersPartial.parameters();
191-
builder.sigAlg(parameters.get(Saml2ParameterNames.SIG_ALG))
192-
.signature(parameters.get(Saml2ParameterNames.SIGNATURE));
204+
Map<String, String> query = this.saml.withSigningKeys(registration.getSigningX509Credentials())
205+
.algorithms(registration.getAssertingPartyMetadata().getSigningAlgorithms())
206+
.sign(signingParameters);
207+
builder.sigAlg(query.get(Saml2ParameterNames.SIG_ALG))
208+
.signature(query.get(Saml2ParameterNames.SIGNATURE));
193209
}
194210
return (T) builder.build();
195211
}
196212
}
197213

198214
private String serialize(AuthnRequest authnRequest) {
199-
try {
200-
Element element = this.marshaller.marshall(authnRequest);
201-
return SerializeSupport.nodeToString(element);
202-
}
203-
catch (MarshallingException ex) {
204-
throw new Saml2Exception(ex);
205-
}
215+
return this.saml.serialize(authnRequest).serialize();
206216
}
207217

208218
private static final class AntPathQueryRequestMatcher implements RequestMatcher {
@@ -236,4 +246,33 @@ public MatchResult matcher(HttpServletRequest request) {
236246

237247
}
238248

249+
static final class AuthnRequestParameters {
250+
251+
private final HttpServletRequest request;
252+
253+
private final RelyingPartyRegistration registration;
254+
255+
private final AuthnRequest authnRequest;
256+
257+
AuthnRequestParameters(HttpServletRequest request, RelyingPartyRegistration registration,
258+
AuthnRequest authnRequest) {
259+
this.request = request;
260+
this.registration = registration;
261+
this.authnRequest = authnRequest;
262+
}
263+
264+
HttpServletRequest getRequest() {
265+
return this.request;
266+
}
267+
268+
RelyingPartyRegistration getRelyingPartyRegistration() {
269+
return this.registration;
270+
}
271+
272+
AuthnRequest getAuthnRequest() {
273+
return this.authnRequest;
274+
}
275+
276+
}
277+
239278
}

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolver.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
*/
4141
public final class OpenSaml4AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver {
4242

43-
private final OpenSamlAuthenticationRequestResolver authnRequestResolver;
43+
private final BaseOpenSamlAuthenticationRequestResolver delegate;
4444

4545
private Consumer<AuthnRequestContext> contextConsumer = (parameters) -> {
4646
};
@@ -53,27 +53,25 @@ public final class OpenSaml4AuthenticationRequestResolver implements Saml2Authen
5353
* @since 6.1
5454
*/
5555
public OpenSaml4AuthenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
56-
this.authnRequestResolver = new OpenSamlAuthenticationRequestResolver((request, id) -> {
56+
this.delegate = new BaseOpenSamlAuthenticationRequestResolver((request, id) -> {
5757
if (id == null) {
5858
return null;
5959
}
6060
return registrations.findByRegistrationId(id);
61-
});
61+
}, new OpenSaml4Template());
6262
}
6363

6464
/**
6565
* Construct a {@link OpenSaml4AuthenticationRequestResolver}
6666
*/
6767
public OpenSaml4AuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
68-
this.authnRequestResolver = new OpenSamlAuthenticationRequestResolver(relyingPartyRegistrationResolver);
68+
this.delegate = new BaseOpenSamlAuthenticationRequestResolver(relyingPartyRegistrationResolver,
69+
new OpenSaml4Template());
6970
}
7071

7172
@Override
7273
public <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) {
73-
return this.authnRequestResolver.resolve(request, (registration, authnRequest) -> {
74-
authnRequest.setIssueInstant(Instant.now(this.clock));
75-
this.contextConsumer.accept(new AuthnRequestContext(request, registration, authnRequest));
76-
});
74+
return this.delegate.resolve(request);
7775
}
7876

7977
/**
@@ -82,20 +80,22 @@ public <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletReque
8280
*/
8381
public void setAuthnRequestCustomizer(Consumer<AuthnRequestContext> contextConsumer) {
8482
Assert.notNull(contextConsumer, "contextConsumer cannot be null");
85-
this.contextConsumer = contextConsumer;
83+
this.delegate.setParametersConsumer(
84+
(parameters) -> contextConsumer.accept(new AuthnRequestContext(parameters.getRequest(),
85+
parameters.getRelyingPartyRegistration(), parameters.getAuthnRequest())));
8686
}
8787

8888
/**
8989
* Set the {@link RequestMatcher} to use for setting the
90-
* {@link OpenSamlAuthenticationRequestResolver#setRequestMatcher(RequestMatcher)}
90+
* {@link BaseOpenSamlAuthenticationRequestResolver#setRequestMatcher(RequestMatcher)}
9191
* (RequestMatcher)}
9292
* @param requestMatcher the {@link RequestMatcher} to identify authentication
9393
* requests.
9494
* @since 5.8
9595
*/
9696
public void setRequestMatcher(RequestMatcher requestMatcher) {
9797
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
98-
this.authnRequestResolver.setRequestMatcher(requestMatcher);
98+
this.delegate.setRequestMatcher(requestMatcher);
9999
}
100100

101101
/**
@@ -114,7 +114,7 @@ public void setClock(Clock clock) {
114114
*/
115115
public void setRelayStateResolver(Converter<HttpServletRequest, String> relayStateResolver) {
116116
Assert.notNull(relayStateResolver, "relayStateResolver cannot be null");
117-
this.authnRequestResolver.setRelayStateResolver(relayStateResolver);
117+
this.delegate.setRelayStateResolver(relayStateResolver);
118118
}
119119

120120
public static final class AuthnRequestContext {

0 commit comments

Comments
 (0)