Skip to content

Commit 51fc056

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

21 files changed

+1613
-804
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
4141
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver;
4242
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver;
43-
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutRequestValidatorParametersResolver;
43+
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestValidatorParametersResolver;
4444
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
4545
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository;
4646
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver;
@@ -251,7 +251,7 @@ private Saml2LogoutRequestFilter createLogoutRequestProcessingFilter(
251251
LogoutHandler[] logoutHandlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
252252
Saml2LogoutResponseResolver logoutResponseResolver = createSaml2LogoutResponseResolver(registrations);
253253
RequestMatcher requestMatcher = createLogoutRequestMatcher();
254-
OpenSamlLogoutRequestValidatorParametersResolver parameters = new OpenSamlLogoutRequestValidatorParametersResolver(
254+
OpenSaml4LogoutRequestValidatorParametersResolver parameters = new OpenSaml4LogoutRequestValidatorParametersResolver(
255255
registrations);
256256
parameters.setRequestMatcher(requestMatcher);
257257
Saml2LogoutRequestFilter filter = new Saml2LogoutRequestFilter(parameters,

docs/modules/ROOT/pages/servlet/saml2/logout.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ Kotlin::
605605
----
606606
@Component
607607
open class MyOpenSamlLogoutResponseValidator: Saml2LogoutResponseValidator {
608-
private val delegate = OpenSamlLogoutResponseValidator()
608+
private val delegate = OpenSaml4LogoutResponseValidator()
609609
610610
@Override
611611
fun logout(parameters: Saml2LogoutResponseValidatorParameters): Saml2LogoutResponseValidator {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ sourceSets.configureEach { set ->
2525
filter { line -> line.replaceAll(".saml2.internal", ".saml2.provider.service.metadata") }
2626
with from
2727
}
28+
29+
copy {
30+
into "$projectDir/src/$set.name/java/org/springframework/security/saml2/provider/service/web/authentication/logout"
31+
filter { line -> line.replaceAll(".saml2.internal", ".saml2.provider.service.web.authentication.logout") }
32+
with from
33+
}
2834
}
2935

3036
dependencies {
Lines changed: 78 additions & 31 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,17 +16,18 @@
1616

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

19-
import java.nio.charset.StandardCharsets;
19+
import java.time.Clock;
20+
import java.time.Instant;
21+
import java.util.HashMap;
22+
import java.util.Map;
2023
import java.util.UUID;
21-
import java.util.function.BiConsumer;
24+
import java.util.function.Consumer;
2225

2326
import jakarta.servlet.http.HttpServletRequest;
24-
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
2527
import org.apache.commons.logging.Log;
2628
import org.apache.commons.logging.LogFactory;
2729
import org.opensaml.core.config.ConfigurationService;
2830
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
29-
import org.opensaml.core.xml.io.MarshallingException;
3031
import org.opensaml.saml.saml2.core.Issuer;
3132
import org.opensaml.saml.saml2.core.LogoutRequest;
3233
import org.opensaml.saml.saml2.core.NameID;
@@ -36,11 +37,9 @@
3637
import org.opensaml.saml.saml2.core.impl.LogoutRequestMarshaller;
3738
import org.opensaml.saml.saml2.core.impl.NameIDBuilder;
3839
import org.opensaml.saml.saml2.core.impl.SessionIndexBuilder;
39-
import org.w3c.dom.Element;
4040

4141
import org.springframework.core.convert.converter.Converter;
4242
import org.springframework.security.core.Authentication;
43-
import org.springframework.security.saml2.Saml2Exception;
4443
import org.springframework.security.saml2.core.OpenSamlInitializationService;
4544
import org.springframework.security.saml2.core.Saml2ParameterNames;
4645
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
@@ -50,21 +49,24 @@
5049
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers;
5150
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers.UriResolver;
5251
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
53-
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlSigningUtils.QueryParametersPartial;
5452
import org.springframework.util.Assert;
5553

5654
/**
5755
* For internal use only. Intended for consolidating common behavior related to minting a
5856
* SAML 2.0 Logout Request.
5957
*/
60-
final class OpenSamlLogoutRequestResolver {
58+
final class BaseOpenSamlLogoutRequestResolver implements Saml2LogoutRequestResolver {
6159

6260
static {
6361
OpenSamlInitializationService.initialize();
6462
}
6563

6664
private final Log logger = LogFactory.getLog(getClass());
6765

66+
private final OpenSamlOperations saml;
67+
68+
private Clock clock = Clock.systemUTC();
69+
6870
private final LogoutRequestMarshaller marshaller;
6971

7072
private final IssuerBuilder issuerBuilder;
@@ -79,11 +81,16 @@ final class OpenSamlLogoutRequestResolver {
7981

8082
private Converter<HttpServletRequest, String> relayStateResolver = (request) -> UUID.randomUUID().toString();
8183

84+
private Consumer<LogoutRequestParameters> parametersConsumer = (parameters) -> {
85+
};
86+
8287
/**
83-
* Construct a {@link OpenSamlLogoutRequestResolver}
88+
* Construct a {@link BaseOpenSamlLogoutRequestResolver}
8489
*/
85-
OpenSamlLogoutRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
90+
BaseOpenSamlLogoutRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver,
91+
OpenSamlOperations saml) {
8692
this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver;
93+
this.saml = saml;
8794
XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
8895
this.marshaller = (LogoutRequestMarshaller) registry.getMarshallerFactory()
8996
.getMarshaller(LogoutRequest.DEFAULT_ELEMENT_NAME);
@@ -100,10 +107,18 @@ final class OpenSamlLogoutRequestResolver {
100107
Assert.notNull(this.sessionIndexBuilder, "sessionIndexBuilder must be configured in OpenSAML");
101108
}
102109

110+
void setClock(Clock clock) {
111+
this.clock = clock;
112+
}
113+
103114
void setRelayStateResolver(Converter<HttpServletRequest, String> relayStateResolver) {
104115
this.relayStateResolver = relayStateResolver;
105116
}
106117

118+
void setParametersConsumer(Consumer<LogoutRequestParameters> parametersConsumer) {
119+
this.parametersConsumer = parametersConsumer;
120+
}
121+
107122
/**
108123
* Prepare to create, sign, and serialize a SAML 2.0 Logout Request.
109124
*
@@ -114,13 +129,8 @@ void setRelayStateResolver(Converter<HttpServletRequest, String> relayStateResol
114129
* @param authentication the current user
115130
* @return a signed and serialized SAML 2.0 Logout Request
116131
*/
117-
Saml2LogoutRequest resolve(HttpServletRequest request, Authentication authentication) {
118-
return resolve(request, authentication, (registration, logoutRequest) -> {
119-
});
120-
}
121-
122-
Saml2LogoutRequest resolve(HttpServletRequest request, Authentication authentication,
123-
BiConsumer<RelyingPartyRegistration, LogoutRequest> logoutRequestConsumer) {
132+
@Override
133+
public Saml2LogoutRequest resolve(HttpServletRequest request, Authentication authentication) {
124134
String registrationId = getRegistrationId(authentication);
125135
RelyingPartyRegistration registration = this.relyingPartyRegistrationResolver.resolve(request, registrationId);
126136
if (registration == null) {
@@ -147,26 +157,33 @@ Saml2LogoutRequest resolve(HttpServletRequest request, Authentication authentica
147157
logoutRequest.getSessionIndexes().add(sessionIndex);
148158
}
149159
}
150-
logoutRequestConsumer.accept(registration, logoutRequest);
160+
logoutRequest.setIssueInstant(Instant.now(this.clock));
161+
this.parametersConsumer
162+
.accept(new LogoutRequestParameters(request, registration, authentication, logoutRequest));
151163
if (logoutRequest.getID() == null) {
152164
logoutRequest.setID("LR" + UUID.randomUUID());
153165
}
154166
String relayState = this.relayStateResolver.convert(request);
155167
Saml2LogoutRequest.Builder result = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
156168
.id(logoutRequest.getID());
157169
if (registration.getAssertingPartyMetadata().getSingleLogoutServiceBinding() == Saml2MessageBinding.POST) {
158-
String xml = serialize(OpenSamlSigningUtils.sign(logoutRequest, registration));
159-
String samlRequest = Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8));
170+
String xml = serialize(this.saml.withSigningKeys(registration.getSigningX509Credentials())
171+
.algorithms(registration.getAssertingPartyMetadata().getSigningAlgorithms())
172+
.sign(logoutRequest));
173+
String samlRequest = Saml2Utils.withDecoded(xml).encode();
160174
return result.samlRequest(samlRequest).relayState(relayState).build();
161175
}
162176
else {
163177
String xml = serialize(logoutRequest);
164-
String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml));
178+
String deflatedAndEncoded = Saml2Utils.withDecoded(xml).deflate(true).encode();
165179
result.samlRequest(deflatedAndEncoded);
166-
QueryParametersPartial partial = OpenSamlSigningUtils.sign(registration)
167-
.param(Saml2ParameterNames.SAML_REQUEST, deflatedAndEncoded)
168-
.param(Saml2ParameterNames.RELAY_STATE, relayState);
169-
return result.parameters((params) -> params.putAll(partial.parameters())).build();
180+
Map<String, String> signingParameters = new HashMap<>();
181+
signingParameters.put(Saml2ParameterNames.SAML_REQUEST, deflatedAndEncoded);
182+
signingParameters.put(Saml2ParameterNames.RELAY_STATE, relayState);
183+
Map<String, String> query = this.saml.withSigningKeys(registration.getSigningX509Credentials())
184+
.algorithms(registration.getAssertingPartyMetadata().getSigningAlgorithms())
185+
.sign(signingParameters);
186+
return result.parameters((params) -> params.putAll(query)).build();
170187
}
171188
}
172189

@@ -185,13 +202,43 @@ private String getRegistrationId(Authentication authentication) {
185202
}
186203

187204
private String serialize(LogoutRequest logoutRequest) {
188-
try {
189-
Element element = this.marshaller.marshall(logoutRequest);
190-
return SerializeSupport.nodeToString(element);
205+
return this.saml.serialize(logoutRequest).serialize();
206+
}
207+
208+
static final class LogoutRequestParameters {
209+
210+
private final HttpServletRequest request;
211+
212+
private final RelyingPartyRegistration registration;
213+
214+
private final Authentication authentication;
215+
216+
private final LogoutRequest logoutRequest;
217+
218+
LogoutRequestParameters(HttpServletRequest request, RelyingPartyRegistration registration,
219+
Authentication authentication, LogoutRequest logoutRequest) {
220+
this.request = request;
221+
this.registration = registration;
222+
this.authentication = authentication;
223+
this.logoutRequest = logoutRequest;
224+
}
225+
226+
HttpServletRequest getRequest() {
227+
return this.request;
191228
}
192-
catch (MarshallingException ex) {
193-
throw new Saml2Exception(ex);
229+
230+
RelyingPartyRegistration getRelyingPartyRegistration() {
231+
return this.registration;
232+
}
233+
234+
Authentication getAuthentication() {
235+
return this.authentication;
194236
}
237+
238+
LogoutRequest getLogoutRequest() {
239+
return this.logoutRequest;
240+
}
241+
195242
}
196243

197244
}

0 commit comments

Comments
 (0)