Skip to content

Commit

Permalink
Add RelyingPartyRegistrationsDecoder
Browse files Browse the repository at this point in the history
This adds the RelyingPartyRegistrationsDecoder component
which allows configuration with signature verification credentials.

Closes spring-projectsgh-12116
Closes spring-projectsgh-15017
Closes spring-projectsgh-15090
  • Loading branch information
jzheaux committed Jul 2, 2024
1 parent 6bd2f1c commit 476d27c
Show file tree
Hide file tree
Showing 9 changed files with 513 additions and 46 deletions.
54 changes: 54 additions & 0 deletions docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,60 @@ class MyCustomSecurityConfiguration {

In this way, the set of `RelyingPartyRegistration`s will refresh based on {spring-framework-reference-url}integration/cache/store-configuration.html[the cache's eviction schedule].

[[servlet-saml2login-relyingpartyregistrationsdecoder]]
In the event that the location you are using is an HTTP url, you should also verify the metadata's signature. You can do this by using ``RelyingPartyRegistrations``'s underlying `RelyingPartyRegistrationsDecoder` like so:

.Relying Party Registrations Decoder
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Configuration
@EnableWebSecurity
public class MyCustomSecurityConfiguration {
@Bean
RelyingPartyRegistrationsDecoder registrationsDecoder() {
PublicKey pub = lookupAssertingPartyPublicKey();
Set<Credential> credentials = Set.of(new BasicCredential(pub));
return new OpenSamlRelyingPartyRegistrationsDecoder(credentials);
}
@Bean
public RelyingPartyRegistrationRepository registrations(RelyingPartyRegistrationsDecoder decoder) {
return new InMemoryRelyingPartyRegistrationRepository(
decoder.decode("https://idp.example.org/ap/metadata").registrationId("ap").build());
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Configuration
@EnableWebSecurity
class MyCustomSecurityConfiguration {
@Bean
fun registrationsDecoder(): RelyingPartyRegistrationsDecoder {
val pub: PublicKey = lookupAssertingPartyPublicKey()
val credentials: Set<Credential> = java.util.Set.of<Credential>(BasicCredential(pub))
return OpenSamlRelyingPartyRegistrationsDecoder(credentials)
}
@Bean
fun registrations(decoder:RelyingPartyRegistrationsDecoder): RelyingPartyRegistrationRepository {
return InMemoryRelyingPartyRegistrationRepository(
decoder.decode("https://idp.example.org/ap/metadata").registrationId("ap").build())
}
}
----
======

[NOTE]
The semantics of `RelyingPartyRegistrationsDecoder` are identical to `RelyingPartyRegistrations` with two important exceptions. First, the default registration id is URL-safe. Second, once each `RelyingPartyRegistration` is built, it is not of type `OpenSamlRelyingPartyRegistration` as this class is now deprecated.

[[servlet-saml2login-relyingpartyregistration]]
== RelyingPartyRegistration
A {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[`RelyingPartyRegistration`]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ static String serialize(XMLObject object) {
}

static <O extends SignableXMLObject> O sign(O object, RelyingPartyRegistration relyingPartyRegistration) {
SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration);
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
return sign(object, algorithms, credentials);
}

static <O extends SignableXMLObject> O sign(O object, List<String> algorithms, List<Credential> credentials) {
SignatureSigningParameters parameters = resolveSigningParameters(algorithms, credentials);
try {
SignatureSupport.signObject(object, parameters);
return object;
Expand All @@ -98,6 +104,11 @@ private static SignatureSigningParameters resolveSigningParameters(
RelyingPartyRegistration relyingPartyRegistration) {
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
return resolveSigningParameters(algorithms, credentials);
}

private static SignatureSigningParameters resolveSigningParameters(List<String> algorithms,
List<Credential> credentials) {
List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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.
Expand All @@ -20,6 +20,8 @@
import java.util.Arrays;
import java.util.List;

import org.opensaml.saml.saml2.metadata.EntityDescriptor;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
Expand Down Expand Up @@ -62,13 +64,15 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
OpenSamlInitializationService.initialize();
}

private final OpenSamlMetadataRelyingPartyRegistrationConverter converter;
private final OpenSamlRelyingPartyRegistrationsDecoder converter;

/**
* Creates a {@link OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter}
*/
public OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter() {
this.converter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
this.converter = new OpenSamlRelyingPartyRegistrationsDecoder();
this.converter.setRegistrationIdGenerator(EntityDescriptor::getEntityID);
this.converter.setBuilderFactory(OpenSamlRelyingPartyRegistration.Builder::new);
}

@Override
Expand All @@ -89,7 +93,7 @@ public List<MediaType> getSupportedMediaTypes() {
@Override
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return this.converter.convert(inputMessage.getBody()).iterator().next();
return this.converter.decode(inputMessage.getBody()).iterator().next();
}

@Override
Expand Down
Loading

0 comments on commit 476d27c

Please sign in to comment.