diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc index 85ba3533bb..0bfe7b8a14 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc @@ -536,7 +536,6 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations .registrationId("my-id") .build(); ---- - .Kotlin [source,kotlin,role="secondary"] ---- @@ -547,6 +546,20 @@ val relyingPartyRegistration = RelyingPartyRegistrations ---- ==== +Note that you can also create a `RelyingPartyRegistration` from an arbitrary `InputStream` source. +One such example is when the metadata is stored in a database: + +[source,java] +---- +String xml = fromDatabase(); +try (InputStream source = new ByteArrayInputStream(xml.getBytes())) { + RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations + .fromMetadata(source) + .registrationId("my-id") + .build(); +} +---- + Though a more sophisticated setup is also possible, like so: ==== diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java index d80c0fe0a2..2ed5246f64 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -28,6 +28,7 @@ import org.springframework.security.saml2.Saml2Exception; * * @author Josh Cummings * @author Ryan Cassar + * @author Marcus da Coregio * @since 5.4 */ public final class RelyingPartyRegistrations { @@ -73,7 +74,7 @@ public final class RelyingPartyRegistrations { */ public static RelyingPartyRegistration.Builder fromMetadataLocation(String metadataLocation) { try (InputStream source = resourceLoader.getResource(metadataLocation).getInputStream()) { - return assertingPartyMetadataConverter.convert(source); + return fromMetadata(source); } catch (IOException ex) { if (ex.getCause() instanceof Saml2Exception) { @@ -83,4 +84,45 @@ public final class RelyingPartyRegistrations { } } + /** + * Return a {@link RelyingPartyRegistration.Builder} based off of the given SAML 2.0 + * Asserting Party (IDP) metadata. + * + *
+ * This method is intended for scenarios when the metadata is looked up by a separate + * mechanism. One such example is when the metadata is stored in a database. + *
+ * + *+ * The callers of this method are accountable for closing the + * {@code InputStream} source. + *
+ * + * Note that by default the registrationId is set to be the given metadata location, + * but this will most often not be sufficient. To complete the configuration, most + * applications will also need to provide a registrationId, like so: + * + *
+ * String xml = fromDatabase();
+ * try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
+ * RelyingPartyRegistration registration = RelyingPartyRegistrations
+ * .fromMetadata(source)
+ * .registrationId("registration-id")
+ * .build();
+ * }
+ *
+ *
+ * Also note that an {@code IDPSSODescriptor} typically only contains information
+ * about the asserting party. Thus, you will need to remember to still populate
+ * anything about the relying party, like any private keys the relying party will use
+ * for signing AuthnRequests.
+ * @param source the {@link InputStream} source containing the asserting party
+ * metadata
+ * @return the {@link RelyingPartyRegistration.Builder} for further configuration
+ * @since 5.6
+ */
+ public static RelyingPartyRegistration.Builder fromMetadata(InputStream source) {
+ return assertingPartyMetadataConverter.convert(source);
+ }
+
}
diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java
index 8a070f941e..9b74aa64a0 100644
--- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java
+++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -17,7 +17,9 @@
package org.springframework.security.saml2.provider.service.registration;
import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
import java.io.File;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
@@ -104,4 +106,27 @@ public class RelyingPartyRegistrationsTests {
.isThrownBy(() -> RelyingPartyRegistrations.fromMetadataLocation("filePath"));
}
+ @Test
+ public void fromMetadataInputStreamWhenResolvableThenPopulatesBuilder() throws Exception {
+ try (InputStream source = new ByteArrayInputStream(this.metadata.getBytes())) {
+ RelyingPartyRegistration registration = RelyingPartyRegistrations.fromMetadata(source).entityId("rp")
+ .build();
+ RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails();
+ assertThat(details.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth");
+ assertThat(details.getSingleSignOnServiceLocation())
+ .isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO");
+ assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST);
+ assertThat(details.getVerificationX509Credentials()).hasSize(1);
+ assertThat(details.getEncryptionX509Credentials()).hasSize(1);
+ }
+ }
+
+ @Test
+ public void fromMetadataInputStreamWhenEmptyThenSaml2Exception() throws Exception {
+ try (InputStream source = new ByteArrayInputStream("".getBytes())) {
+ assertThatExceptionOfType(Saml2Exception.class)
+ .isThrownBy(() -> RelyingPartyRegistrations.fromMetadata(source));
+ }
+ }
+
}