From 5352e3471f82fbd74fb6ab96fe8dedcc0456f9db Mon Sep 17 00:00:00 2001 From: fine-pine Date: Tue, 26 Aug 2025 18:00:15 +0900 Subject: [PATCH 1/2] Disallow usage of the openid scope in device authorization requests Closes gh-2177 Signed-off-by: fine-pine --- ...rizationRequestAuthenticationProvider.java | 6 +++++- ...ionRequestAuthenticationProviderTests.java | 20 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java index e3208148..5a80768c 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 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. @@ -39,6 +39,7 @@ import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2UserCode; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; @@ -120,6 +121,9 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE); } } + if (requestedScopes.contains(OidcScopes.OPENID)) { + throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE); + } } if (this.logger.isTraceEnabled()) { diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProviderTests.java index 81b038f6..a60abf29 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2025 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. @@ -34,6 +34,7 @@ import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2UserCode; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; @@ -165,6 +166,23 @@ public class OAuth2DeviceAuthorizationRequestAuthenticationProviderTests { // @formatter:on } + @Test + public void authenticateWhenOpenIdScopeThenThrowOAuth2AuthenticationException() { + RegisteredClient registeredClient = TestRegisteredClients.registeredClient() + .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) + .scope(OidcScopes.OPENID) + .build(); + Authentication authentication = createAuthentication(registeredClient); + // @formatter:off + assertThatExceptionOfType(OAuth2AuthenticationException.class) + .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) + .withMessageContaining(OAuth2ParameterNames.SCOPE) + .extracting(OAuth2AuthenticationException::getError) + .extracting(OAuth2Error::getErrorCode) + .isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE); + // @formatter:on + } + @Test public void authenticateWhenDeviceCodeIsNullThenThrowOAuth2AuthenticationException() { @SuppressWarnings("unchecked") From 4a3a1baea4a4b551e39f1f3ffa544e4af2a2ebdf Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:15:00 -0400 Subject: [PATCH 2/2] Polish gh-2177 --- docs/src/test/java/sample/jpa/JpaTests.java | 5 +++-- .../src/test/java/sample/redis/RedisTests.java | 7 +++++-- .../java/sample/util/RegisteredClients.java | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/docs/src/test/java/sample/jpa/JpaTests.java b/docs/src/test/java/sample/jpa/JpaTests.java index 58048c4d..51419d41 100644 --- a/docs/src/test/java/sample/jpa/JpaTests.java +++ b/docs/src/test/java/sample/jpa/JpaTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 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. @@ -63,6 +63,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; +import static sample.util.RegisteredClients.deviceMessagingClient; import static sample.util.RegisteredClients.messagingClient; /** @@ -140,7 +141,7 @@ public class JpaTests { assertThat(this.authorizationService).isInstanceOf(JpaOAuth2AuthorizationService.class); assertThat(this.authorizationConsentService).isInstanceOf(JpaOAuth2AuthorizationConsentService.class); - RegisteredClient registeredClient = messagingClient(); + RegisteredClient registeredClient = deviceMessagingClient(); this.registeredClientRepository.save(registeredClient); DeviceAuthorizationGrantFlow deviceAuthorizationGrantFlow = new DeviceAuthorizationGrantFlow(this.mockMvc); diff --git a/docs/src/test/java/sample/redis/RedisTests.java b/docs/src/test/java/sample/redis/RedisTests.java index b3f80910..b4530f37 100644 --- a/docs/src/test/java/sample/redis/RedisTests.java +++ b/docs/src/test/java/sample/redis/RedisTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 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. @@ -63,6 +63,8 @@ import static org.assertj.core.api.Assertions.assertThat; public class RedisTests { private static final RegisteredClient TEST_MESSAGING_CLIENT = RegisteredClients.messagingClient(); + private static final RegisteredClient TEST_DEVICE_MESSAGING_CLIENT = RegisteredClients.deviceMessagingClient(); + @Autowired private MockMvc mockMvc; @@ -126,7 +128,7 @@ public class RedisTests { assertThat(this.authorizationService).isInstanceOf(RedisOAuth2AuthorizationService.class); assertThat(this.authorizationConsentService).isInstanceOf(RedisOAuth2AuthorizationConsentService.class); - RegisteredClient registeredClient = TEST_MESSAGING_CLIENT; + RegisteredClient registeredClient = TEST_DEVICE_MESSAGING_CLIENT; DeviceAuthorizationGrantFlow deviceAuthorizationGrantFlow = new DeviceAuthorizationGrantFlow(this.mockMvc); deviceAuthorizationGrantFlow.setUsername("user"); @@ -194,6 +196,7 @@ public class RedisTests { void postConstruct() throws IOException { this.redisServer.start(); this.registeredClientRepository.save(TEST_MESSAGING_CLIENT); + this.registeredClientRepository.save(TEST_DEVICE_MESSAGING_CLIENT); } @PreDestroy diff --git a/docs/src/test/java/sample/util/RegisteredClients.java b/docs/src/test/java/sample/util/RegisteredClients.java index e9ccb162..f4cff8a9 100644 --- a/docs/src/test/java/sample/util/RegisteredClients.java +++ b/docs/src/test/java/sample/util/RegisteredClients.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2025 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. @@ -46,4 +46,20 @@ public class RegisteredClients { .build(); } // @formatter:on + + // @formatter:off + public static RegisteredClient deviceMessagingClient() { + return RegisteredClient.withId(UUID.randomUUID().toString()) + .clientId("device-messaging-client") + .clientSecret("{noop}secret") + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) + .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .scope("message.read") + .scope("message.write") + .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) + .build(); + } + // @formatter:on + }