diff --git a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/RelyingPartyMetadata.java b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/RelyingPartyMetadata.java new file mode 100644 index 0000000..63c8c30 --- /dev/null +++ b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/RelyingPartyMetadata.java @@ -0,0 +1,133 @@ +/* + * Copyright 2002-2024 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 example; + +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.io.ApplicationResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.security.converter.RsaKeyConverters; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("saml2") +public class RelyingPartyMetadata { + + private final ResourceLoader resourceLoader = new ApplicationResourceLoader(); + + private String entityId = "{baseUrl}/saml/metadata"; + + private String sso = "{baseUrl}/saml/SSO"; + + private SingleLogout slo = new SingleLogout(); + + private X509Certificate certificate; + + private RSAPrivateKey key; + + public RelyingPartyRegistration apply(RelyingPartyRegistration.Builder builder) { + Saml2X509Credential signing = Saml2X509Credential.signing(this.key, this.certificate); + return builder.entityId(this.entityId) + .assertionConsumerServiceLocation(this.sso) + .singleLogoutServiceBinding(this.slo.getBinding()) + .singleLogoutServiceLocation(this.slo.getLocation()) + .singleLogoutServiceResponseLocation(this.slo.getResponseLocation()) + .signingX509Credentials((c) -> c.add(signing)) + .build(); + } + + public void setEntityId(String entityId) { + this.entityId = entityId; + } + + public void setSso(String sso) { + this.sso = sso; + } + + public void setSlo(SingleLogout slo) { + this.slo = slo; + } + + public void setCertificate(String certificate) { + Resource source = this.resourceLoader.getResource(certificate); + try (InputStream in = source.getInputStream()) { + CertificateFactory certificates = CertificateFactory.getInstance("X.509"); + this.certificate = (X509Certificate) certificates.generateCertificate(in); + } + catch (CertificateException | IOException ex) { + throw new IllegalArgumentException(ex); + } + } + + public void setKey(String key) { + Resource source = this.resourceLoader.getResource(key); + try (InputStream in = source.getInputStream()) { + this.key = RsaKeyConverters.pkcs8().convert(in); + } + catch (IOException ex) { + throw new IllegalArgumentException(ex); + } + } + + public static class SingleLogout { + + private Saml2MessageBinding binding = Saml2MessageBinding.REDIRECT; + + private String location = "{baseUrl}/saml/logout"; + + private String responseLocation = "{baseUrl}/saml/SingleLogout"; + + public Saml2MessageBinding getBinding() { + return this.binding; + } + + public void setBinding(Saml2MessageBinding binding) { + this.binding = binding; + } + + public String getLocation() { + return this.location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getResponseLocation() { + if (this.responseLocation == null) { + return this.location; + } + return this.responseLocation; + } + + public void setResponseLocation(String responseLocation) { + this.responseLocation = responseLocation; + } + + } + +} diff --git a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SecurityConfiguration.java b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SecurityConfiguration.java index 7fbc953..95c7f64 100644 --- a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SecurityConfiguration.java +++ b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/java/example/SecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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,26 +16,16 @@ package example; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPrivateKey; -import java.util.UUID; -import java.util.stream.Collectors; +import java.util.List; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties; -import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyProperties.Registration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; import org.springframework.security.web.SecurityFilterChain; @@ -60,32 +50,13 @@ public class SecurityConfiguration { } @Bean - InMemoryRelyingPartyRegistrationRepository repository(Saml2RelyingPartyProperties properties, - @Value("classpath:credentials/rp-private.key") RSAPrivateKey key, - @Value("classpath:credentials/rp-certificate.crt") File cert) { - Saml2X509Credential signing = Saml2X509Credential.signing(key, x509Certificate(cert)); - Registration registration = properties.getRegistration().values().iterator().next(); - return new InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistrations - .collectionFromMetadataLocation(registration.getAssertingparty().getMetadataUri()) + RelyingPartyRegistrationRepository registrations(RelyingPartyMetadata rp, + @Value("${saml2.ap.metadata}") String ap) { + List registrations = RelyingPartyRegistrations.collectionFromMetadataLocation(ap) .stream() - .map((builder) -> builder.registrationId(UUID.randomUUID().toString()) - .entityId(registration.getEntityId()) - .assertionConsumerServiceLocation(registration.getAcs().getLocation()) - .singleLogoutServiceBinding(registration.getSinglelogout().getBinding()) - .singleLogoutServiceLocation(registration.getSinglelogout().getUrl()) - .singleLogoutServiceResponseLocation(registration.getSinglelogout().getResponseUrl()) - .signingX509Credentials((credentials) -> credentials.add(signing)) - .build()) - .collect(Collectors.toList())); - } - - X509Certificate x509Certificate(File location) { - try (InputStream source = new FileInputStream(location)) { - return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(source); - } - catch (CertificateException | IOException ex) { - throw new IllegalArgumentException(ex); - } + .map(rp::apply) + .toList(); + return new InMemoryRelyingPartyRegistrationRepository(registrations); } } diff --git a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/application.yml b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/application.yml index 5abbffb..61eebdf 100644 --- a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/application.yml +++ b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/application.yml @@ -10,15 +10,8 @@ spring: security: filter: dispatcher-types: async, error, request, forward - saml2: - relyingparty: - registration: - metadata: - entity-id: "{baseUrl}/saml/metadata" - singlelogout: - binding: REDIRECT - url: "{baseUrl}/saml/logout" - responseUrl: "{baseUrl}/saml/SingleLogout" - acs: - location: "{baseUrl}/saml/SSO" - assertingparty.metadata-uri: http://idp-one.7f000001.nip.io/simplesaml/saml2/idp/metadata.php + +saml2: + certificate: classpath:credentials/rp-certificate.crt + key: classpath:credentials/rp-private.key + ap.metadata: http://idp-one.7f000001.nip.io/simplesaml/saml2/idp/metadata.php \ No newline at end of file diff --git a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/credentials/idp-certificate.crt b/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/credentials/idp-certificate.crt deleted file mode 100644 index 9c4ee07..0000000 --- a/servlet/spring-boot/java/saml2/saml-extension-federation/src/main/resources/credentials/idp-certificate.crt +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD -VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD -VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX -c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw -aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ -BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa -BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD -DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr -QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62 -E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz -2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW -RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ -nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5 -cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph -iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5 -ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD -AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO -nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v -ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu -xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z -V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3 -lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk ------END CERTIFICATE-----