Address reviewer requested changes

Closes gh-17806

Signed-off-by: Bernard Budano <bbudano@gmail.com>
This commit is contained in:
Bernard Budano 2025-09-10 19:06:22 +02:00 committed by Rob Winch
parent 8e3cf9677c
commit 02a948da81
4 changed files with 44 additions and 15 deletions

View File

@ -5,7 +5,7 @@ Spring Security's OAuth Support can integrate with `RestClient` and `WebClient`
[[configuration]] [[configuration]]
== Configuration == Configuration
After xref:features/integrations/rest/http-interface.adoc#configuration-restclient[RestClient] or xref:features/integrations/rest/http-interface.adoc#configuration-webclient[WebClient] specific configuration, usage of xref:features/integrations/rest/http-interface.adoc[] only requires adding a xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] to methods that require OAuth. After xref:features/integrations/rest/http-interface.adoc#configuration-restclient[RestClient] or xref:features/integrations/rest/http-interface.adoc#configuration-webclient[WebClient] specific configuration, usage of xref:features/integrations/rest/http-interface.adoc[] only requires adding a xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] to methods that require OAuth or their declaring HTTP interface.
Since the presence of xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] determines if and how the OAuth token will be resolved, it is safe to add Spring Security's OAuth support any configuration. Since the presence of xref:features/integrations/rest/http-interface.adoc#client-registration-id[`@ClientRegistrationId`] determines if and how the OAuth token will be resolved, it is safe to add Spring Security's OAuth support any configuration.

View File

@ -49,6 +49,7 @@ http.csrf((csrf) -> csrf.spa());
* Added OAuth2 Support for xref:features/integrations/rest/http-interface.adoc[HTTP Interface Integration] * Added OAuth2 Support for xref:features/integrations/rest/http-interface.adoc[HTTP Interface Integration]
* Added support for custom `JwkSource` in `NimbusJwtDecoder`, allowing usage of Nimbus's `JwkSourceBuilder` API * Added support for custom `JwkSource` in `NimbusJwtDecoder`, allowing usage of Nimbus's `JwkSourceBuilder` API
* Added builder for `NimbusJwtEncoder`, supports specifying an EC or RSA key pair or a secret key * Added builder for `NimbusJwtEncoder`, supports specifying an EC or RSA key pair or a secret key
* Added support for `@ClientRegistrationId` at class level, eliminating the need for method level repetition
== SAML 2.0 == SAML 2.0

View File

@ -17,12 +17,12 @@
package org.springframework.security.oauth2.client.web.client; package org.springframework.security.oauth2.client.web.client;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Optional;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.core.annotation.SecurityAnnotationScanner;
import org.springframework.security.core.annotation.SecurityAnnotationScanners;
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId; import org.springframework.security.oauth2.client.annotation.ClientRegistrationId;
import org.springframework.security.oauth2.client.web.ClientAttributes; import org.springframework.security.oauth2.client.web.ClientAttributes;
import org.springframework.web.service.invoker.HttpRequestValues; import org.springframework.web.service.invoker.HttpRequestValues;
@ -38,15 +38,13 @@ public final class ClientRegistrationIdProcessor implements HttpRequestValues.Pr
public static ClientRegistrationIdProcessor DEFAULT_INSTANCE = new ClientRegistrationIdProcessor(); public static ClientRegistrationIdProcessor DEFAULT_INSTANCE = new ClientRegistrationIdProcessor();
private ClientRegistrationIdProcessor() { private SecurityAnnotationScanner<ClientRegistrationId> securityAnnotationScanner = SecurityAnnotationScanners
} .requireUnique(ClientRegistrationId.class);
@Override @Override
public void process(Method method, MethodParameter[] parameters, @Nullable Object[] arguments, public void process(Method method, MethodParameter[] parameters, @Nullable Object[] arguments,
HttpRequestValues.Builder builder) { HttpRequestValues.Builder builder) {
ClientRegistrationId registeredId = Optional ClientRegistrationId registeredId = this.securityAnnotationScanner.scan(method, method.getDeclaringClass());
.ofNullable(AnnotationUtils.findAnnotation(method, ClientRegistrationId.class))
.orElseGet(() -> AnnotationUtils.findAnnotation(method.getDeclaringClass(), ClientRegistrationId.class));
if (registeredId != null) { if (registeredId != null) {
String registrationId = registeredId.registrationId(); String registrationId = registeredId.registrationId();
@ -54,4 +52,7 @@ public final class ClientRegistrationIdProcessor implements HttpRequestValues.Pr
} }
} }
private ClientRegistrationIdProcessor() {
}
} }

View File

@ -22,12 +22,14 @@ import java.lang.reflect.Method;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId; import org.springframework.security.oauth2.client.annotation.ClientRegistrationId;
import org.springframework.security.oauth2.client.web.ClientAttributes; import org.springframework.security.oauth2.client.web.ClientAttributes;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.web.service.invoker.HttpRequestValues; import org.springframework.web.service.invoker.HttpRequestValues;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/** /**
* Unit tests for {@link ClientRegistrationIdProcessor}. * Unit tests for {@link ClientRegistrationIdProcessor}.
@ -56,9 +58,8 @@ class ClientRegistrationIdProcessorTests {
@Test @Test
void processWhenMetaClientRegistrationIdPresentThenSet() { void processWhenMetaClientRegistrationIdPresentThenSet() {
HttpRequestValues.Builder builder = HttpRequestValues.builder(); HttpRequestValues.Builder builder = HttpRequestValues.builder();
Method hasMetaClientRegistrationId = ReflectionUtils.findMethod(RestService.class, Method hasClientRegistrationId = ReflectionUtils.findMethod(RestService.class, "hasMetaClientRegistrationId");
"hasMetaClientRegistrationId"); this.processor.process(hasClientRegistrationId, null, null, builder);
this.processor.process(hasMetaClientRegistrationId, null, null, builder);
String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes()); String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes());
assertThat(registrationId).isEqualTo(REGISTRATION_ID); assertThat(registrationId).isEqualTo(REGISTRATION_ID);
@ -67,8 +68,8 @@ class ClientRegistrationIdProcessorTests {
@Test @Test
void processWhenNoClientRegistrationIdPresentThenNull() { void processWhenNoClientRegistrationIdPresentThenNull() {
HttpRequestValues.Builder builder = HttpRequestValues.builder(); HttpRequestValues.Builder builder = HttpRequestValues.builder();
Method noClientRegistrationId = ReflectionUtils.findMethod(RestService.class, "noClientRegistrationId"); Method hasClientRegistrationId = ReflectionUtils.findMethod(RestService.class, "noClientRegistrationId");
this.processor.process(noClientRegistrationId, null, null, builder); this.processor.process(hasClientRegistrationId, null, null, builder);
String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes()); String registrationId = ClientAttributes.resolveClientRegistrationId(builder.build().getAttributes());
assertThat(registrationId).isNull(); assertThat(registrationId).isNull();
@ -77,7 +78,7 @@ class ClientRegistrationIdProcessorTests {
@Test @Test
void processWhenClientRegistrationIdPresentOnDeclaringClassThenSet() { void processWhenClientRegistrationIdPresentOnDeclaringClassThenSet() {
HttpRequestValues.Builder builder = HttpRequestValues.builder(); HttpRequestValues.Builder builder = HttpRequestValues.builder();
Method declaringClassHasClientRegistrationId = ReflectionUtils.findMethod(AnnotatedRestService.class, Method declaringClassHasClientRegistrationId = ReflectionUtils.findMethod(TypeAnnotatedRestService.class,
"declaringClassHasClientRegistrationId"); "declaringClassHasClientRegistrationId");
this.processor.process(declaringClassHasClientRegistrationId, null, null, builder); this.processor.process(declaringClassHasClientRegistrationId, null, null, builder);
@ -85,6 +86,16 @@ class ClientRegistrationIdProcessorTests {
assertThat(registrationId).isEqualTo(REGISTRATION_ID); assertThat(registrationId).isEqualTo(REGISTRATION_ID);
} }
@Test
void processWhenDuplicateClientRegistrationIdPresentOnAggregateServiceThenException() {
HttpRequestValues.Builder builder = HttpRequestValues.builder();
Method shouldFailDueToDuplicateClientRegistrationId = ReflectionUtils.findMethod(AggregateRestService.class,
"shouldFailDueToDuplicateClientRegistrationId");
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(
() -> this.processor.process(shouldFailDueToDuplicateClientRegistrationId, null, null, builder));
}
interface RestService { interface RestService {
@ClientRegistrationId(REGISTRATION_ID) @ClientRegistrationId(REGISTRATION_ID)
@ -104,10 +115,26 @@ class ClientRegistrationIdProcessorTests {
} }
@ClientRegistrationId(REGISTRATION_ID) @ClientRegistrationId(REGISTRATION_ID)
interface AnnotatedRestService { interface TypeAnnotatedRestService {
void declaringClassHasClientRegistrationId(); void declaringClassHasClientRegistrationId();
} }
@ClientRegistrationId("a")
interface ARestService {
}
@ClientRegistrationId("b")
interface BRestService {
}
interface AggregateRestService extends ARestService, BRestService {
void shouldFailDueToDuplicateClientRegistrationId();
}
} }