Skip to content

Commit f460ea5

Browse files
committed
Support POST binding for AuthNRequest
Has been tested with - Keycloak - SSOCircle - Okta - SimpleSAMLPhp This PR extends (builds on previous commits and adds user configuration options) #7758
1 parent ec61462 commit f460ea5

File tree

14 files changed

+660
-72
lines changed

14 files changed

+660
-72
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestRelyingPartyRegistrations.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ static RelyingPartyRegistration saml2AuthenticationConfiguration() {
4343
Saml2X509Credential idpVerificationCertificate = verificationCertificate();
4444
String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
4545
return RelyingPartyRegistration.withRegistrationId(registrationId)
46-
.remoteIdpEntityId(idpEntityId)
47-
.idpWebSsoUrl(webSsoEndpoint)
46+
.providerDetails(c -> c.entityId(idpEntityId))
47+
.providerDetails(c -> c.webSsoUrl(webSsoEndpoint))
4848
.credentials(c -> c.add(signingCredential))
4949
.credentials(c -> c.add(idpVerificationCertificate))
5050
.localEntityIdTemplate(localEntityIdTemplate)

config/src/test/kotlin/org/springframework/security/config/web/servlet/Saml2DslTests.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ class Saml2DslTests {
8585
relyingPartyRegistrationRepository =
8686
InMemoryRelyingPartyRegistrationRepository(
8787
RelyingPartyRegistration.withRegistrationId("samlId")
88-
.remoteIdpEntityId("entityId")
8988
.assertionConsumerServiceUrlTemplate("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI)
9089
.credentials { c -> c.add(Saml2X509Credential(loadCert("rod.cer"), VERIFICATION)) }
91-
.idpWebSsoUrl("ssoUrl")
90+
.providerDetails { c -> c.webSsoUrl("ssoUrl") }
91+
.providerDetails { c -> c.entityId("entityId") }
9292
.build()
9393
)
9494
}

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

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
5454
*/
5555
@Override
5656
public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) {
57-
String xml = createAuthenticationRequest(context, context.getRelyingPartyRegistration().getSigningCredentials());
57+
List<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration().getProviderDetails().isSignAuthNRequest() ?
58+
context.getRelyingPartyRegistration().getSigningCredentials() :
59+
emptyList();
60+
61+
String xml = createAuthenticationRequest(context, signingCredentials);
5862
return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context)
5963
.samlRequest(samlEncode(xml.getBytes(UTF_8)))
6064
.build();
@@ -66,19 +70,24 @@ public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2Authe
6670
@Override
6771
public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext context) {
6872
String xml = createAuthenticationRequest(context, emptyList());
69-
List<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration().getSigningCredentials();
7073
Builder result = Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context);
71-
7274
String deflatedAndEncoded = samlEncode(samlDeflate(xml));
73-
Map<String, String> signedParams = this.saml.signQueryParameters(
74-
signingCredentials,
75-
deflatedAndEncoded,
76-
context.getRelayState()
77-
);
78-
result.samlRequest(signedParams.get("SAMLRequest"))
79-
.relayState(signedParams.get("RelayState"))
80-
.sigAlg(signedParams.get("SigAlg"))
81-
.signature(signedParams.get("Signature"));
75+
result.samlRequest(deflatedAndEncoded)
76+
.relayState(context.getRelayState());
77+
78+
if (context.getRelyingPartyRegistration().getProviderDetails().isSignAuthNRequest()) {
79+
List<Saml2X509Credential> signingCredentials = context.getRelyingPartyRegistration().getSigningCredentials();
80+
Map<String, String> signedParams = this.saml.signQueryParameters(
81+
signingCredentials,
82+
deflatedAndEncoded,
83+
context.getRelayState()
84+
);
85+
result.samlRequest(signedParams.get("SAMLRequest"))
86+
.relayState(signedParams.get("RelayState"))
87+
.sigAlg(signedParams.get("SigAlg"))
88+
.signature(signedParams.get("Signature"));
89+
}
90+
8291
return result.build();
8392
}
8493

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public String getRelayState() {
9191
* @return the Destination value
9292
*/
9393
public String getDestination() {
94-
return this.getRelyingPartyRegistration().getIdpWebSsoUrl();
94+
return this.getRelyingPartyRegistration().getProviderDetails().getWebSsoUrl();
9595
}
9696

9797
/**

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java

Lines changed: 201 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@
5151
* //IDP certificate for verification of incoming messages
5252
* Saml2X509Credential idpVerificationCertificate = getVerificationCertificate();
5353
* RelyingPartyRegistration rp = RelyingPartyRegistration.withRegistrationId(registrationId)
54-
* .remoteIdpEntityId(idpEntityId)
55-
* .idpWebSsoUrl(webSsoEndpoint)
54+
* .providerDetails(config -> config.entityId(idpEntityId));
55+
* .providerDetails(config -> config.webSsoUrl(url));
5656
* .credentials(c -> c.add(signingCredential))
5757
* .credentials(c -> c.add(idpVerificationCertificate))
5858
* .localEntityIdTemplate(localEntityIdTemplate)
@@ -64,37 +64,41 @@
6464
public class RelyingPartyRegistration {
6565

6666
private final String registrationId;
67-
private final String remoteIdpEntityId;
6867
private final String assertionConsumerServiceUrlTemplate;
69-
private final String idpWebSsoUrl;
7068
private final List<Saml2X509Credential> credentials;
7169
private final String localEntityIdTemplate;
72-
73-
private RelyingPartyRegistration(String idpEntityId, String registrationId, String assertionConsumerServiceUrlTemplate,
74-
String idpWebSsoUri, List<Saml2X509Credential> credentials, String localEntityIdTemplate) {
75-
hasText(idpEntityId, "idpEntityId cannot be empty");
70+
private final ProviderDetails providerDetails;
71+
72+
private RelyingPartyRegistration(
73+
String registrationId,
74+
String assertionConsumerServiceUrlTemplate,
75+
ProviderDetails providerDetails,
76+
List<Saml2X509Credential> credentials,
77+
String localEntityIdTemplate) {
7678
hasText(registrationId, "registrationId cannot be empty");
7779
hasText(assertionConsumerServiceUrlTemplate, "assertionConsumerServiceUrlTemplate cannot be empty");
7880
hasText(localEntityIdTemplate, "localEntityIdTemplate cannot be empty");
7981
notEmpty(credentials, "credentials cannot be empty");
80-
notNull(idpWebSsoUri, "idpWebSsoUri cannot be empty");
82+
notNull(providerDetails, "providerDetails cannot be null");
83+
hasText(providerDetails.webSsoUrl, "providerDetails.webSsoUrl cannot be empty");
8184
for (Saml2X509Credential c : credentials) {
8285
notNull(c, "credentials cannot contain null elements");
8386
}
8487
this.registrationId = registrationId;
85-
this.remoteIdpEntityId = idpEntityId;
8688
this.assertionConsumerServiceUrlTemplate = assertionConsumerServiceUrlTemplate;
8789
this.credentials = unmodifiableList(new LinkedList<>(credentials));
88-
this.idpWebSsoUrl = idpWebSsoUri;
90+
this.providerDetails = providerDetails;
8991
this.localEntityIdTemplate = localEntityIdTemplate;
9092
}
9193

9294
/**
9395
* Returns the entity ID of the IDP, the asserting party.
9496
* @return entity ID of the asserting party
97+
* @deprecated use {@link ProviderDetails#getEntityId()} from {@link #getProviderDetails()}
9598
*/
99+
@Deprecated
96100
public String getRemoteIdpEntityId() {
97-
return this.remoteIdpEntityId;
101+
return this.providerDetails.getEntityId();
98102
}
99103

100104
/**
@@ -119,9 +123,20 @@ public String getAssertionConsumerServiceUrlTemplate() {
119123
* Contains the URL for which to send the SAML 2 Authentication Request to initiate
120124
* a single sign on flow.
121125
* @return a IDP URL that accepts REDIRECT or POST binding for authentication requests
126+
* @deprecated use {@link ProviderDetails#getWebSsoUrl()} from {@link #getProviderDetails()}
122127
*/
128+
@Deprecated
123129
public String getIdpWebSsoUrl() {
124-
return this.idpWebSsoUrl;
130+
return this.getProviderDetails().webSsoUrl;
131+
}
132+
133+
/**
134+
* Returns specific configuration around the Identity Provider SSO endpoint
135+
* @return the IDP SSO endpoint configuration
136+
* @since 5.3
137+
*/
138+
public ProviderDetails getProviderDetails() {
139+
return this.providerDetails;
125140
}
126141

127142
/**
@@ -200,13 +215,158 @@ public static Builder withRegistrationId(String registrationId) {
200215
return new Builder(registrationId);
201216
}
202217

203-
public static class Builder {
218+
/**
219+
* Creates a {@code RelyingPartyRegistration} {@link Builder} based on an existing object
220+
* @param registration the {@code RelyingPartyRegistration}
221+
* @return {@code Builder} to create a {@code RelyingPartyRegistration} object
222+
*/
223+
public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) {
224+
Assert.notNull(registration, "registration cannot be null");
225+
return withRegistrationId(registration.getRegistrationId())
226+
.providerDetails(c -> {
227+
c.webSsoUrl(registration.getProviderDetails().getWebSsoUrl());
228+
c.binding(registration.getProviderDetails().getBinding());
229+
c.signAuthNRequest(registration.getProviderDetails().isSignAuthNRequest());
230+
c.entityId(registration.getProviderDetails().getEntityId());
231+
})
232+
.credentials(c -> c.addAll(registration.getCredentials()))
233+
.localEntityIdTemplate(registration.getLocalEntityIdTemplate())
234+
.assertionConsumerServiceUrlTemplate(registration.getAssertionConsumerServiceUrlTemplate())
235+
;
236+
}
237+
238+
/**
239+
* Configuration for IDP SSO endpoint configuration
240+
* @since 5.3
241+
*/
242+
public final static class ProviderDetails {
243+
private final String entityId;
244+
private final String webSsoUrl;
245+
private final boolean signAuthNRequest;
246+
private final Saml2MessageBinding binding;
247+
248+
private ProviderDetails(
249+
String entityId,
250+
String webSsoUrl,
251+
boolean signAuthNRequest,
252+
Saml2MessageBinding binding) {
253+
hasText(entityId, "entityId cannot be null or empty");
254+
notNull(webSsoUrl, "webSsoUrl cannot be null");
255+
notNull(binding, "binding cannot be null");
256+
this.entityId = entityId;
257+
this.webSsoUrl = webSsoUrl;
258+
this.signAuthNRequest = signAuthNRequest;
259+
this.binding = binding;
260+
}
261+
262+
/**
263+
* Returns the entity ID of the Identity Provider
264+
* @return the entity ID of the IDP
265+
*/
266+
public String getEntityId() {
267+
return entityId;
268+
}
269+
270+
/**
271+
* Contains the URL for which to send the SAML 2 Authentication Request to initiate
272+
* a single sign on flow.
273+
* @return a IDP URL that accepts REDIRECT or POST binding for authentication requests
274+
*/
275+
public String getWebSsoUrl() {
276+
return webSsoUrl;
277+
}
278+
279+
/**
280+
* @return {@code true} if AuthNRequests from this relying party to the IDP should be signed
281+
* {@code false} if no signature is required.
282+
*/
283+
public boolean isSignAuthNRequest() {
284+
return signAuthNRequest;
285+
}
286+
287+
/**
288+
* @return the type of SAML 2 Binding the AuthNRequest should be sent on
289+
*/
290+
public Saml2MessageBinding getBinding() {
291+
return binding;
292+
}
293+
294+
/**
295+
* Builder for IDP SSO endpoint configuration
296+
* @since 5.3
297+
*/
298+
public final static class Builder {
299+
private String entityId;
300+
private String webSsoUrl;
301+
private boolean signAuthNRequest = true;
302+
private Saml2MessageBinding binding = Saml2MessageBinding.REDIRECT;
303+
304+
/**
305+
* Sets the {@code EntityID} for the remote asserting party, the Identity Provider.
306+
*
307+
* @param entityId - the EntityID of the IDP. May be a URL.
308+
* @return this object
309+
*/
310+
public Builder entityId(String entityId) {
311+
this.entityId = entityId;
312+
return this;
313+
}
314+
315+
/**
316+
* Sets the {@code SSO URL} for the remote asserting party, the Identity Provider.
317+
*
318+
* @param url - a URL that accepts authentication requests via REDIRECT or POST bindings
319+
* @return this object
320+
*/
321+
public Builder webSsoUrl(String url) {
322+
this.webSsoUrl = url;
323+
return this;
324+
}
325+
326+
/**
327+
* Set to true if the AuthNRequest message should be signed
328+
*
329+
* @param signAuthNRequest true if the message should be signed
330+
* @return this object
331+
*/
332+
public Builder signAuthNRequest(boolean signAuthNRequest) {
333+
this.signAuthNRequest = signAuthNRequest;
334+
return this;
335+
}
336+
337+
338+
/**
339+
* Sets the message binding to be used when sending an AuthNRequest message
340+
*
341+
* @param binding either {@link Saml2MessageBinding#POST} or {@link Saml2MessageBinding#REDIRECT}
342+
* @return this object
343+
*/
344+
public Builder binding(Saml2MessageBinding binding) {
345+
this.binding = binding;
346+
return this;
347+
}
348+
349+
/**
350+
* Creates an immutable ProviderDetails object representing the configuration for an Identity Provider, IDP
351+
* @return immutable ProviderDetails object
352+
*/
353+
public ProviderDetails build() {
354+
return new ProviderDetails(
355+
this.entityId,
356+
this.webSsoUrl,
357+
this.signAuthNRequest,
358+
this.binding
359+
);
360+
}
361+
}
362+
}
363+
364+
public final static class Builder {
204365
private String registrationId;
205-
private String remoteIdpEntityId;
206-
private String idpWebSsoUrl;
207366
private String assertionConsumerServiceUrlTemplate;
208367
private List<Saml2X509Credential> credentials = new LinkedList<>();
209368
private String localEntityIdTemplate = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
369+
private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder();
210370

211371
private Builder(String registrationId) {
212372
this.registrationId = registrationId;
@@ -227,9 +387,11 @@ public Builder registrationId(String id) {
227387
* Sets the {@code entityId} for the remote asserting party, the Identity Provider.
228388
* @param entityId the IDP entityId
229389
* @return this object
390+
* @deprecated use {@link #providerDetails(Consumer< ProviderDetails.Builder >)}
230391
*/
392+
@Deprecated
231393
public Builder remoteIdpEntityId(String entityId) {
232-
this.remoteIdpEntityId = entityId;
394+
this.providerDetails(idp -> idp.entityId(entityId));
233395
return this;
234396
}
235397

@@ -250,9 +412,21 @@ public Builder assertionConsumerServiceUrlTemplate(String assertionConsumerServi
250412
* Sets the {@code SSO URL} for the remote asserting party, the Identity Provider.
251413
* @param url - a URL that accepts authentication requests via REDIRECT or POST bindings
252414
* @return this object
415+
* @deprecated use {@link #providerDetails(Consumer< ProviderDetails.Builder >)}
253416
*/
417+
@Deprecated
254418
public Builder idpWebSsoUrl(String url) {
255-
this.idpWebSsoUrl = url;
419+
providerDetails(config -> config.webSsoUrl(url));
420+
return this;
421+
}
422+
423+
/**
424+
* Configures the IDP SSO endpoint
425+
* @param providerDetails a consumer that configures the IDP SSO endpoint
426+
* @return this object
427+
*/
428+
public Builder providerDetails(Consumer<ProviderDetails.Builder> providerDetails) {
429+
providerDetails.accept(this.providerDetails);
256430
return this;
257431
}
258432

@@ -288,17 +462,19 @@ public Builder localEntityIdTemplate(String template) {
288462
return this;
289463
}
290464

465+
/**
466+
* Constructs a RelyingPartyRegistration object based on the builder configurations
467+
* @return a RelyingPartyRegistration instance
468+
*/
291469
public RelyingPartyRegistration build() {
292470
return new RelyingPartyRegistration(
293-
remoteIdpEntityId,
294-
registrationId,
295-
assertionConsumerServiceUrlTemplate,
296-
idpWebSsoUrl,
297-
credentials,
298-
localEntityIdTemplate
471+
this.registrationId,
472+
this.assertionConsumerServiceUrlTemplate,
473+
this.providerDetails.build(),
474+
this.credentials,
475+
this.localEntityIdTemplate
299476
);
300477
}
301478
}
302479

303-
304480
}

0 commit comments

Comments
 (0)